refactor: 统一错误消息数据源,优化错误码与映射表管理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m36s

主要改动:
- 改造 errors.New() 和 Wrap() 函数签名为可变参数,优先使用 errorMessages 映射表
- 添加 allErrorCodes 注册表和 init() 启动时校验,确保错误码与映射表一致
- 添加 TestAllCodesHaveMessages 和 TestNoOrphanMessages 测试防止映射表腐化
- 清理 109 处与映射表一致的冗余硬编码(service 层)
- 保留业务特定消息覆盖能力

新增 API 用法:
- errors.New(errors.CodeUnauthorized) // 使用映射表默认消息
- errors.New(errors.CodeNotFound, "提现申请不存在") // 覆盖为自定义消息
This commit is contained in:
2026-01-22 18:27:42 +08:00
parent b68e7ec013
commit 6821e5abcf
28 changed files with 665 additions and 81 deletions

View File

@@ -1,5 +1,7 @@
package errors
import "fmt"
// 错误码定义
const (
// 成功
@@ -66,6 +68,68 @@ const (
CodeTaskQueueError = 2006 // 任务队列错误
)
// allErrorCodes 所有已注册的错误码
// 新增错误码时必须同时在此列表中注册
var allErrorCodes = []int{
CodeSuccess,
CodeInvalidParam,
CodeMissingToken,
CodeInvalidToken,
CodeUnauthorized,
CodeForbidden,
CodeNotFound,
CodeConflict,
CodeTooManyRequests,
CodeRequestTooLarge,
CodeAccountNotFound,
CodeAccountDisabled,
CodeAccountDeleted,
CodeUsernameExists,
CodePhoneExists,
CodeInvalidPassword,
CodePasswordTooWeak,
CodeParentIDRequired,
CodeInvalidParentID,
CodeCannotModifyParent,
CodeCannotModifyUserType,
CodeRoleNotFound,
CodeRoleNameExists,
CodePermissionNotFound,
CodePermCodeExists,
CodeInvalidPermCode,
CodeRoleAlreadyAssigned,
CodePermAlreadyAssigned,
CodeShopNotFound,
CodeShopCodeExists,
CodeShopLevelExceeded,
CodeEnterpriseNotFound,
CodeEnterpriseCodeExists,
CodeCustomerNotFound,
CodeCustomerPhoneExists,
CodeInvalidCredentials,
CodeAccountLocked,
CodePasswordExpired,
CodeInvalidOldPassword,
CodeInvalidStatus,
CodeInsufficientBalance,
CodeWithdrawalNotFound,
CodeWalletNotFound,
CodeInternalError,
CodeDatabaseError,
CodeRedisError,
CodeServiceUnavailable,
CodeTimeout,
CodeTaskQueueError,
}
func init() {
for _, code := range allErrorCodes {
if _, ok := errorMessages[code]; !ok {
panic(fmt.Sprintf("错误码 %d 缺少映射消息,请在 errorMessages 中添加", code))
}
}
}
// errorMessages 错误消息映射表(中文)
var errorMessages = map[int]string{
CodeSuccess: "成功",

View File

@@ -135,6 +135,35 @@ func TestGetLogLevel(t *testing.T) {
}
}
func TestAllCodesHaveMessages(t *testing.T) {
var missing []int
for _, code := range allErrorCodes {
if _, ok := errorMessages[code]; !ok {
missing = append(missing, code)
}
}
if len(missing) > 0 {
t.Errorf("以下错误码缺少映射消息: %v", missing)
}
}
func TestNoOrphanMessages(t *testing.T) {
codeSet := make(map[int]bool)
for _, code := range allErrorCodes {
codeSet[code] = true
}
var orphan []int
for code := range errorMessages {
if !codeSet[code] {
orphan = append(orphan, code)
}
}
if len(orphan) > 0 {
t.Errorf("以下错误码在 errorMessages 中存在但未在 allErrorCodes 中注册: %v", orphan)
}
}
// BenchmarkGetHTTPStatus 基准测试 HTTP 状态码映射性能
func BenchmarkGetHTTPStatus(b *testing.B) {
codes := []int{

View File

@@ -32,10 +32,16 @@ func (e *AppError) Unwrap() error {
}
// New 创建新的 AppError
func New(code int, message string) *AppError {
// 如果消息为空,使用默认消息
if message == "" {
message = GetMessage(code, "zh-CN")
// 优先使用 errorMessages 映射表中的消息,允许通过可选参数覆盖
// 用法:
// - errors.New(errors.CodeNotFound) // 使用映射表默认消息
// - errors.New(errors.CodeNotFound, "提现申请不存在") // 覆盖为自定义消息
func New(code int, customMsg ...string) *AppError {
// 默认从映射表获取消息
message := GetMessage(code, "zh-CN")
// 如果提供了自定义消息且非空,则覆盖
if len(customMsg) > 0 && customMsg[0] != "" {
message = customMsg[0]
}
return &AppError{
Code: code,
@@ -44,10 +50,14 @@ func New(code int, message string) *AppError {
}
// Wrap 用错误码和消息包装现有错误
func Wrap(code int, message string, err error) *AppError {
// 如果消息为空,使用默认消息
if message == "" {
message = GetMessage(code, "zh-CN")
// 优先使用 errorMessages 映射表中的消息,允许通过可选参数覆盖
// 用法:
// - errors.Wrap(errors.CodeDatabaseError, originalErr) // 使用映射表默认消息
// - errors.Wrap(errors.CodeDatabaseError, originalErr, "查询用户失败") // 覆盖为自定义消息
func Wrap(code int, err error, customMsg ...string) *AppError {
message := GetMessage(code, "zh-CN")
if len(customMsg) > 0 && customMsg[0] != "" {
message = customMsg[0]
}
return &AppError{
Code: code,

View File

@@ -124,7 +124,7 @@ func TestAppErrorMethods(t *testing.T) {
// TestAppErrorUnwrap 测试错误链支持
func TestAppErrorUnwrap(t *testing.T) {
originalErr := errors.New("database connection failed")
appErr := Wrap(CodeDatabaseError, "", originalErr)
appErr := Wrap(CodeDatabaseError, originalErr)
// 测试 Unwrap
unwrapped := appErr.Unwrap()
@@ -239,7 +239,12 @@ func TestWrapError(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Wrap(tt.code, tt.message, tt.originalErr)
var err *AppError
if tt.message == "" {
err = Wrap(tt.code, tt.originalErr)
} else {
err = Wrap(tt.code, tt.originalErr, tt.message)
}
if err.Error() != tt.expectedMessage {
t.Errorf("Wrap().Error() = %q, expected %q", err.Error(), tt.expectedMessage)