Fix registration error responses
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m18s

This commit is contained in:
zyc 2026-06-01 17:40:33 +08:00
parent ad22999fdb
commit 29867f91c1
5 changed files with 76 additions and 19 deletions

View File

@ -238247,7 +238247,7 @@ function validateFields(shape, source = "body") {
if (!parseResult.success) {
const errors = parseResult.error.issues.map((issue3) => `\u5B57\u6BB5 ${issue3.path.join(".")} ${issue3.message}`);
console.error(errors);
return res.status(400).json({ message: "\u53C2\u6570\u9519\u8BEF", errors });
return res.status(400).json(error50("\u53C2\u6570\u9519\u8BEF", errors));
}
next();
};
@ -238256,6 +238256,7 @@ var init_middleware = __esm({
"src/middleware/middleware.ts"() {
"use strict";
init_zod();
init_responseFormat();
init_locales2();
external_exports.config(zh_CN_default());
}
@ -240227,6 +240228,11 @@ var init_me = __esm({
});
// src/lib/smsCode.ts
function createSmsCodeError(message) {
const error73 = new Error(message);
error73.status = 400;
return error73;
}
function createNumericCode() {
return String(Math.floor(1e5 + Math.random() * 9e5));
}
@ -240234,7 +240240,7 @@ async function assertCanSendSmsCode(phone, purpose) {
const latest = await utils_default.db("o_smsCode").where({ phone, purpose, used: 0 }).orderBy("createTime", "desc").first();
if (latest?.sentAt && Date.now() - latest.sentAt < RESEND_INTERVAL_MS) {
const waitSeconds = Math.ceil((RESEND_INTERVAL_MS - (Date.now() - latest.sentAt)) / 1e3);
throw new Error(`\u9A8C\u8BC1\u7801\u53D1\u9001\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7 ${waitSeconds} \u79D2\u540E\u518D\u8BD5`);
throw createSmsCodeError(`\u9A8C\u8BC1\u7801\u53D1\u9001\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7 ${waitSeconds} \u79D2\u540E\u518D\u8BD5`);
}
}
async function saveSmsCode(phone, purpose, code) {
@ -240252,12 +240258,12 @@ async function saveSmsCode(phone, purpose, code) {
}
async function verifySmsCode(phone, purpose, code) {
const record3 = await utils_default.db("o_smsCode").where({ phone, purpose, used: 0 }).orderBy("createTime", "desc").first();
if (!record3) throw new Error("\u9A8C\u8BC1\u7801\u4E0D\u5B58\u5728\u6216\u5DF2\u5931\u6548");
if ((record3.attempts ?? 0) >= MAX_ATTEMPTS) throw new Error("\u9A8C\u8BC1\u7801\u5C1D\u8BD5\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6");
if ((record3.expiresAt ?? 0) < Date.now()) throw new Error("\u9A8C\u8BC1\u7801\u5DF2\u8FC7\u671F");
if (!record3) throw createSmsCodeError("\u9A8C\u8BC1\u7801\u4E0D\u5B58\u5728\u6216\u5DF2\u5931\u6548");
if ((record3.attempts ?? 0) >= MAX_ATTEMPTS) throw createSmsCodeError("\u9A8C\u8BC1\u7801\u5C1D\u8BD5\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6");
if ((record3.expiresAt ?? 0) < Date.now()) throw createSmsCodeError("\u9A8C\u8BC1\u7801\u5DF2\u8FC7\u671F");
await utils_default.db("o_smsCode").where("id", record3.id).update({ attempts: (record3.attempts ?? 0) + 1 });
const matched = await verifyPassword(code, record3.codeHash);
if (!matched) throw new Error("\u9A8C\u8BC1\u7801\u9519\u8BEF");
if (!matched) throw createSmsCodeError("\u9A8C\u8BC1\u7801\u9519\u8BEF");
await utils_default.db("o_smsCode").where("id", record3.id).update({ used: 1 });
}
var EXPIRES_IN_MS, RESEND_INTERVAL_MS, MAX_ATTEMPTS;
@ -259834,6 +259840,7 @@ async function requestHasProjectAccess(req, userId) {
// src/app.ts
init_requestContext();
init_responseFormat();
var app = (0, import_express173.default)();
var server = import_node_http.default.createServer(app);
async function checkPermissions() {
@ -259927,11 +259934,15 @@ async function startServe(randomPort = false) {
app.use((_, res, next) => {
return res.status(404).send({ message: "API 404 Not Found" });
});
app.use((err, _, res, __) => {
res.locals.message = err.message;
app.use((err, _, res, next) => {
if (res.headersSent) return next(err);
const normalizedError = utils_default.error(err);
const status = Number(err?.status || err?.statusCode || normalizedError.status || 500);
const message = normalizedError.message || "\u8BF7\u6C42\u5931\u8D25";
res.locals.message = message;
res.locals.error = err;
console.error(err);
res.status(err.status || 500).send(err);
res.status(Number.isFinite(status) ? status : 500).send(error50(message));
});
const port = randomPort ? 0 : 10588;
return await new Promise((resolve3) => {

View File

@ -16,6 +16,7 @@ import { isEletron } from "@/utils/getPath";
import { normalizeBearerToken, verifyAuthToken } from "@/lib/auth";
import { requestHasProjectAccess } from "@/lib/workspaceAccess";
import { runWithRequestContext } from "@/lib/requestContext";
import { error as formatError } from "@/lib/responseFormat";
const app = express();
const server = http.createServer(app);
@ -139,11 +140,17 @@ export default async function startServe(randomPort: Boolean = false) {
});
// 错误处理
app.use((err: any, _: Request, res: Response, __: NextFunction) => {
res.locals.message = err.message;
app.use((err: any, _: Request, res: Response, next: NextFunction) => {
if (res.headersSent) return next(err);
const normalizedError = u.error(err);
const status = Number(err?.status || err?.statusCode || normalizedError.status || 500);
const message = normalizedError.message || "请求失败";
res.locals.message = message;
res.locals.error = err;
console.error(err);
res.status(err.status || 500).send(err);
res.status(Number.isFinite(status) ? status : 500).send(formatError(message));
});
const port = randomPort ? 0 : 10588;

View File

@ -7,6 +7,12 @@ const EXPIRES_IN_MS = 5 * 60 * 1000;
const RESEND_INTERVAL_MS = 60 * 1000;
const MAX_ATTEMPTS = 5;
function createSmsCodeError(message: string) {
const error = new Error(message) as Error & { status: number };
error.status = 400;
return error;
}
export function createNumericCode() {
return String(Math.floor(100000 + Math.random() * 900000));
}
@ -15,7 +21,7 @@ export async function assertCanSendSmsCode(phone: string, purpose: SmsPurpose) {
const latest = await u.db("o_smsCode").where({ phone, purpose, used: 0 }).orderBy("createTime", "desc").first();
if (latest?.sentAt && Date.now() - latest.sentAt < RESEND_INTERVAL_MS) {
const waitSeconds = Math.ceil((RESEND_INTERVAL_MS - (Date.now() - latest.sentAt)) / 1000);
throw new Error(`验证码发送过于频繁,请 ${waitSeconds} 秒后再试`);
throw createSmsCodeError(`验证码发送过于频繁,请 ${waitSeconds} 秒后再试`);
}
}
@ -35,13 +41,13 @@ export async function saveSmsCode(phone: string, purpose: SmsPurpose, code: stri
export async function verifySmsCode(phone: string, purpose: SmsPurpose, code: string) {
const record = await u.db("o_smsCode").where({ phone, purpose, used: 0 }).orderBy("createTime", "desc").first();
if (!record) throw new Error("验证码不存在或已失效");
if ((record.attempts ?? 0) >= MAX_ATTEMPTS) throw new Error("验证码尝试次数过多,请重新获取");
if ((record.expiresAt ?? 0) < Date.now()) throw new Error("验证码已过期");
if (!record) throw createSmsCodeError("验证码不存在或已失效");
if ((record.attempts ?? 0) >= MAX_ATTEMPTS) throw createSmsCodeError("验证码尝试次数过多,请重新获取");
if ((record.expiresAt ?? 0) < Date.now()) throw createSmsCodeError("验证码已过期");
await u.db("o_smsCode").where("id", record.id).update({ attempts: (record.attempts ?? 0) + 1 });
const matched = await verifyPassword(code, record.codeHash);
if (!matched) throw new Error("验证码错误");
if (!matched) throw createSmsCodeError("验证码错误");
await u.db("o_smsCode").where("id", record.id).update({ used: 1 });
}

View File

@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z, ZodTypeAny } from "zod";
import { error } from "@/lib/responseFormat";
import { zhCN } from "zod/locales";
@ -17,7 +18,7 @@ export function validateFields(
if (!parseResult.success) {
const errors = parseResult.error.issues.map((issue) => `字段 ${issue.path.join(".")} ${issue.message}`);
console.error(errors);
return res.status(400).json({ message: "参数错误", errors });
return res.status(400).json(error("参数错误", errors));
}
next();
};

View File

@ -7,6 +7,35 @@ import { normalizeApiBaseUrl } from "@/utils/apiBaseUrl";
const instance = axios.create();
function isApiResponse(payload: any) {
return payload && typeof payload === "object" && typeof payload.code === "number" && "message" in payload && "data" in payload;
}
function getErrorMessage(error: any) {
const payload = error?.response?.data ?? error;
if (payload?.message) return payload.message;
if (payload?.error?.message) return payload.error.message;
if (typeof payload?.error === "string") return payload.error;
if (Array.isArray(payload?.errors) && payload.errors.length) return payload.errors.join("");
if (error?.message) return error.message;
return "请求失败,请稍后重试";
}
function createRequestError(error: any) {
const payload = error?.response?.data ?? error;
const requestError = new Error(getErrorMessage(error)) as Error & {
code?: string | number;
data?: unknown;
status?: number;
response?: unknown;
};
requestError.code = payload?.code ?? error?.code;
requestError.data = payload?.data ?? payload;
requestError.status = error?.response?.status;
requestError.response = error?.response;
return requestError;
}
instance.interceptors.request.use(function (config) {
const { baseUrl, otherSetting } = storeToRefs(settingStore());
const resolvedBaseUrl = normalizeApiBaseUrl(baseUrl.value);
@ -24,6 +53,9 @@ instance.interceptors.request.use(function (config) {
instance.interceptors.response.use(
function (response) {
if (isApiResponse(response.data) && response.data.code !== 200) {
return Promise.reject(createRequestError(response.data));
}
return response.data;
},
function (error) {
@ -33,7 +65,7 @@ instance.interceptors.response.use(
router.push("/login");
MessagePlugin.error(window.$t("common.sessionExpired"));
}
return Promise.reject(error?.response?.data ?? error);
return Promise.reject(createRequestError(error));
},
);