跳到主要内容

错误码与异常处理规范

概述

本文档定义了井云服务中心后端系统的错误码规范、异常处理机制和错误响应格式,确保前后端能够统一处理各种错误情况。

错误码设计原则

分段规则

错误码采用 5 位数字格式,按照服务模块进行分段:

[服务码][错误类型][具体错误]
2位 1位 2位

服务码分配

  • 10:通用错误(系统级)
  • 11:认证服务(Auth)
  • 12:用户服务(User)
  • 13:智能体服务(Agent)
  • 14:租户服务(Tenant)
  • 15:支付服务(Payment)
  • 16:集成服务(Integration)
  • 17:网关服务(Gateway)
  • 18:定时任务服务(Cron)

错误类型

  • 1:参数错误(客户端错误)
  • 2:权限错误(授权失败)
  • 3:业务逻辑错误(业务规则违反)
  • 4:系统错误(服务端错误)
  • 5:第三方错误(外部依赖错误)

具体错误码定义

通用错误(10xxx)

错误码错误信息HTTP状态码说明
10101参数错误400请求参数格式或内容错误
10102缺少必需参数400缺少必需的请求参数
10103参数类型错误400参数类型不匹配
10104参数值超出范围400参数值不在允许范围内
10201未授权访问401缺少有效的认证信息
10202Token 已过期401JWT Token 已过期
10203Token 无效401JWT Token 格式错误或签名无效
10204权限不足403用户权限不足,无法访问资源
10301资源不存在404请求的资源不存在
10302资源已存在409资源已存在,无法重复创建
10303操作不允许403当前状态下不允许此操作
10401系统内部错误500服务器内部错误
10402数据库连接失败500数据库连接异常
10403缓存服务异常500Redis 连接异常
10404消息队列异常500RabbitMQ 连接异常
10501第三方服务不可用503外部服务暂时不可用
10502第三方服务超时504外部服务响应超时

认证服务错误(11xxx)

错误码错误信息HTTP状态码说明
11101手机号格式错误400手机号格式不正确
11102验证码错误400短信验证码错误
11103验证码已过期400短信验证码已过期
11104验证码发送频率过高429短信验证码发送过于频繁
11201微信登录失败401微信授权失败
11202微信用户信息获取失败401无法获取微信用户信息
11301用户已被禁用403用户账号已被禁用
11302用户未注册404用户不存在

用户服务错误(12xxx)

错误码错误信息HTTP状态码说明
12101用户信息不完整400用户缺少必要信息
12102分销码无效400分销邀请码无效
12201无权限访问用户信息403无权限访问其他用户信息
12301点数余额不足400用户点数余额不足
12302点数已过期400尝试使用已过期的点数
12303点数消费失败500点数扣减操作失败
12401分销等级不满足条件400当前分销等级不满足操作条件
12402佣金余额不足400佣金余额不足,无法提现

智能体服务错误(13xxx)

错误码错误信息HTTP状态码说明
13101智能体名称重复409同一租户下智能体名称已存在
13102智能体类型不支持400不支持的智能体类型
13103智能体配置错误400智能体配置格式错误
13201无权限访问智能体403无权限访问该智能体
13301智能体不存在404指定的智能体不存在
13302智能体分类不存在404指定的分类不存在
13401精选分类已存在409租户下已存在精选分类
13501平台同步失败500与 AI 平台同步失败
13502平台 API 调用失败502AI 平台 API 调用失败

租户服务错误(14xxx)

错误码错误信息HTTP状态码说明
14101租户名称重复409租户名称已存在
14102二级域名重复409二级域名已被占用
14103租户域名格式错误400二级域名格式不正确
14201无权限访问租户403无权限访问该租户
14301租户不存在404指定的租户不存在
14302版本不存在404指定的版本不存在
14303租户已过期403租户已过期,无法使用
14401菜单层级过深400菜单层级超过最大限制
14402菜单名称重复409同一级别下菜单名称重复

支付服务错误(15xxx)

错误码错误信息HTTP状态码说明
15101订单不存在404指定的订单不存在
15102订单状态错误400订单状态不允许当前操作
15103订单已过期410订单已过期,无法支付
15104订单金额错误400订单金额与实际不符
15201支付失败500支付处理失败
15202支付超时504支付处理超时
15301版本购买失败500版本购买流程失败
15302租户创建失败500支付成功后租户创建失败

集成服务错误(16xxx)

