golang封装业务err(结合iris)

golang封装业务err

我们有时在web开发时,仅凭httpStatus以及msg是不方便维护和体现我们的业务逻辑的。所以就需要封装我们自己的业务错误。

  • 自定义biz_err
  • 维护err map:errorResponseMap、errorHttpStatusMap

注意:本文主要以演示为主,主要是让大家熟悉封装自定义错误的思路,故而封装的较为简单。大家可根据自己公司需求来进行拓展。

代码仓库地址:https://github.com/ziyifast/ziyifast-code_instruction

项目结构:
golang封装业务err(结合iris)_第1张图片

1 err:自定义err,重写打印格式等

1.1 biz_err_demo/error/zerr/errors.go:new方法

  1. 重写控制台打印格式
  2. 封装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(),
	}
}

1.2 biz_err_demo/error/zerr/stack.go:堆栈打印格式

定义堆栈打印格式

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:]
}

1.3 biz_err_demo/error/zerr/wrap.go:判断err类型

由自定义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()

1.4 biz_err_demo/error/biz_err/code.go:业务错误码

自定义业务错误码、对应错误信息及对应错误对应的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
}

2 controller:封装base_controller

2.1 biz_err_demo/constant/constant.go

package constant

const (
	ContentTypeJson = "application/json"
	ContentTypeXml  = "application/xml"
)

2.2 biz_err_demo/controller/base_controller.go

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)
}

2.3 biz_err_demo/controller/test_biz_controller.go

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)
}

3 封装response

3.1 biz_err_demo/response/json_response.go

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,
	}
}

4 测试效果

4.1 biz_err_demo/test/main.go

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)
}

golang封装业务err(结合iris)_第2张图片

4.2 biz_err_demo/main.go

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)
}

golang封装业务err(结合iris)_第3张图片

这样前端就能直接根据我们的业务错误码展示对应msg信息

你可能感兴趣的:(框架,golang,开发语言,后端,前端,封装,错误码)