赞
踩
我们有时在web开发时,仅凭httpStatus以及msg是不方便维护和体现我们的业务逻辑的。所以就需要封装我们自己的业务错误。
- 自定义biz_err
- 维护err map:errorResponseMap、errorHttpStatusMap
注意:本文主要以演示为主,主要是让大家熟悉封装自定义错误的思路,故而封装的较为简单。大家可根据自己公司需求来进行拓展。
代码仓库地址:https://github.com/ziyifast/ziyifast-code_instruction
项目结构:

- 重写控制台打印格式
- 封装new方法
- DefaultBizWrap:不含原始err
- BizWrap:包含原始err
package zerr import ( "errors" "fmt" "io" ) func New(message string) error { return &fundamental{ msg: message, stack: callers(), } } func Errorf(format string, args ...interface{}) error { return &fundamental{ msg: fmt.Sprintf(format, args...), stack: callers(), } } type fundamental struct { msg string *stack } func (f *fundamental) Error() string { return f.msg } func (f *fundamental) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { io.WriteString(s, f.msg) f.stack.Format(s, verb) return } fallthrough case 's': io.WriteString(s, f.msg) case 'q': fmt.Fprintf(s, "%q", f.msg) } } func WithStack(err error) error { if err == nil { return nil } return &withStack{ err, callers(), } } type withStack struct { error *stack } func (w *withStack) Cause() error { return w.error } func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { fmt.Fprintf(s, "%+v", w.Cause()) w.stack.Format(s, verb) return } fallthrough case 's': io.WriteString(s, w.Error()) case 'q': fmt.Fprintf(s, "%q", w.Error()) } } func Wrap(err error, message string) error { if err == nil { return nil } err = &withMessage{ cause: err, msg: message, } return &withStack{ err, callers(), } } func Trace(err error) error { return Wrapf(err, "") } func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } err = &withMessage{ cause: err, msg: fmt.Sprintf(format, args...), } return &withStack{ err, callers(), } } func WithMessage(err error, message string) error { if err == nil { return nil } return &withMessage{ cause: err, msg: message, } } func WithMessagef(err error, format string, args ...interface{}) error { if err == nil { return nil } return &withMessage{ cause: err, msg: fmt.Sprintf(format, args...), } } type withMessage struct { cause error msg string } func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Cause() error { return w.cause } func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { fmt.Fprintf(s, "%+v\n", w.Cause()) io.WriteString(s, w.msg) return } fallthrough case 's', 'q': io.WriteString(s, w.Error()) } } func Cause(err error) error { type causer interface { Cause() error } for err != nil { cause, ok := err.(causer) if !ok { break } err = cause.Cause() } return err } func WithCode(err error, code string) error { if err == nil { return nil } return &ErrWrap{ cause: err, code: code, } } func WithCodef(err error, format string, args ...interface{}) error { if err == nil { return nil } return &ErrWrap{ cause: err, code: fmt.Sprintf(format, args...), } } type ErrWrap struct { cause error code string vars []string } func (w *ErrWrap) Vars() []string { return w.vars } func (w *ErrWrap) Code() string { return w.code } func (w *ErrWrap) Error() string { var msg string if w.cause != nil { msg += w.cause.Error() } return msg } func (w *ErrWrap) Cause() error { return w.cause } // Format rewrite format func (w *ErrWrap) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { fmt.Fprintf(s, "%+v\n", w.Cause()) io.WriteString(s, "BizCode=["+string(w.code)+"]") return } fallthrough case 's', 'q': io.WriteString(s, w.Error()) } } func BizWrap(err error, code string, message string, vars ...string) error { if err == nil { return nil } codeErr := &ErrWrap{ cause: err, code: code, vars: vars, } err = &withMessage{ cause: codeErr, msg: message, } return &withStack{ err, callers(), } } func DefaultBizWrap(code string, vars ...string) error { err := errors.New("") codeErr := &ErrWrap{ cause: err, code: code, vars: vars, } err = &withMessage{ cause: codeErr, } return &withStack{ err, callers(), } }
定义堆栈打印格式
package zerr import ( "fmt" "io" "path" "runtime" "strings" ) type Frame uintptr func (f Frame) pc() uintptr { return uintptr(f) - 1 } func (f Frame) file() string { fn := runtime.FuncForPC(f.pc()) if fn == nil { return "unknown" } file, _ := fn.FileLine(f.pc()) return file } func (f Frame) line() int { fn := runtime.FuncForPC(f.pc()) if fn == nil { return 0 } _, line := fn.FileLine(f.pc()) return line } func (f Frame) Format(s fmt.State, verb rune) { switch verb { case 's': switch { case s.Flag('+'): pc := f.pc() fn := runtime.FuncForPC(pc) if fn == nil { io.WriteString(s, "unknown") } else { file, _ := fn.FileLine(pc) fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) } default: io.WriteString(s, path.Base(f.file())) } case 'd': fmt.Fprintf(s, "%d", f.line()) case 'n': name := runtime.FuncForPC(f.pc()).Name() io.WriteString(s, funcname(name)) case 'v': f.Format(s, 's') io.WriteString(s, ":") f.Format(s, 'd') } } type StackTrace []Frame func (st StackTrace) Format(s fmt.State, verb rune) { switch verb { case 'v': switch { case s.Flag('+'): for _, f := range st { fmt.Fprintf(s, "\n%+v", f) } case s.Flag('#'): fmt.Fprintf(s, "%#v", []Frame(st)) default: fmt.Fprintf(s, "%v", []Frame(st)) } case 's': fmt.Fprintf(s, "%s", []Frame(st)) } } type stack []uintptr func (s *stack) Format(st fmt.State, verb rune) { switch verb { case 'v': switch { case st.Flag('+'): for _, pc := range *s { f := Frame(pc) fmt.Fprintf(st, "\n%+v", f) } } } } func (s *stack) StackTrace() StackTrace { f := make([]Frame, len(*s)) for i := 0; i < len(f); i++ { f[i] = Frame((*s)[i]) } return f } func callers() *stack { const depth = 32 var pcs [depth]uintptr n := runtime.Callers(3, pcs[:]) if n > 1 { n = 1 } var st stack = pcs[0:n] return &st } func funcname(name string) string { i := strings.LastIndex(name, "/") name = name[i+1:] i = strings.Index(name, ".") return name[i+1:] }
由自定义err,判断是否属于某个err
package zerr import ( "errors" "reflect" ) func Unwrap(err error) error { u, ok := err.(interface { Cause() error }) if !ok { return errors.Unwrap(err) } return u.Cause() } func Is(err, target error) bool { if target == nil { return err == target } isComparable := reflect.TypeOf(target).Comparable() for { if isComparable && err == target { return true } if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { return true } if err = Unwrap(err); err == nil { return false } } } func As(err error, target interface{}) bool { if target == nil { panic("errors: target cannot be nil") } val := reflect.ValueOf(target) typ := val.Type() if typ.Kind() != reflect.Ptr || val.IsNil() { panic("errors: target must be a non-nil pointer") } targetType := typ.Elem() if targetType.Kind() != reflect.Interface && !targetType.Implements(errorType) { panic("errors: *target must be interface or implement error") } for err != nil { if reflect.TypeOf(err).AssignableTo(targetType) { val.Elem().Set(reflect.ValueOf(err)) return true } if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { return true } err = Unwrap(err) } return false } var errorType = reflect.TypeOf((*error)(nil)).Elem()
自定义业务错误码、对应错误信息及对应错误对应的httpStatusCode
package biz_err import ( "myTest/demo_home/biz_err_demo/error/zerr" "net/http" "strings" ) const ( Undefined = "Undefined" OsCreateFileError = "OsCreateFileError" ImageNotSupported = "ImageNotSupported" UsernameOrPasswordInValid = "UsernameOrPasswordInValid" ) var errorResponseMap = map[string]string{ OsCreateFileError: "创建文件失败", ImageNotSupported: "图片格式不支持", UsernameOrPasswordInValid: "用户名或密码错误", } var errorHttpStatusMap = map[string]int{ OsCreateFileError: http.StatusInternalServerError, ImageNotSupported: http.StatusInternalServerError, UsernameOrPasswordInValid: http.StatusInternalServerError, } func ParseBizErr(err error) (httpStatus int, code, msg string) { if err == nil { code = Undefined } vars := make([]string, 0) errWrap := new(zerr.ErrWrap) var cause error if as := zerr.As(err, &errWrap); as { code = errWrap.Code() cause = errWrap.Cause() vars = errWrap.Vars() } else { code = Undefined } if code == Undefined { var undefinedMsg string if err != nil { undefinedMsg = err.Error() } if undefinedMsg == "" || undefinedMsg == ": " { undefinedMsg = errorResponseMap[code] } return errorHttpStatusMap[code], code, undefinedMsg } if status, ok := errorHttpStatusMap[code]; ok { httpStatus = status } else { httpStatus = http.StatusOK } if bizMsg, ok := errorResponseMap[code]; ok { for _, v := range vars { bizMsg = strings.Replace(bizMsg, "%s", v, 1) } msg = bizMsg if cause != nil { _, _, causeMsg := ParseBizErr(cause) if causeMsg != "" { msg += ", " + causeMsg } else { msg += ", " + errWrap.Error() } } } else { msg = errWrap.Error() } return httpStatus, code, msg } func ErrResponse(err error) (httpStatus int, code, msg string) { if err == nil { code = Undefined } vars := make([]string, 0) errWrap := new(zerr.ErrWrap) var cause error if as := zerr.As(err, &errWrap); as { code = errWrap.Code() cause = errWrap.Cause() vars = errWrap.Vars() } else { code = Undefined } if status, ok := errorHttpStatusMap[code]; ok { httpStatus = status } else { httpStatus = http.StatusOK } if bizMsg, ok := errorResponseMap[code]; ok { for _, v := range vars { bizMsg = strings.Replace(bizMsg, "%s", v, 1) } msg = bizMsg if cause != nil { _, _, causeMsg := ErrResponse(cause) if causeMsg != "" { msg += causeMsg } else { msg += errWrap.Error() } } } else { msg = errWrap.Error() } return httpStatus, code, msg }
package constant
const (
ContentTypeJson = "application/json"
ContentTypeXml = "application/xml"
)
package controller import ( "encoding/json" "encoding/xml" "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/mvc" "github.com/sirupsen/logrus" "myTest/demo_home/biz_err_demo/constant" "myTest/demo_home/biz_err_demo/error/biz_err" "myTest/demo_home/biz_err_demo/response" "net/http" ) type BaseController struct { Ctx iris.Context } func commonResp(errMsg string, httpCode int, returnCode response.Code, content interface{}) mvc.Response { payload := &response.JsonResponse{ Code: returnCode, Msg: errMsg, Content: content, } contentDetail, err := json.Marshal(payload) if err != nil { logrus.Infof("marshal json response error %v", err) } return mvc.Response{ Code: httpCode, Content: contentDetail, ContentType: constant.ContentTypeJson, } } func (c *BaseController) Xml(httpCode int, content interface{}) mvc.Response { payload, err := xml.Marshal(content) if err != nil { logrus.Errorf("marshal xml response error %v", err) } return c.XmlRaw(httpCode, payload) } func (c *BaseController) XmlOK(content interface{}) mvc.Response { payload, err := xml.Marshal(content) if err != nil { logrus.Errorf("marshal xml response error %v", err) } return c.XmlRaw(http.StatusOK, payload) } func (c *BaseController) XmlRaw(httpCode int, content []byte) mvc.Response { return mvc.Response{ Code: httpCode, Content: content, ContentType: constant.ContentTypeXml, } } func (c *BaseController) JsonBizError(err error) mvc.Response { httpStatus, code, msg := biz_err.ErrResponse(err) return commonResp(msg, httpStatus, response.Code(code), nil) }
package controller import ( "errors" "github.com/kataras/iris/v12/mvc" "myTest/demo_home/biz_err_demo/error/biz_err" "myTest/demo_home/biz_err_demo/error/zerr" "myTest/demo_home/biz_err_demo/response" "net/http" ) type TestBizController struct { BaseController } func (t *TestBizController) BeforeActivation(b mvc.BeforeActivation) { b.Handle(http.MethodGet, "/testBizErr", "TestBizErr") } func (t *TestBizController) TestBizErr() mvc.Result { err1 := errors.New("") err := zerr.BizWrap(err1, biz_err.UsernameOrPasswordInValid, "") return response.JsonBizError(err) }
package response import ( "encoding/json" "github.com/kataras/iris/v12/mvc" "github.com/sirupsen/logrus" "myTest/demo_home/biz_err_demo/constant" "myTest/demo_home/biz_err_demo/error/biz_err" ) type Code string type JsonResponse struct { Code Code `json:"code"` Msg string `json:"msg"` Content interface{} `json:"content,omitempty"` } func JsonBizError(err error) mvc.Response { httpStatus, code, msg := biz_err.ErrResponse(err) return commonResp(msg, httpStatus, Code(code), nil) } func commonResp(errMsg string, httpCode int, returnCode Code, content interface{}) mvc.Response { payload := &JsonResponse{ Code: returnCode, Msg: errMsg, Content: content, } contentDetail, err := json.Marshal(payload) if err != nil { logrus.Errorf("%v", err) } return mvc.Response{ Code: httpCode, Content: contentDetail, ContentType: constant.ContentTypeJson, } }
package main import ( "errors" "github.com/sirupsen/logrus" "myTest/demo_home/biz_err_demo/error/biz_err" "myTest/demo_home/biz_err_demo/error/zerr" ) func init() { logrus.SetReportCaller(true) // 设置日志是否记录被调用的位置,默认值为 false } func main() { TestWithNoSourceErr() TestWithSourceErr() TestParseBizErr() } func TestWithNoSourceErr() { err := zerr.DefaultBizWrap(biz_err.UsernameOrPasswordInValid, "") logrus.Errorf("TestWithNoSourceErr %+v", err) } func TestWithSourceErr() { err := errors.New("invalid image") err = zerr.BizWrap(err, biz_err.ImageNotSupported, "") logrus.Errorf("TestWithSourceErr %+v", err) } func TestParseBizErr() { err := errors.New("") err = zerr.BizWrap(err, biz_err.ImageNotSupported, "") httpStatus, bizCode, msg := biz_err.ParseBizErr(err) logrus.Errorf("httpStatus:%d bizCode:%s msg:%s", httpStatus, bizCode, msg) }

package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"myTest/demo_home/biz_err_demo/controller"
)
func main() {
app := iris.New()
mvc.New(app).Handle(new(controller.TestBizController))
app.Listen(":8088", nil)
}

这样前端就能直接根据我们的业务错误码展示对应msg信息
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。