错误码错误信息HTTP状态码说明
16101文件上传失败500文件上传到存储服务失败
16102文件格式不支持400不支持的文件格式
16103文件大小超限400文件大小超过限制
16201短信发送失败500短信发送失败
16202短信余额不足400短信服务余额不足
16301微信第三方平台配置错误500微信第三方平台配置错误
16302微信授权失败401微信第三方平台授权失败

网关服务错误(17xxx)

错误码错误信息HTTP状态码说明
17101服务不可用503后端服务不可用
17102服务响应超时504后端服务响应超时
17201请求频率过高429请求频率超过限制
17202IP 被限制403IP 地址被限制访问

定时任务服务错误(18xxx)

错误码错误信息HTTP状态码说明
18101任务执行失败500定时任务执行失败
18102任务调度异常500任务调度器异常
18201数据清理失败500过期数据清理失败

错误响应格式

HTTP API 错误响应

{
"code": 10101,
"message": "参数错误",
"details": "手机号格式不正确",
"request_id": "req_123456789",
"timestamp": "2025-12-27T10:30:00Z",
"data": {
"field": "phone",
"value": "123456",
"reason": "手机号必须是11位数字"
}
}

字段说明

  • code:错误码,5位数字
  • message:错误信息,用户友好的中文描述
  • details:详细错误信息,可选
  • request_id:请求唯一标识,用于问题追踪
  • timestamp:错误发生时间,ISO 8601 格式
  • data:错误相关数据,可选

gRPC 错误响应

