From 5f4db21c119d40d77c66a855f15beddf37438203 Mon Sep 17 00:00:00 2001 From: NoahLan <6995syu@163.com> Date: Fri, 4 Aug 2023 16:54:21 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20nstatus=E9=87=8D=E6=9E=84=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=AE=9A=E4=B9=89=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/nstatus/api_err.go | 6 ++- core/nstatus/biz_err.go | 11 +++++ core/nstatus/grpc_err.go | 51 +++++++++++----------- core/nstatus/grpc_interceptor.go | 19 +++++++- core/nstatus/result.go | 74 +++++++++++++++++++++++--------- core/nstatus/status.go | 34 --------------- core/nstatus/util.go | 12 ++++++ zero/statusz/handler.go | 19 ++++---- 8 files changed, 135 insertions(+), 91 deletions(-) create mode 100644 core/nstatus/biz_err.go delete mode 100644 core/nstatus/status.go create mode 100644 core/nstatus/util.go diff --git a/core/nstatus/api_err.go b/core/nstatus/api_err.go index 911f24b..1a7e661 100644 --- a/core/nstatus/api_err.go +++ b/core/nstatus/api_err.go @@ -2,11 +2,13 @@ package nstatus import "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code" +// NewApiErr 构建Api错误,code为http状态码 func NewApiErr(code int, message string) error { - return NewResult(code, message) + return NewResult(code, message, ApiErr) } -func NewApiErrWithCode(code int) error { +// NewApiErrNoMsg 构建Api错误,空错误消息 +func NewApiErrNoMsg(code int) error { return NewApiErr(code, "") } diff --git a/core/nstatus/biz_err.go b/core/nstatus/biz_err.go new file mode 100644 index 0000000..3f11d96 --- /dev/null +++ b/core/nstatus/biz_err.go @@ -0,0 +1,11 @@ +package nstatus + +// NewBizErr 构建业务错误 +func NewBizErr(code int, message string) error { + return NewResult(code, message, BizErr) +} + +// NewBizErrNoMsg 构建业务错误,空错误消息 +func NewBizErrNoMsg(code int) error { + return NewBizErr(code, "") +} diff --git a/core/nstatus/grpc_err.go b/core/nstatus/grpc_err.go index 29726d5..69a25f2 100644 --- a/core/nstatus/grpc_err.go +++ b/core/nstatus/grpc_err.go @@ -1,30 +1,31 @@ package nstatus import ( - "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -var table = map[codes.Code]int{ - codes.OK: code.StatusOK, - codes.Canceled: code.StatusRequestTimeout, - codes.Unknown: code.StatusInternalServerError, - codes.InvalidArgument: code.StatusBadRequest, - codes.DeadlineExceeded: code.StatusGatewayTimeout, - codes.NotFound: code.StatusNotFound, - codes.AlreadyExists: code.StatusConflict, - codes.PermissionDenied: code.StatusForbidden, - codes.ResourceExhausted: code.StatusTooManyRequests, - codes.FailedPrecondition: code.StatusBadRequest, - codes.Aborted: code.StatusConflict, - codes.OutOfRange: code.StatusBadRequest, - codes.Unimplemented: code.StatusNotImplemented, - codes.Internal: code.StatusInternalServerError, - codes.Unavailable: code.StatusServiceUnavailable, - codes.DataLoss: code.StatusInternalServerError, - codes.Unauthenticated: code.StatusUnauthorized, -} +//var table = map[codes.Code]int{ +// codes.OK: code.StatusOK, +// codes.Canceled: code.StatusRequestTimeout, +// codes.Unknown: code.StatusInternalServerError, +// codes.InvalidArgument: code.StatusBadRequest, +// codes.DeadlineExceeded: code.StatusGatewayTimeout, +// codes.NotFound: code.StatusNotFound, +// codes.AlreadyExists: code.StatusConflict, +// codes.PermissionDenied: code.StatusForbidden, +// codes.ResourceExhausted: code.StatusTooManyRequests, +// codes.FailedPrecondition: code.StatusBadRequest, +// codes.Aborted: code.StatusConflict, +// codes.OutOfRange: code.StatusBadRequest, +// codes.Unimplemented: code.StatusNotImplemented, +// codes.Internal: code.StatusInternalServerError, +// codes.Unavailable: code.StatusServiceUnavailable, +// codes.DataLoss: code.StatusInternalServerError, +// codes.Unauthenticated: code.StatusUnauthorized, +//} + +const GrpcResultTypeKey = "_RESULT_TYPE" // WrapGrpcErr 将grpc-error转换为 Result func WrapGrpcErr(err error) *Result { @@ -32,10 +33,10 @@ func WrapGrpcErr(err error) *Result { if gErr == nil { return nil } - if c, ok := table[gErr.Code()]; ok { - return NewResult(c, gErr.Message()) - } - return NewResult(int(gErr.Code()), gErr.Message()) + //if c, ok := table[gErr.Code()]; ok { + // return NewResult(c, gErr.Message(), RpcErr) + //} + return NewResult(int(gErr.Code()), gErr.Message(), RpcErr) } // IsGrpcErr return an error if grpc status @@ -52,7 +53,7 @@ func IsGrpcErr(err error) bool { } func NewGrpcErr(code codes.Code, msg string) error { - return NewResult(int(code), msg) + return NewResult(int(code), msg, RpcErr) } // NewGrpcInternalErr returns status error with Internal error code. diff --git a/core/nstatus/grpc_interceptor.go b/core/nstatus/grpc_interceptor.go index b475aa4..a9a6397 100644 --- a/core/nstatus/grpc_interceptor.go +++ b/core/nstatus/grpc_interceptor.go @@ -5,19 +5,21 @@ import ( "github.com/pkg/errors" "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) // UnaryServerInterceptor grpc 错误拦截 服务端拦截器 // 将服务端传递出的任何错误拦截并转换为标准grpc-status,保持code与msg,不进行翻译 +// 将Result中的Type加入到metadata中进行传递 func UnaryServerInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { resp, err = handler(ctx, req) if err != nil { cause := errors.Cause(err) - e := ConvertErr(cause) if e != nil { + writeResultTypeCtx(ctx, e) // 转换为grpc-err err = status.Error(codes.Code(e.Code), e.Msg) } @@ -26,6 +28,10 @@ func UnaryServerInterceptor() grpc.UnaryServerInterceptor { } } +func writeResultTypeCtx(ctx context.Context, r *Result) { + _ = grpc.SetHeader(ctx, metadata.Pairs(GrpcResultTypeKey, string(r.Type))) +} + // UnaryClientInterceptor 将客户端调用后产生的grpc-err转换为 *Result // 客户端直接强转为*Result 或 再次调用 ConvertErr 即可使用 func UnaryClientInterceptor() grpc.UnaryClientInterceptor { @@ -34,6 +40,15 @@ func UnaryClientInterceptor() grpc.UnaryClientInterceptor { if err == nil { return nil } - return ConvertErr(err) + e := ConvertErr(err) + // 读取header(metadata)中的GrpcResultType + md, ok := metadata.FromOutgoingContext(ctx) + if ok { + rt := md.Get(GrpcResultTypeKey) + if len(rt) > 0 { + e.Type = ResultType(rt[0]) + } + } + return e } } diff --git a/core/nstatus/result.go b/core/nstatus/result.go index 53e36db..6ecd555 100644 --- a/core/nstatus/result.go +++ b/core/nstatus/result.go @@ -1,45 +1,79 @@ package nstatus import ( - "encoding/json" - "fmt" + "fmt" + "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code" + "git.noahlan.cn/noahlan/ntool/njson" +) + +type ResultType string + +const ( + CommonResult ResultType = "common" // 一般返回值 + ApiErr = "api_err" // ApiErr Api错误,用于返回携带http状态码的错误返回信息 + BizErr = "biz_err" // BizErr 业务错误,错误状态码统一为500,详细状态码在返回体的code中 + RpcErr = "rpc_err" // RpcErr Rpc错误 ) // Result 统一返回结果 结构体 // {"code":200,"msg":"OK","data":{}} // API RPC 共用 type Result struct { - Code int `json:"code"` // 返回编码(业务+错误) - Msg string `json:"msg"` // 消息 - Data any `json:"data,omitempty,optional"` // 数据 + Code int `json:"code"` // 返回编码(业务+错误) + Msg string `json:"msg"` // 消息 + Data any `json:"data,omitempty,optional"` // 数据 + Type ResultType `json:"-,omitempty,optional"` // Typ 返回结果类型,忽略json } -func NewResult(code int, message string) *Result { - return NewResultWithData(code, message, nil) +func NewResult(code int, message string, typ ResultType) *Result { + return NewResultWithData(code, message, typ, nil) } -func NewResultWithData(code int, message string, data any) *Result { - return &Result{ - Code: code, - Msg: message, - Data: data, - } +func NewResultWithData(code int, message string, typ ResultType, data any) *Result { + return &Result{ + Code: code, + Msg: message, + Data: data, + Type: typ, + } } // Error 错误输出,同时可作为错误 func (r *Result) Error() string { - return r.String() + return r.Msg +} + +func (r *Result) Err() error { + return r +} + +func (r *Result) Cause() error { + return r } func (r *Result) String() string { - return fmt.Sprintf("Code:%d Message:%s Data:%v", r.Code, r.Msg, r.Data) + return fmt.Sprintf("Code:%d Message:%s Data:%v", r.Code, r.Msg, r.Data) } // JsonString 返回JsonString格式 func (r *Result) JsonString() string { - bytes, err := json.Marshal(r) - if err != nil { - return "" - } - return string(bytes) + return njson.MarshalStrSafe(r) +} + +// FromError returns a Status representation of err. +// +// - If err was produced by this package *Result`, the appropriate Result is returned. +// - If err is nil, a nil is returned. +// +// - Otherwise, err is an error not compatible with this package. In this +// case, a *Result is returned with code.StatusUnknown and err's Error() message, +// and ok is false. +func FromError(err error) (*Result, bool) { + if err == nil { + return nil, false + } + if se, ok := err.(*Result); ok { + return se, true + } + return NewResult(code.StatusUnknown, err.Error(), CommonResult), false } diff --git a/core/nstatus/status.go b/core/nstatus/status.go deleted file mode 100644 index 44d4462..0000000 --- a/core/nstatus/status.go +++ /dev/null @@ -1,34 +0,0 @@ -package nstatus - -import ( - "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code" -) - -// ConvertErr is a convenience function which removes the need to handle the -// boolean return value from FromError. -func ConvertErr(err error) *Result { - s, ok := FromError(err) - if !ok { - // 尝试grpc - return WrapGrpcErr(err) - } - return s -} - -// FromError returns a Status representation of err. -// -// - If err was produced by this package *Result`, the appropriate Result is returned. -// - If err is nil, a nil is returned. -// -// - Otherwise, err is an error not compatible with this package. In this -// case, a *Result is returned with code.StatusUnknown and err's Error() message, -// and ok is false. -func FromError(err error) (s *Result, ok bool) { - if err == nil { - return nil, false - } - if se, ok := err.(*Result); ok { - return se, true - } - return NewResult(code.StatusUnknown, err.Error()), false -} diff --git a/core/nstatus/util.go b/core/nstatus/util.go new file mode 100644 index 0000000..51c6890 --- /dev/null +++ b/core/nstatus/util.go @@ -0,0 +1,12 @@ +package nstatus + +// ConvertErr is a convenience function which removes the need to handle the +// boolean return value from FromError. +func ConvertErr(err error) *Result { + s, ok := FromError(err) + if !ok { + // 尝试grpc + return WrapGrpcErr(err) + } + return s +} diff --git a/zero/statusz/handler.go b/zero/statusz/handler.go index eb81c7e..eab21b8 100644 --- a/zero/statusz/handler.go +++ b/zero/statusz/handler.go @@ -3,7 +3,6 @@ package statusz import ( "git.noahlan.cn/noahlan/ntool-biz/core/i18n" "git.noahlan.cn/noahlan/ntool-biz/core/nstatus" - "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code" "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/msg" "github.com/zeromicro/go-zero/rest/httpx" "net/http" @@ -12,27 +11,31 @@ import ( // ResponseHandler API执行结果处理,统一返回值类型与结构 func ResponseHandler(req *http.Request, w http.ResponseWriter, trans bool, resp any, err error) { if err == nil { - r := nstatus.NewResultWithData(code.StatusOK, msg.Success, resp) + r := nstatus.NewResultWithData(http.StatusOK, msg.Success, nstatus.CommonResult, resp) if trans { r.Msg = i18n.Trans(req.Context(), r.Msg) } - httpx.WriteJson(w, code.StatusOK, r) + httpx.WriteJson(w, http.StatusOK, r) return } result := nstatus.ConvertErr(err) if result == nil { // 不可能发生此情况,这里处理是以防万一 - result = nstatus.NewResult(code.StatusFailed, msg.Failed) + result = nstatus.NewResult(http.StatusOK, msg.Success, nstatus.CommonResult) } result.Data = resp if trans { result.Msg = i18n.Trans(req.Context(), result.Msg) } - c := result.Code - // 判断code,如果不是http-status,全部使用500 - if result.Code > http.StatusNetworkAuthenticationRequired { - c = http.StatusInternalServerError + c := http.StatusInternalServerError + if result.Type == nstatus.ApiErr { + // API错误,按照Result内的code返回,若code不是标准http错误,则按照500返回 + c = result.Code + if c > http.StatusNetworkAuthenticationRequired { + c = http.StatusInternalServerError + } } + // 其它错误统一按照500返回 httpx.WriteJson(w, c, result) }