更新 wiki

This commit is contained in:
eafonyang
2026-07-17 17:17:22 +08:00
parent 1da6d72544
commit d292c23dc0
159 changed files with 2918 additions and 2735 deletions
@@ -0,0 +1,59 @@
---
kind: error_handling
name: 前后端错误处理体系:统一响应体 + HTTP 状态码 + 前端拦截器
category: error_handling
scope:
- '**'
source_files:
- backend/src/utils/response.js
- backend/src/middleware/auth.js
- backend/src/app.js
- backend/src/services/curveClient.js
- backend/src/services/measurementStorage.js
- backend/src/services/squiglink.js
- frontend/src/utils/request.js
- frontend_v2/src/service/request/index.ts
- frontend_v2/src/service/request/shared.ts
---
## 1. 整体方案概述
- 后端采用「业务码 + 消息」的 JSON 响应体(ApiResponse),配合 Express 中间件对认证/权限类错误直接返回 HTTP 401/403,未捕获异常由路由层 try/catch 包裹并以 ApiResponse.error 返回。
- 前端两套工程均通过 axios 拦截器集中处理:解析后端 code、处理 401/403、按配置决定是否弹窗或静默失败,并提供 skipErrorToast 让调用方自行控制提示。
## 2. 关键文件与职责
- 后端统一响应体:backend/src/utils/response.js — 定义 ApiResponse.success / error / noData 与 PageData。
- 认证中间件:backend/src/middleware/auth.js — 校验 Bearer Token,区分 TokenExpiredError 返回 401,超级管理员校验返回 403。
- 应用入口:backend/src/app.js — 启动时 sequelize.sync() 异常被 catch 并降级继续运行;所有业务路由挂载后无全局错误中间件,依赖各路由 try/catch。
- 服务层抛错:backend/src/services/*.jscurveClient、eqCacheStorage、measurementStorage、otaStorage、squiglink)在参数校验失败、外部存储/网络异常时 throw new Error(...),由上层路由捕获并转为 ApiResponse.error。
- 前端 v1 请求封装:frontend/src/utils/request.js — 基于 axios,拦截器根据 res.code === 0 判定业务失败,401 清理本地态并跳转登录页,403 提示无权限。
- 前端 v2 请求封装:frontend_v2/src/service/request/index.ts + shared.ts — 使用 @sa/axios 的 createFlatRequest,通过环境变量 VITE_SERVICE_SUCCESS_CODE、VITE_SERVICE_LOGOUT_CODES、VITE_SERVICE_MODAL_LOGOUT_CODES、VITE_SERVICE_EXPIRED_TOKEN_CODES 驱动成功码、登出码、弹窗登出码、过期 token 码等策略;提供 handleExpiredRequest 防抖刷新与 showErrorMsg 去重提示。
## 3. 架构与约定
- 后端响应格式
- 成功:{ code: 1, msg: 'success', data }
- 业务失败:{ code: 0, msg, data: null }(部分场景用非 0 负数表示不同错误域)
- 空数据:{ code: 2, msg, data: null }(仍视为“成功”路径)
- HTTP 状态码约定
- 401:未登录/凭证无效/Token 过期,响应体带 { detail } 字段,前端据此跳转登录或提示。
- 403:权限不足,响应体同样带 { detail }。
- 错误传播路径
- 服务层 throw new Error(...) → 路由层 try/catch → res.json(ApiResponse.error(msg)) → 前端拦截器解析 code 并展示。
- 认证相关错误不走 ApiResponse,直接 res.status(401/403).json({ detail }),由前端拦截器优先处理。
- 前端错误分类与处理
- 业务失败(code !== 1 且 !== 2):默认弹出 ElMessage/NMessage 错误提示,可通过 config.skipErrorToast = true 关闭。
- 401:清除本地 token,跳转到 /login?redirect=...v1)或重置 store 并提示(v2)。
- 403:仅提示无权限,不跳转。
- 网络异常:显示“网络错误”或 axios 原始 message。
- 登出/弹窗登出/过期 token:通过环境变量列表匹配,分别执行静默登出、二次确认弹窗登出、或尝试刷新 token(当前后端无 refresh-token API,实际一律登出)。
## 4. 开发者应遵循的规则
- 后端
- 业务错误一律通过 ApiResponse.error(msg, code?) 返回,不要直接 res.send 裸对象。
- 需要鉴权的路由挂载 authMiddleware,敏感操作再叠加 requireSuperAdmin。
- 服务层只负责抛出语义清晰的 Error,不要在 service 里直接写 res.json。
- 路由层必须 try/catch 外层包裹,将异常转为 ApiResponse.error,避免 500 堆栈泄露。
- 前端
- 使用统一的 request 实例发起调用,不要绕过拦截器。
- 需要自定义错误提示的场景,给请求配置加上 skipErrorToast: true,并在业务逻辑中自行提示。
- 依赖环境变量 VITE_SERVICE_*_CODES 管理登出与弹窗行为,新增错误码时同步更新配置与文案。
- 401/403 已由拦截器兜底,业务代码无需重复处理,除非需要额外埋点或统计。