type ErrorResponse struct {
Code int32 `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
RequestId string `json:"request_id"`
Timestamp string `json:"timestamp"`
Data map[string]string `json:"data,omitempty"`
}

异常处理机制

分层异常处理

1. 网关层异常处理

  • 统一捕获所有异常
  • 记录请求日志和错误日志
  • 转换为标准错误响应格式
  • 处理跨域和安全头

2. 服务层异常处理

  • 业务逻辑异常捕获和转换
  • 参数验证异常处理
  • 权限检查异常处理
  • 数据库操作异常处理

3. 数据层异常处理

  • 数据库连接异常处理
  • SQL 执行异常处理
  • 事务异常处理
  • 缓存操作异常处理

异常分类和处理策略

可重试异常

  • 数据库连接超时
  • 缓存服务不可用
  • 消息队列连接异常

处理策略:

  • 指数退避重试
  • 最大重试次数限制
  • 熔断保护

不可重试异常

  • 参数验证失败
  • 权限不足
  • 业务逻辑错误
  • 资源不存在

处理策略:

  • 立即返回错误
  • 记录错误日志
  • 不进行重试

第三方服务异常

  • 微信服务异常
  • 短信服务异常
  • 支付服务异常

处理策略:

  • 降级处理
  • 限流保护
  • 人工干预

错误日志规范

日志格式

{
"level": "error",
"timestamp": "2025-12-27T10:30:00Z",
"request_id": "req_123456789",
"service": "user-service",
"method": "CreateUser",
"error_code": 12101,
"error_message": "用户信息不完整",
"error_details": "缺少手机号字段",
"stack_trace": "...",
"user_id": 12345,
"tenant_id": 1,
"duration_ms": 150
}

日志级别

  • error:系统错误和异常
  • warn:业务逻辑警告
  • info:重要业务操作
  • debug:调试信息

必须记录的信息

  • 请求唯一标识
  • 错误码和错误信息
  • 用户和租户信息
  • 操作耗时
  • 相关上下文数据

前端错误处理指南

HTTP 状态码处理

  • 400:参数错误,提示用户检查输入
  • 401:未授权,跳转到登录页面
  • 403:权限不足,提示用户联系管理员
  • 404:资源不存在,提示用户检查操作
  • 429:请求过频,提示用户稍后重试
  • 500:服务器错误,提示用户稍后重试
  • 503:服务不可用,提示用户稍后重试

错误码处理

  • 10xxx:通用错误,显示通用错误提示
  • 11xxx:认证错误,跳转到登录页面
  • 12xxx:用户服务错误,显示具体错误信息
  • 13xxx:智能体服务错误,显示具体错误信息
  • 14xxx:租户服务错误,显示具体错误信息
  • 15xxx:支付错误,显示支付相关错误信息

用户体验优化

  • 错误信息要用户友好,避免技术术语
  • 提供解决方案或操作建议
  • 重要操作失败时提供重试按钮
  • 网络错误时提供离线提示

监控和告警

错误监控指标

  • 错误率(按错误码统计)
  • 错误趋势(按时间统计)
  • 服务可用性
  • 响应时间分布

告警规则

  • 错误率超过 5% 时告警
  • 服务可用性低于 99% 时告警
  • 关键错误码出现时立即告警
  • 第三方服务错误率超过阈值时告警

告警处理流程

  1. 接收告警通知
  2. 确认告警级别
  3. 查看错误日志和监控数据
  4. 定位问题根因
  5. 执行修复方案
  6. 验证修复效果
  7. 记录处理过程

测试验证

错误码测试

  • 每个错误码都有对应的测试用例
  • 验证错误响应格式正确性
  • 验证错误日志记录完整性
  • 验证前端错误处理逻辑

异常场景测试

  • 网络异常场景
  • 数据库异常场景
  • 第三方服务异常场景
  • 高并发异常场景

回归测试

  • 新增错误码时的回归测试
  • 修改错误处理逻辑时的回归测试
  • 升级第三方依赖时的回归测试

最佳实践

错误码设计

  • 错误码要有明确的业务含义
  • 错误码要预留扩展空间
  • 错误码要定期清理和整理
  • 错误码变更要考虑兼容性

异常处理

  • 异常处理要统一和规范
  • 避免捕获过于宽泛的异常
  • 重要异常要有完整的上下文
  • 异常恢复要有明确的策略

日志记录

  • 日志要结构化和可查询
  • 敏感信息要脱敏处理
  • 日志级别要合理设置
  • 日志要定期清理和归档

用户体验

  • 错误提示要友好和明确
  • 提供解决问题的建议
  • 避免暴露技术细节
  • 支持多语言错误提示

错误码系统实现指南

系统概述

井云后端采用统一的错误码系统,提供类型安全、易于管理的错误处理方案。错误码系统位于 backend/pkg/errors/ 包中。

核心特性

  • 5 位数字错误码:符合规范,易于分类和管理
  • 类型安全:使用 Code 类型,编译时检查
  • 服务分层:按服务模块组织,易于维护
  • 自动 HTTP 映射:自动映射错误码到 HTTP 状态码
  • 便捷 API:提供丰富的便捷函数

包结构

backend/pkg/errors/
├── codes.go # 核心错误码定义(通用错误 10xxx)
├── errors.go # 错误类型和工具函数
├── service/ # 各服务特定错误码
│ ├── auth.go # 认证服务错误码(11xxx)
│ ├── user.go # 用户服务错误码(12xxx)
│ ├── agent.go # 智能体服务错误码(13xxx)
│ ├── tenant.go # 租户服务错误码(14xxx)
│ ├── payment.go # 支付服务错误码(15xxx)
│ ├── integration.go # 集成服务错误码(16xxx)
│ ├── gateway.go # 网关服务错误码(17xxx)
│ └── cron.go # 定时任务服务错误码(18xxx)
└── CODES_REFERENCE.md # 错误码完整参考

使用示例

1. 导入包

import (
"jingyun_center/pkg/errors"
errorservice "jingyun_center/pkg/errors/service"
)

2. 创建错误

// 使用服务特定错误码
err := errorservice.TenantNotFound(123)
err := errorservice.InsufficientPoints(100, 50)
err := errorservice.OrderNotFound(123456)

// 使用通用错误码
err := errors.ParameterError("参数错误")
err := errors.NotFound("租户")
err := errors.Unauthorized("未授权")

// 带详情的错误
err := errors.New(errors.ErrParameterError, "参数错误").
WithDetails("field", "phone").
WithDetails("value", "13800138000")

3. 错误判断

// 判断特定错误码
if errors.Is(err, errorservice.ErrTenantNotFound) {
// 处理租户不存在
}

// 判断错误类型
if errors.IsClientError(err) {
// 客户端错误,提示用户
}

if errors.IsServerError(err) {
// 服务端错误,记录日志
}

// 判断是否可重试
if errors.IsRetryable(err) {
// 执行重试逻辑
}

4. 获取错误信息

// 获取错误码
code := errors.GetCode(err)
fmt.Println(code.String()) // "14301"

// 获取错误消息
message := errors.GetMessage(err)

// 获取错误详情
details := errors.GetDetails(err)

// 获取 HTTP 状态码
statusCode := err.(*errors.Error).HTTPStatusCode()

5. HTTP 响应

// 转换为 HTTP 响应格式
response := err.(*errors.Error).HTTPResponse()
// 返回:
// {
// "code": "14301",
// "message": "指定的租户不存在",
// "details": {"tenant_id": 123},
// "request_id": "req_123456789"
// }

Code 类型方法

// 错误码解析
code := errorservice.ErrTenantNotFound

// 获取服务码(前2位)
fmt.Println(code.Service()) // 14

// 获取错误类型(第3位)
fmt.Println(code.Type()) // 3

// 获取具体错误(后2位)
fmt.Println(code.Detail()) // 1

// 获取 HTTP 状态码
fmt.Println(code.HTTPStatus()) // 404

// 判断是否可重试
fmt.Println(code.IsRetryable()) // false

// 判断错误类型
fmt.Println(code.IsClientError()) // true
fmt.Println(code.IsServerError()) // false

业务层最佳实践

func (uc *TenantUsecase) GetByID(ctx context.Context, id int64) (*Tenant, error) {
// 参数验证
if id <= 0 {
return nil, errors.ParameterError("无效的ID").
WithDetails("field", "id").
WithDetails("value", id)
}

// 查询数据库
tenant, err := uc.repo.GetByID(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, errorservice.TenantNotFound(id)
}
return nil, errors.InternalError("查询租户失败").WithCause(err)
}

return tenant, nil
}

网关层最佳实践

func customResponseEncoder(w http.ResponseWriter, r *http.Request, data interface{}) error {
// 检查是否为错误
if err, ok := data.(error); ok {
stdErr := errors.FromHTTPError(err)

// 添加请求 ID
requestID := errors.GetRequestID(r.Context())
if requestID != "" {
stdErr.WithRequestID(requestID)
}

// 转换为 HTTP 响应
response := stdErr.HTTPResponse()
response["timestamp"] = time.Now().Format(time.RFC3339)

// 编码响应
codec := encoding.GetCodec("json")
body, _ := codec.Marshal(response)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(stdErr.HTTPStatusCode())
w.Write(body)
return nil
}

// 正常响应处理
// ...
}

测试最佳实践

func TestGetTenant(t *testing.T) {
_, err := uc.GetByID(ctx, -1)
require.Error(t, err)

// 使用错误码匹配,而非字符串匹配
require.True(t, errors.Is(err, errors.ErrParameterError))
require.True(t, errors.IsClientError(err))
}

日志记录最佳实践

func (uc *TenantUsecase) GetByID(ctx context.Context, id int64) (*Tenant, error) {
tenant, err := uc.repo.GetByID(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
stdErr := errorservice.TenantNotFound(id)
uc.log.WithContext(ctx).Errorw(
"租户不存在",
"error_code", stdErr.Code.String(),
"error_message", stdErr.Message,
"tenant_id", id,
"request_id", errors.GetRequestID(ctx),
)
return nil, stdErr
}
// ...
}
return tenant, nil
}

错误码常量命名规范

错误码常量使用 PascalCase 命名,格式为 Err<Service><Type><Detail>

// 格式:Err + 服务名 + 错误类型 + 具体错误
ErrTenantNotFound // 租户 + 不存在
ErrInsufficientPoints // 点数余额不足
ErrOrderExpired // 订单已过期
ErrPaymentFailed // 支付失败

添加新错误码

当需要添加新错误码时,按照以下步骤:

  1. 在对应的 service/*.go 文件中添加错误码常量
  2. 添加便捷的错误创建函数
  3. 更新本文档的错误码定义表
  4. 添加对应的测试用例
// service/tenant.go

// 1. 添加错误码常量
const (
ErrTenantExpired errors.Code = 14303 // 租户已过期
)

// 2. 添加便捷函数
func TenantExpired(tenantID int64) *errors.Error {
return errors.New(ErrTenantExpired, "租户已过期,无法使用").
WithDetails("tenant_id", tenantID)
}

错误码参考

完整的错误码参考请查看:

常见问题

Q: 如何判断错误是否可重试?

if errors.IsRetryable(err) {
// 执行重试逻辑
}

Q: 如何获取 HTTP 状态码?

statusCode := err.(*errors.Error).HTTPStatusCode()
// 或
statusCode := errors.GetCode(err).HTTPStatus()

Q: 如何添加请求 ID?

// 在上下文中添加请求 ID
ctx = errors.WithRequestID(ctx, generateRequestID())

// 在错误中添加请求 ID
err = err.WithRequestID(errors.GetRequestID(ctx))

Q: 如何包装底层错误?

return errors.Wrap(cause, errors.ErrDatabaseConnectionFailed, "数据库连接失败")

相关文档