refactor: 统一错误消息数据源,优化错误码与映射表管理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m36s
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:
@@ -85,7 +85,6 @@ type LogRotationConfig struct {
|
||||
|
||||
// MiddlewareConfig 中间件配置
|
||||
type MiddlewareConfig struct {
|
||||
EnableAuth bool `mapstructure:"enable_auth"` // 启用 keyauth 中间件
|
||||
EnableRateLimiter bool `mapstructure:"enable_rate_limiter"` // 启用限流器(默认:false)
|
||||
RateLimiter RateLimiterConfig `mapstructure:"rate_limiter"` // 限流器配置
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func BenchmarkGet(b *testing.B) {
|
||||
_ = cfg.Server.Address
|
||||
_ = cfg.Redis.Address
|
||||
_ = cfg.Logging.Level
|
||||
_ = cfg.Middleware.EnableAuth
|
||||
_ = cfg.Middleware.EnableRateLimiter
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,9 +104,6 @@ middleware:
|
||||
if cfg.Logging.Level != "info" {
|
||||
t.Errorf("expected logging.level info, got %s", cfg.Logging.Level)
|
||||
}
|
||||
if cfg.Middleware.EnableAuth != true {
|
||||
t.Errorf("expected enable_auth true, got %v", cfg.Middleware.EnableAuth)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -165,7 +162,6 @@ logging:
|
||||
compress: false
|
||||
|
||||
middleware:
|
||||
enable_auth: false
|
||||
enable_rate_limiter: false
|
||||
rate_limiter:
|
||||
max: 50
|
||||
@@ -194,9 +190,6 @@ middleware:
|
||||
if cfg.Logging.Level != "debug" {
|
||||
t.Errorf("expected logging.level debug, got %s", cfg.Logging.Level)
|
||||
}
|
||||
if cfg.Middleware.EnableAuth != false {
|
||||
t.Errorf("expected enable_auth false, got %v", cfg.Middleware.EnableAuth)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -576,9 +569,6 @@ middleware:
|
||||
if newCfg.Redis.PoolSize != 20 {
|
||||
t.Errorf("expected updated redis.pool_size 20, got %d", newCfg.Redis.PoolSize)
|
||||
}
|
||||
if newCfg.Middleware.EnableAuth != false {
|
||||
t.Errorf("expected updated enable_auth false, got %v", newCfg.Middleware.EnableAuth)
|
||||
}
|
||||
if newCfg.Middleware.EnableRateLimiter != true {
|
||||
t.Errorf("expected updated enable_rate_limiter true, got %v", newCfg.Middleware.EnableRateLimiter)
|
||||
}
|
||||
|
||||
@@ -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: "成功",
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -163,7 +163,7 @@ func Auth(config AuthConfig) fiber.Handler {
|
||||
return appErr
|
||||
}
|
||||
// 否则包装为 AppError
|
||||
return errors.Wrap(errors.CodeInvalidToken, "认证令牌无效", err)
|
||||
return errors.Wrap(errors.CodeInvalidToken, err, "认证令牌无效")
|
||||
}
|
||||
|
||||
// 将用户信息设置到 context
|
||||
|
||||
@@ -70,7 +70,7 @@ func RequirePermission(permCode string, config PermissionConfig) fiber.Handler {
|
||||
return appErr
|
||||
}
|
||||
// 否则包装为 AppError
|
||||
return errors.Wrap(errors.CodeInternalError, "权限检查失败", err)
|
||||
return errors.Wrap(errors.CodeInternalError, err, "权限检查失败")
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
@@ -119,7 +119,7 @@ func RequireAnyPermission(permCodes []string, config PermissionConfig) fiber.Han
|
||||
return appErr
|
||||
}
|
||||
// 否则包装为 AppError
|
||||
return errors.Wrap(errors.CodeInternalError, "权限检查失败", err)
|
||||
return errors.Wrap(errors.CodeInternalError, err, "权限检查失败")
|
||||
}
|
||||
|
||||
// 如果拥有任意一个权限,则放行
|
||||
@@ -170,7 +170,7 @@ func RequireAllPermissions(permCodes []string, config PermissionConfig) fiber.Ha
|
||||
return appErr
|
||||
}
|
||||
// 否则包装为 AppError
|
||||
return errors.Wrap(errors.CodeInternalError, "权限检查失败", err)
|
||||
return errors.Wrap(errors.CodeInternalError, err, "权限检查失败")
|
||||
}
|
||||
|
||||
// 如果缺少任意一个权限,则拒绝访问
|
||||
|
||||
Reference in New Issue
Block a user