refactor: nstatus重构,重新定义错误处理。

main v1.2.0
NoahLan 11 months ago
parent ea15293e22
commit 5f4db21c11

@ -2,11 +2,13 @@ package nstatus
import "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code" import "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code"
// NewApiErr 构建Api错误code为http状态码
func NewApiErr(code int, message string) error { 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, "") return NewApiErr(code, "")
} }

@ -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, "")
}

@ -1,30 +1,31 @@
package nstatus package nstatus
import ( import (
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
var table = map[codes.Code]int{ //var table = map[codes.Code]int{
codes.OK: code.StatusOK, // codes.OK: code.StatusOK,
codes.Canceled: code.StatusRequestTimeout, // codes.Canceled: code.StatusRequestTimeout,
codes.Unknown: code.StatusInternalServerError, // codes.Unknown: code.StatusInternalServerError,
codes.InvalidArgument: code.StatusBadRequest, // codes.InvalidArgument: code.StatusBadRequest,
codes.DeadlineExceeded: code.StatusGatewayTimeout, // codes.DeadlineExceeded: code.StatusGatewayTimeout,
codes.NotFound: code.StatusNotFound, // codes.NotFound: code.StatusNotFound,
codes.AlreadyExists: code.StatusConflict, // codes.AlreadyExists: code.StatusConflict,
codes.PermissionDenied: code.StatusForbidden, // codes.PermissionDenied: code.StatusForbidden,
codes.ResourceExhausted: code.StatusTooManyRequests, // codes.ResourceExhausted: code.StatusTooManyRequests,
codes.FailedPrecondition: code.StatusBadRequest, // codes.FailedPrecondition: code.StatusBadRequest,
codes.Aborted: code.StatusConflict, // codes.Aborted: code.StatusConflict,
codes.OutOfRange: code.StatusBadRequest, // codes.OutOfRange: code.StatusBadRequest,
codes.Unimplemented: code.StatusNotImplemented, // codes.Unimplemented: code.StatusNotImplemented,
codes.Internal: code.StatusInternalServerError, // codes.Internal: code.StatusInternalServerError,
codes.Unavailable: code.StatusServiceUnavailable, // codes.Unavailable: code.StatusServiceUnavailable,
codes.DataLoss: code.StatusInternalServerError, // codes.DataLoss: code.StatusInternalServerError,
codes.Unauthenticated: code.StatusUnauthorized, // codes.Unauthenticated: code.StatusUnauthorized,
} //}
const GrpcResultTypeKey = "_RESULT_TYPE"
// WrapGrpcErr 将grpc-error转换为 Result // WrapGrpcErr 将grpc-error转换为 Result
func WrapGrpcErr(err error) *Result { func WrapGrpcErr(err error) *Result {
@ -32,10 +33,10 @@ func WrapGrpcErr(err error) *Result {
if gErr == nil { if gErr == nil {
return nil return nil
} }
if c, ok := table[gErr.Code()]; ok { //if c, ok := table[gErr.Code()]; ok {
return NewResult(c, gErr.Message()) // return NewResult(c, gErr.Message(), RpcErr)
} //}
return NewResult(int(gErr.Code()), gErr.Message()) return NewResult(int(gErr.Code()), gErr.Message(), RpcErr)
} }
// IsGrpcErr return an error if grpc status // IsGrpcErr return an error if grpc status
@ -52,7 +53,7 @@ func IsGrpcErr(err error) bool {
} }
func NewGrpcErr(code codes.Code, msg string) error { 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. // NewGrpcInternalErr returns status error with Internal error code.

@ -5,19 +5,21 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
// UnaryServerInterceptor grpc 错误拦截 服务端拦截器 // UnaryServerInterceptor grpc 错误拦截 服务端拦截器
// 将服务端传递出的任何错误拦截并转换为标准grpc-status保持code与msg不进行翻译 // 将服务端传递出的任何错误拦截并转换为标准grpc-status保持code与msg不进行翻译
// 将Result中的Type加入到metadata中进行传递
func UnaryServerInterceptor() grpc.UnaryServerInterceptor { func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
resp, err = handler(ctx, req) resp, err = handler(ctx, req)
if err != nil { if err != nil {
cause := errors.Cause(err) cause := errors.Cause(err)
e := ConvertErr(cause) e := ConvertErr(cause)
if e != nil { if e != nil {
writeResultTypeCtx(ctx, e)
// 转换为grpc-err // 转换为grpc-err
err = status.Error(codes.Code(e.Code), e.Msg) 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 // UnaryClientInterceptor 将客户端调用后产生的grpc-err转换为 *Result
// 客户端直接强转为*Result 或 再次调用 ConvertErr 即可使用 // 客户端直接强转为*Result 或 再次调用 ConvertErr 即可使用
func UnaryClientInterceptor() grpc.UnaryClientInterceptor { func UnaryClientInterceptor() grpc.UnaryClientInterceptor {
@ -34,6 +40,15 @@ func UnaryClientInterceptor() grpc.UnaryClientInterceptor {
if err == nil { if err == nil {
return 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
} }
} }

@ -1,45 +1,79 @@
package nstatus package nstatus
import ( 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 统一返回结果 结构体 // Result 统一返回结果 结构体
// {"code":200,"msg":"OK","data":{}} // {"code":200,"msg":"OK","data":{}}
// API RPC 共用 // API RPC 共用
type Result struct { type Result struct {
Code int `json:"code"` // 返回编码(业务+错误) Code int `json:"code"` // 返回编码(业务+错误)
Msg string `json:"msg"` // 消息 Msg string `json:"msg"` // 消息
Data any `json:"data,omitempty,optional"` // 数据 Data any `json:"data,omitempty,optional"` // 数据
Type ResultType `json:"-,omitempty,optional"` // Typ 返回结果类型忽略json
} }
func NewResult(code int, message string) *Result { func NewResult(code int, message string, typ ResultType) *Result {
return NewResultWithData(code, message, nil) return NewResultWithData(code, message, typ, nil)
} }
func NewResultWithData(code int, message string, data any) *Result { func NewResultWithData(code int, message string, typ ResultType, data any) *Result {
return &Result{ return &Result{
Code: code, Code: code,
Msg: message, Msg: message,
Data: data, Data: data,
} Type: typ,
}
} }
// Error 错误输出,同时可作为错误 // Error 错误输出,同时可作为错误
func (r *Result) Error() string { 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 { 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格式 // JsonString 返回JsonString格式
func (r *Result) JsonString() string { func (r *Result) JsonString() string {
bytes, err := json.Marshal(r) return njson.MarshalStrSafe(r)
if err != nil { }
return ""
} // FromError returns a Status representation of err.
return string(bytes) //
// - 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
} }

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

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

@ -3,7 +3,6 @@ package statusz
import ( import (
"git.noahlan.cn/noahlan/ntool-biz/core/i18n" "git.noahlan.cn/noahlan/ntool-biz/core/i18n"
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus" "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" "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/msg"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"net/http" "net/http"
@ -12,27 +11,31 @@ import (
// ResponseHandler API执行结果处理统一返回值类型与结构 // ResponseHandler API执行结果处理统一返回值类型与结构
func ResponseHandler(req *http.Request, w http.ResponseWriter, trans bool, resp any, err error) { func ResponseHandler(req *http.Request, w http.ResponseWriter, trans bool, resp any, err error) {
if err == nil { if err == nil {
r := nstatus.NewResultWithData(code.StatusOK, msg.Success, resp) r := nstatus.NewResultWithData(http.StatusOK, msg.Success, nstatus.CommonResult, resp)
if trans { if trans {
r.Msg = i18n.Trans(req.Context(), r.Msg) r.Msg = i18n.Trans(req.Context(), r.Msg)
} }
httpx.WriteJson(w, code.StatusOK, r) httpx.WriteJson(w, http.StatusOK, r)
return return
} }
result := nstatus.ConvertErr(err) result := nstatus.ConvertErr(err)
if result == nil { if result == nil {
// 不可能发生此情况,这里处理是以防万一 // 不可能发生此情况,这里处理是以防万一
result = nstatus.NewResult(code.StatusFailed, msg.Failed) result = nstatus.NewResult(http.StatusOK, msg.Success, nstatus.CommonResult)
} }
result.Data = resp result.Data = resp
if trans { if trans {
result.Msg = i18n.Trans(req.Context(), result.Msg) result.Msg = i18n.Trans(req.Context(), result.Msg)
} }
c := result.Code c := http.StatusInternalServerError
// 判断code如果不是http-status全部使用500 if result.Type == nstatus.ApiErr {
if result.Code > http.StatusNetworkAuthenticationRequired { // API错误按照Result内的code返回若code不是标准http错误则按照500返回
c = http.StatusInternalServerError c = result.Code
if c > http.StatusNetworkAuthenticationRequired {
c = http.StatusInternalServerError
}
} }
// 其它错误统一按照500返回
httpx.WriteJson(w, c, result) httpx.WriteJson(w, c, result)
} }

Loading…
Cancel
Save