设计说明: 错误处理机制
本文介绍 OneAdmin 项目中的错误处理机制,通过统一的错误码与错误信息设计,实现对用户响应与系统日志的分离处理。通过在 Handler 层集中处理错误,并结合日志系统进行分级记录,既保证了系统安全性,又提升了问题排查与维护效率。
在 OneAdmin 的分层架构中,错误处理作为基础能力的一部分存在,负责统一管理系统中的错误返回与日志记录。整个项目采用 “错误码 + 错误信息” 的方式进行错误处理,并在 Handler 层进行统一收敛。
核心设计#
错误处理的核心思路如下:
-
错误向上返回
- Repository 返回 error
- Service 透传或封装 error
- Handler 统一处理
-
对外隐藏原始错误
- 不直接将内部 error 返回给用户
- 避免暴露系统实现细节
-
基于错误码返回信息
- 返回给用户的内容由错误码映射生成
- 通过多语言模块进行翻译
为什么不直接返回 error#
在实际项目中,直接返回 error 信息存在风险,例如:
- 暴露数据库结构或 SQL 语句
- 泄露内部逻辑细节
- 被利用进行攻击或漏洞探测
因此系统约定:
所有对外错误信息必须通过错误码转换生成,不直接返回原始 error
错误处理流程#
整体错误流转过程如下:
Repository 抛错 → Service 透传 → Handler 统一处理 → 返回标准响应
Handler 层负责:
- 根据错误码返回用户可见信息
- 记录原始错误日志
日志记录#
系统使用:
zap作为日志框架lumberjack进行日志切割
并在 internal/handler/common.go 中封装了统一方法:
ErrorLog(log *zap.Logger, msg string, code int, err error, fields ...zap.Field)go说明:
log:当前模块的日志实例msg:日志描述信息code:错误码err:原始错误fields:附加日志字段
日志实例在 internal/logger/logger.go 中创建。
日志使用约定#
建议每个 Handler 模块维护独立的 *zap.Logger:
- 区分不同模块日志输出路径
- 方便问题定位与分析
用户返回信息#
返回给用户的错误信息来源于多语言文件中的 error 节点,通过错误码映射生成:
- 使用 i18n 模块进行翻译
- 不包含系统内部细节
具体实现可参考「多语言处理」章节。
错误码设计规范#
项目采用统一的错误码结构:
T MM XXgo示例:
1 00 01go含义如下:
- T(错误类型) :标识错误大类
- MM(模块编号) :标识所属业务模块
- XX(具体错误) :具体错误编号
错误类型说明(T)#
| 类型 | 含义 | 额外说明 |
|---|---|---|
| 1 | 参数/请求错误(Request) | 只处理“请求本身有问题” 如:参数缺失、类型错误、格式错误、JSON解析失败等 |
| 2 | 认证错误(AuthN) | “你是谁”没搞清楚 如:未登录、Token无效、Token过期等 |
| 3 | 权限错误(AuthZ) | “你是谁”没问题,但“你不能这么干” 如:非管理员操作、越权访问等 |
| 4 | 业务错误(Business) | 请求是合法的,但业务不成立(最常用的一类) 如:密码错误、余额不足、状态不允许、验证码错误等 |
| 5 | 资源错误(Resource) | 只表示“资源不存在” 如:用户不存在、订单不存在、商品不存在等 |
| 6 | 系统错误(System) | 服务器内部异常 如:数据库错误、缓存错误、第三方服务异常、未捕获panic等 |
模块编号说明(MM)#
| 编号 | 模块 |
|---|---|
| 00 | 通用模块(Common) |
| 01 | 管理员模块(Admin) |
| 02 | 角色模块(Role) |
| 03 | 菜单模块(Menu) |
(可根据实际业务扩展)
设计约定#
在实际开发中,建议遵循以下规则:
- 同一错误类型 + 模块下,错误码必须唯一
- 具体错误编号(XX)从 01 递增,不复用
- 错误语义清晰,避免一个错误码表示多种含义
- 优先使用业务错误(4xx),避免滥用参数错误(1xx)
- 资源错误(5xx)只表示“是否存在”,不包含业务逻辑
- 系统错误(6xx)不暴露细节,统一返回通用提示
评论似乎卡住了,尝试刷新?✨