星河避难所

返回

设计说明: 错误处理机制

本文介绍 OneAdmin 项目中的错误处理机制,通过统一的错误码与错误信息设计,实现对用户响应与系统日志的分离处理。通过在 Handler 层集中处理错误,并结合日志系统进行分级记录,既保证了系统安全性,又提升了问题排查与维护效率。

在 OneAdmin 的分层架构中,错误处理作为基础能力的一部分存在,负责统一管理系统中的错误返回与日志记录。整个项目采用 “错误码 + 错误信息” 的方式进行错误处理,并在 Handler 层进行统一收敛。


核心设计#

错误处理的核心思路如下:

  1. 错误向上返回

    • Repository 返回 error
    • Service 透传或封装 error
    • Handler 统一处理
  2. 对外隐藏原始错误

    • 不直接将内部 error 返回给用户
    • 避免暴露系统实现细节
  3. 基于错误码返回信息

    • 返回给用户的内容由错误码映射生成
    • 通过多语言模块进行翻译

为什么不直接返回 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 XX
go

示例:

1 00 01
go

含义如下:

  • 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)

(可根据实际业务扩展)

设计约定#

在实际开发中,建议遵循以下规则:

  1. 同一错误类型 + 模块下,错误码必须唯一
  2. 具体错误编号(XX)从 01 递增,不复用
  3. 错误语义清晰,避免一个错误码表示多种含义
  4. 优先使用业务错误(4xx),避免滥用参数错误(1xx)
  5. 资源错误(5xx)只表示“是否存在”,不包含业务逻辑
  6. 系统错误(6xx)不暴露细节,统一返回通用提示
评论似乎卡住了,尝试刷新?✨