fix: 系统性修复安全问题、性能问题和错误处理
安全问题修复: - X-Forwarded-For越界检查(auth.go) - checkTokenStatus Context参数传递(auth.go) - Type Assertion安全检查(auth.go) 性能问题修复: - TokenCache过期清理机制 - BruteForceProtection过期清理 - InMemoryIdempotencyStore过期清理 错误处理修复: - AuditStore.Emit返回error - domain层emitAudit辅助方法 - List方法返回空slice而非nil - 金额/价格负数验证 架构一致性: - 统一使用model.RoleHierarchyLevels 新增功能: - Alert API完整实现(CRUD+Resolve) - pkg/error错误码集中管理
This commit is contained in:
132
supply-api/pkg/error/errors.go
Normal file
132
supply-api/pkg/error/errors.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrorCode 错误码格式:{DOMAIN}_{CODE}
|
||||
// 错误码命名规范:{模块}_{问题类型}_{序号}
|
||||
//
|
||||
// 示例:
|
||||
// - SUP_ACC_4001 (Supplier Account - 业务错误 - 4001)
|
||||
// - AUDIT_EVT_4041 (Audit Event - 资源不存在 - 4041)
|
||||
//
|
||||
// 错误码分类:
|
||||
// - 4xxx: 业务逻辑错误
|
||||
// - 5xxx: 系统/服务器错误
|
||||
// - 9xxx: 内部/未知错误
|
||||
|
||||
// 预定义的错误码前缀
|
||||
const (
|
||||
PrefixSUP = "SUP" // Supplier 模块
|
||||
PrefixIAM = "IAM" // Identity & Access Management 模块
|
||||
PrefixAudit = "AUDIT" // Audit 模块
|
||||
PrefixRepo = "REPO" // Repository 模块
|
||||
PrefixSys = "SYS" // 系统级错误
|
||||
)
|
||||
|
||||
// CodeError 带错误码的错误
|
||||
type CodeError struct {
|
||||
Code string // 错误码,如 "SUP_ACC_4001"
|
||||
Message string // 错误消息
|
||||
Err error // 底层错误(可选)
|
||||
}
|
||||
|
||||
// Error 实现 error 接口
|
||||
func (e *CodeError) Error() string {
|
||||
if e.Err != nil {
|
||||
return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Err)
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// Unwrap 获取底层错误
|
||||
func (e *CodeError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewCodeError 创建带错误码的错误
|
||||
func NewCodeError(code, message string) *CodeError {
|
||||
return &CodeError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// WrapCodeError 包装已有错误
|
||||
func WrapCodeError(err error, code, message string) *CodeError {
|
||||
return &CodeError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// IsCodeError 检查错误是否为 CodeError
|
||||
func IsCodeError(err error) bool {
|
||||
_, ok := err.(*CodeError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetErrorCode 从错误中提取错误码
|
||||
func GetErrorCode(err error) string {
|
||||
var codeErr *CodeError
|
||||
if As(err, &codeErr) {
|
||||
return codeErr.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// As 类型断言辅助函数
|
||||
func As(err error, target **CodeError) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if e, ok := err.(*CodeError); ok {
|
||||
*target = e
|
||||
return true
|
||||
}
|
||||
if e, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return As(e.Unwrap(), target)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Common errors - 可以被各模块引用的通用错误
|
||||
var (
|
||||
// ErrNotFound 资源不存在
|
||||
ErrNotFound = NewCodeError("SYS_4040", "resource not found")
|
||||
|
||||
// ErrInvalidInput 输入参数无效
|
||||
ErrInvalidInput = NewCodeError("SYS_4000", "invalid input parameter")
|
||||
|
||||
// ErrUnauthorized 未授权
|
||||
ErrUnauthorized = NewCodeError("SYS_4010", "unauthorized")
|
||||
|
||||
// ErrForbidden 禁止访问
|
||||
ErrForbidden = NewCodeError("SYS_4030", "forbidden")
|
||||
|
||||
// ErrInternalServer 服务器内部错误
|
||||
ErrInternalServer = NewCodeError("SYS_5000", "internal server error")
|
||||
|
||||
// ErrConcurrencyConflict 并发冲突
|
||||
ErrConcurrencyConflict = NewCodeError("SYS_4090", "concurrency conflict")
|
||||
)
|
||||
|
||||
// ValidateErrorCode 验证错误码格式是否合法
|
||||
func ValidateErrorCode(code string) bool {
|
||||
parts := strings.Split(code, "_")
|
||||
if len(parts) < 2 {
|
||||
return false
|
||||
}
|
||||
// 检查前缀是否为有效值
|
||||
prefix := parts[0]
|
||||
validPrefixes := []string{PrefixSUP, PrefixIAM, PrefixAudit, PrefixRepo, PrefixSys}
|
||||
for _, p := range validPrefixes {
|
||||
if prefix == p {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
95
supply-api/pkg/error/errors_test.go
Normal file
95
supply-api/pkg/error/errors_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewCodeError(t *testing.T) {
|
||||
err := NewCodeError("TEST_4001", "test error message")
|
||||
assert.Equal(t, "TEST_4001", err.Code)
|
||||
assert.Equal(t, "test error message", err.Message)
|
||||
assert.Nil(t, err.Err)
|
||||
assert.Equal(t, "TEST_4001: test error message", err.Error())
|
||||
}
|
||||
|
||||
func TestWrapCodeError(t *testing.T) {
|
||||
originalErr := errors.New("original error")
|
||||
err := WrapCodeError(originalErr, "TEST_4001", "wrapped error")
|
||||
assert.Equal(t, "TEST_4001", err.Code)
|
||||
assert.Equal(t, "wrapped error", err.Message)
|
||||
assert.Equal(t, originalErr, err.Err)
|
||||
assert.Contains(t, err.Error(), "caused by: original error")
|
||||
}
|
||||
|
||||
func TestIsCodeError(t *testing.T) {
|
||||
codeErr := NewCodeError("TEST_4001", "test")
|
||||
assert.True(t, IsCodeError(codeErr))
|
||||
|
||||
stdErr := errors.New("standard error")
|
||||
assert.False(t, IsCodeError(stdErr))
|
||||
}
|
||||
|
||||
func TestGetErrorCode(t *testing.T) {
|
||||
codeErr := NewCodeError("SUP_ACC_4001", "test")
|
||||
assert.Equal(t, "SUP_ACC_4001", GetErrorCode(codeErr))
|
||||
|
||||
stdErr := errors.New("standard error")
|
||||
assert.Equal(t, "", GetErrorCode(stdErr))
|
||||
}
|
||||
|
||||
func TestUnwrap(t *testing.T) {
|
||||
originalErr := errors.New("original")
|
||||
wrapped := WrapCodeError(originalErr, "TEST_4001", "wrapped")
|
||||
|
||||
// 通过 Unwrap 获取原始错误
|
||||
unwrapped := wrapped.Unwrap()
|
||||
assert.Equal(t, originalErr, unwrapped)
|
||||
}
|
||||
|
||||
func TestValidateErrorCode(t *testing.T) {
|
||||
tests := []struct {
|
||||
code string
|
||||
expected bool
|
||||
}{
|
||||
{"SUP_ACC_4001", true},
|
||||
{"IAM_ROLE_4040", true},
|
||||
{"AUDIT_EVT_5000", true},
|
||||
{"REPO_NOT_FOUND", true},
|
||||
{"SYS_5000", true},
|
||||
{"INVALID", false}, // 没有下划线分隔
|
||||
{"BAD_CODE", false}, // 前缀不在白名单
|
||||
{"X_4001", false}, // 前缀不在白名单
|
||||
{"", false}, // 空字符串
|
||||
{"TOOLONG_4001", false}, // 前缀太长
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.code, func(t *testing.T) {
|
||||
result := ValidateErrorCode(tc.code)
|
||||
assert.Equal(t, tc.expected, result, "code: %s", tc.code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommonErrors(t *testing.T) {
|
||||
assert.Equal(t, "SYS_4040", ErrNotFound.Code)
|
||||
assert.Equal(t, "resource not found", ErrNotFound.Message)
|
||||
|
||||
assert.Equal(t, "SYS_4000", ErrInvalidInput.Code)
|
||||
assert.Equal(t, "invalid input parameter", ErrInvalidInput.Message)
|
||||
|
||||
assert.Equal(t, "SYS_4010", ErrUnauthorized.Code)
|
||||
assert.Equal(t, "unauthorized", ErrUnauthorized.Message)
|
||||
|
||||
assert.Equal(t, "SYS_4030", ErrForbidden.Code)
|
||||
assert.Equal(t, "forbidden", ErrForbidden.Message)
|
||||
|
||||
assert.Equal(t, "SYS_5000", ErrInternalServer.Code)
|
||||
assert.Equal(t, "internal server error", ErrInternalServer.Message)
|
||||
|
||||
assert.Equal(t, "SYS_4090", ErrConcurrencyConflict.Code)
|
||||
assert.Equal(t, "concurrency conflict", ErrConcurrencyConflict.Message)
|
||||
}
|
||||
Reference in New Issue
Block a user