246 lines
5.9 KiB
Go
246 lines
5.9 KiB
Go
package errors
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// 定义错误类型常量
|
|
const (
|
|
ErrTypeDatabase = "database"
|
|
ErrTypeWeChat = "wechat"
|
|
ErrTypeHTTP = "http"
|
|
ErrTypeConfig = "config"
|
|
ErrTypeInvalidArg = "invalid_argument"
|
|
ErrTypeAuth = "authentication"
|
|
ErrTypePermission = "permission"
|
|
ErrTypeNotFound = "not_found"
|
|
ErrTypeValidation = "validation"
|
|
ErrTypeRateLimit = "rate_limit"
|
|
ErrTypeInternal = "internal"
|
|
)
|
|
|
|
// AppError 表示应用程序错误
|
|
type AppError struct {
|
|
Type string `json:"type"` // 错误类型
|
|
Message string `json:"message"` // 错误消息
|
|
Cause error `json:"-"` // 原始错误
|
|
Code int `json:"-"` // HTTP Code
|
|
Stack []string `json:"-"` // 错误堆栈
|
|
RequestID string `json:"request_id,omitempty"` // 请求ID,用于跟踪
|
|
}
|
|
|
|
// Error 实现 error 接口
|
|
func (e *AppError) Error() string {
|
|
if e.Cause != nil {
|
|
return fmt.Sprintf("%s: %s: %v", e.Type, e.Message, e.Cause)
|
|
}
|
|
return fmt.Sprintf("%s: %s", e.Type, e.Message)
|
|
}
|
|
|
|
// String 返回错误的字符串表示
|
|
func (e *AppError) String() string {
|
|
return e.Error()
|
|
}
|
|
|
|
// Unwrap 实现 errors.Unwrap 接口,用于错误链
|
|
func (e *AppError) Unwrap() error {
|
|
return e.Cause
|
|
}
|
|
|
|
// WithStack 添加堆栈信息到错误
|
|
func (e *AppError) WithStack() *AppError {
|
|
const depth = 32
|
|
var pcs [depth]uintptr
|
|
n := runtime.Callers(2, pcs[:])
|
|
frames := runtime.CallersFrames(pcs[:n])
|
|
|
|
stack := make([]string, 0, n)
|
|
for {
|
|
frame, more := frames.Next()
|
|
if !strings.Contains(frame.File, "runtime/") {
|
|
stack = append(stack, fmt.Sprintf("%s:%d %s", frame.File, frame.Line, frame.Function))
|
|
}
|
|
if !more {
|
|
break
|
|
}
|
|
}
|
|
|
|
e.Stack = stack
|
|
return e
|
|
}
|
|
|
|
// WithRequestID 添加请求ID到错误
|
|
func (e *AppError) WithRequestID(requestID string) *AppError {
|
|
e.RequestID = requestID
|
|
return e
|
|
}
|
|
|
|
// New 创建新的应用错误
|
|
func New(errType, message string, cause error, code int) *AppError {
|
|
return &AppError{
|
|
Type: errType,
|
|
Message: message,
|
|
Cause: cause,
|
|
Code: code,
|
|
}
|
|
}
|
|
|
|
// Wrap 包装现有错误为 AppError
|
|
func Wrap(err error, errType, message string, code int) *AppError {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// 如果已经是 AppError,保留原始类型但更新消息
|
|
if appErr, ok := err.(*AppError); ok {
|
|
return &AppError{
|
|
Type: appErr.Type,
|
|
Message: message,
|
|
Cause: appErr.Cause,
|
|
Code: appErr.Code,
|
|
Stack: appErr.Stack,
|
|
}
|
|
}
|
|
|
|
return New(errType, message, err, code)
|
|
}
|
|
|
|
// Is 检查错误是否为特定类型
|
|
func Is(err error, errType string) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
var appErr *AppError
|
|
if errors.As(err, &appErr) {
|
|
return appErr.Type == errType
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetType 获取错误类型
|
|
func GetType(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
|
|
var appErr *AppError
|
|
if errors.As(err, &appErr) {
|
|
return appErr.Type
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
// GetCode 获取错误的 HTTP 状态码
|
|
func GetCode(err error) int {
|
|
if err == nil {
|
|
return http.StatusOK
|
|
}
|
|
|
|
var appErr *AppError
|
|
if errors.As(err, &appErr) {
|
|
return appErr.Code
|
|
}
|
|
|
|
return http.StatusInternalServerError
|
|
}
|
|
|
|
// RootCause 获取错误链中的根本原因
|
|
func RootCause(err error) error {
|
|
for err != nil {
|
|
unwrapped := errors.Unwrap(err)
|
|
if unwrapped == nil {
|
|
return err
|
|
}
|
|
err = unwrapped
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ErrInvalidArg 无效参数错误
|
|
func ErrInvalidArg(param string) *AppError {
|
|
return New(ErrTypeInvalidArg, fmt.Sprintf("invalid arg: %s", param), nil, http.StatusBadRequest).WithStack()
|
|
}
|
|
|
|
// Database 创建数据库错误
|
|
func Database(message string, cause error) *AppError {
|
|
return New(ErrTypeDatabase, message, cause, http.StatusInternalServerError).WithStack()
|
|
}
|
|
|
|
// WeChat 创建微信相关错误
|
|
func WeChat(message string, cause error) *AppError {
|
|
return New(ErrTypeWeChat, message, cause, http.StatusInternalServerError).WithStack()
|
|
}
|
|
|
|
// HTTP 创建HTTP服务错误
|
|
func HTTP(message string, cause error) *AppError {
|
|
return New(ErrTypeHTTP, message, cause, http.StatusInternalServerError).WithStack()
|
|
}
|
|
|
|
// Config 创建配置错误
|
|
func Config(message string, cause error) *AppError {
|
|
return New(ErrTypeConfig, message, cause, http.StatusInternalServerError).WithStack()
|
|
}
|
|
|
|
// NotFound 创建资源不存在错误
|
|
func NotFound(resource string, cause error) *AppError {
|
|
message := fmt.Sprintf("resource not found: %s", resource)
|
|
return New(ErrTypeNotFound, message, cause, http.StatusNotFound).WithStack()
|
|
}
|
|
|
|
// Unauthorized 创建未授权错误
|
|
func Unauthorized(message string, cause error) *AppError {
|
|
return New(ErrTypeAuth, message, cause, http.StatusUnauthorized).WithStack()
|
|
}
|
|
|
|
// Forbidden 创建权限不足错误
|
|
func Forbidden(message string, cause error) *AppError {
|
|
return New(ErrTypePermission, message, cause, http.StatusForbidden).WithStack()
|
|
}
|
|
|
|
// Validation 创建数据验证错误
|
|
func Validation(message string, cause error) *AppError {
|
|
return New(ErrTypeValidation, message, cause, http.StatusBadRequest).WithStack()
|
|
}
|
|
|
|
// RateLimit 创建请求频率限制错误
|
|
func RateLimit(message string, cause error) *AppError {
|
|
return New(ErrTypeRateLimit, message, cause, http.StatusTooManyRequests).WithStack()
|
|
}
|
|
|
|
// Internal 创建内部服务器错误
|
|
func Internal(message string, cause error) *AppError {
|
|
return New(ErrTypeInternal, message, cause, http.StatusInternalServerError).WithStack()
|
|
}
|
|
|
|
// Err 在HTTP响应中返回错误
|
|
func Err(c *gin.Context, err error) {
|
|
// 获取请求ID(如果有)
|
|
requestID := c.GetString("RequestID")
|
|
|
|
if appErr, ok := err.(*AppError); ok {
|
|
if requestID != "" {
|
|
appErr.RequestID = requestID
|
|
}
|
|
c.JSON(appErr.Code, appErr)
|
|
return
|
|
}
|
|
|
|
// 未知错误
|
|
unknownErr := &AppError{
|
|
Type: "unknown",
|
|
Message: err.Error(),
|
|
Code: http.StatusInternalServerError,
|
|
RequestID: requestID,
|
|
}
|
|
c.JSON(http.StatusInternalServerError, unknownErr)
|
|
}
|