package errcode import ( "encoding/json" "fmt" "path" "runtime" ) var codes = map[int]struct{}{} // AppError define struct of internal error type AppError struct { code int msg string cause error occurred string // 保存由底层错误导致AppErr发生时的位置 } func (e *AppError) Error() string { if e == nil { return "" } errBytes, err := json.Marshal(e.toStructuredError()) if err != nil { return fmt.Sprintf("Error() is error: json marshal error: %v", err) } return string(errBytes) } func (e *AppError) String() string { return e.Error() } // Code define func return error code func (e *AppError) Code() int { return e.code } // Msg define func return error msg func (e *AppError) Msg() string { return e.msg } // Cause define func return base error func (e *AppError) Cause() error { return e.cause } // WithCause define func return top level predefined errors,where the cause field contains the underlying base error // 在逻辑执行中出现错误, 比如dao层返回的数据库查询错误 // 可以在领域层返回预定义的错误前附加上导致错误的基础错误。 // 如果业务模块预定义的错误码比较详细, 可以使用这个方法, 反之错误码定义的比较笼统建议使用Wrap方法包装底层错误生成项目自定义Error // 并将其记录到日志后再使用预定义错误码返回接口响应 func (e *AppError) WithCause(err error) *AppError { newErr := e.Clone() newErr.cause = err newErr.occurred = getAppErrOccurredInfo() return newErr } // Wrap define func packaging information and errors returned by the underlying logic // 用于逻辑中包装底层函数返回的error 和 WithCause 一样都是为了记录错误链条 // 该方法生成的error 用于日志记录, 返回响应请使用预定义好的error func Wrap(msg string, err error) *AppError { if err == nil { return nil } appErr := &AppError{code: -1, msg: msg, cause: err} appErr.occurred = getAppErrOccurredInfo() return appErr } // UnWrap define func return the error wrapped in structure func (e *AppError) UnWrap() error { return e.cause } // Is define func return result of whether any error in err's tree matches target. implemented to support errors.Is(err, target) func (e *AppError) Is(target error) bool { targetErr, ok := target.(*AppError) if !ok { return false } return targetErr.Code() == e.Code() } // Clone define func return a new AppError with source AppError's code, msg, cause, occurred func (e *AppError) Clone() *AppError { return &AppError{ code: e.code, msg: e.msg, cause: e.cause, occurred: e.occurred, } } func newError(code int, msg string) *AppError { if code > -1 { if _, duplicated := codes[code]; duplicated { panic(fmt.Sprintf("预定义错误码 %d 不能重复, 请检查后更换", code)) } codes[code] = struct{}{} } return &AppError{code: code, msg: msg} } // getAppErrOccurredInfo 获取项目中调用Wrap或者WithCause方法时的程序位置, 方便排查问题 func getAppErrOccurredInfo() string { pc, file, line, ok := runtime.Caller(2) if !ok { return "" } file = path.Base(file) funcName := runtime.FuncForPC(pc).Name() triggerInfo := fmt.Sprintf("func: %s, file: %s, line: %d", funcName, file, line) return triggerInfo } // AppendMsg define func append a message to the existing error message func (e *AppError) AppendMsg(msg string) *AppError { n := e.Clone() n.msg = fmt.Sprintf("%s, %s", e.msg, msg) return n } // SetMsg define func set error message into specify field func (e *AppError) SetMsg(msg string) *AppError { n := e.Clone() n.msg = msg return n } type formattedErr struct { Code int `json:"code"` Msg string `json:"msg"` Cause interface{} `json:"cause"` Occurred string `json:"occurred"` } // toStructuredError 在JSON Encode 前把Error进行格式化 func (e *AppError) toStructuredError() *formattedErr { fe := new(formattedErr) fe.Code = e.Code() fe.Msg = e.Msg() fe.Occurred = e.occurred if e.cause != nil { if appErr, ok := e.cause.(*AppError); ok { fe.Cause = appErr.toStructuredError() } else { fe.Cause = e.cause.Error() } } return fe }