From 29867f91c119563521e50f90396e683b37216866 Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Mon, 1 Jun 2026 17:40:33 +0800 Subject: [PATCH] Fix registration error responses --- data/serve/app.js | 29 ++++++++++++++++++++--------- src/app.ts | 13 ++++++++++--- src/lib/smsCode.ts | 16 +++++++++++----- src/middleware/middleware.ts | 3 ++- web-core/src/utils/axios.ts | 34 +++++++++++++++++++++++++++++++++- 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/data/serve/app.js b/data/serve/app.js index a3825f3..e9388cc 100644 --- a/data/serve/app.js +++ b/data/serve/app.js @@ -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) => { diff --git a/src/app.ts b/src/app.ts index 46ea995..3815d50 100644 --- a/src/app.ts +++ b/src/app.ts @@ -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; diff --git a/src/lib/smsCode.ts b/src/lib/smsCode.ts index 02d079f..93e59b1 100644 --- a/src/lib/smsCode.ts +++ b/src/lib/smsCode.ts @@ -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 }); } diff --git a/src/middleware/middleware.ts b/src/middleware/middleware.ts index 4ccb19b..64dafcc 100644 --- a/src/middleware/middleware.ts +++ b/src/middleware/middleware.ts @@ -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(); }; diff --git a/web-core/src/utils/axios.ts b/web-core/src/utils/axios.ts index fc1599c..08deae2 100644 --- a/web-core/src/utils/axios.ts +++ b/web-core/src/utils/axios.ts @@ -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)); }, );