GPRC 接口统一返回值处理以及错误处理规范

**

GPRC 接口统一返回值处理以及错误处理规范

**
公共返回值
规范:

grpc接口 公共返回值:

type BaseResponse struct {
	Code uint32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
	Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
	Data *anypb.Any `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
}

使用 proto.google.protobuf 库 anypb.Any 结构体, 业务接口不需要关注 公共的结构体,eg: Code , Message, Data, 只要关注 返回的第三方业务数据

//改造前
func (a *Auth) GetUser(ctx context.Context, req *authsvr.User) (*authsvr.UserResponse, error) {
	rsp := &authsvr.UserResponse{}
	var user model.User
	// where 条件要写在前面
	result := mysql.DB().Where("username = ?", req.Username).Preload("Roles").First(&user)
	if result.Error != nil {
		log.Errorf(ctx, "GetUser:First err:%+v", result.Error)
		rsp.Message = result.Error.Error()
		rsp.Code = _const.DbOperationError
		return rsp, nil
	}

	rsp.Data = &authsvr.User{
		Id:          uint64(user.ID),
		Username:    user.Username,
		ChineseName: user.ChineseName,
		Email:       user.Email,
		IsAdmin:     user.IsAdmin,
	}
	// 用户角色
	for _, role := range user.Roles {
		rsp.Data.Roles = append(rsp.Data.Roles, role.Name)
	}
	rsp.Message = "ok"
	rsp.Code = _const.Success
	return rsp, nil
}


// 改造后
func (a *Auth) GetUser(ctx context.Context, req *authsvr.User) (*base.BaseResponse, error) {
	var user model.User
	// where 条件要写在前面
	result := mysql.DB().Where("username = ?", req.Username).Preload("Roles").First(&user)
	if result.Error != nil {
		return response.Option().Code(_const.DbOperationError).Log(ctx, "GetUser:First err:%+v", result.Error).Add(func() {
			log.Printf(ctx, "test")
		}).End()
	}

	rsp := &authsvr.User{
		Id:          uint64(user.ID),
		Username:    user.Username,
		ChineseName: user.ChineseName,
		Email:       user.Email,
		IsAdmin:     user.IsAdmin,
	}
	// 用户角色
	for _, role := range user.Roles {
		rsp.Roles = append(rsp.Roles, role.Name)
	}

	return response.Option().End(rsp)
}

// 客户端 模块调用
func GetUser(c *gin.Context) {
	userName, _ := c.GetQuery("username")
	rsp, err := handler.AuthClient.GetUser(context.Background(), &authsvr.User{
		Username: userName,
	})
	handler.Response(c, rsp, err)
}

func Response(c *gin.Context, rsp *base.BaseResponse, err error) {
	if rsp != nil {
		// 业务逻辑正常返回
		doResponseRsp(c, rsp)
	} else if err != nil {
		// Grpc内部服务异常,eg: 服务panic等
		doResponseErr(c, err)
	} else {
		// 其他未知异常
		c.JSON(http.StatusOK, SuccessResponse{
			Code: _const.UnKnown,
			Msg:  _const.CodeMessageMap[_const.UnKnown],
		})
	}
}

func doResponseRsp(c *gin.Context, rsp *base.BaseResponse) {
	if rsp.GetCode() != _const.Success {
		c.JSON(http.StatusOK, SuccessResponse{
			Code: int(rsp.Code),
			Msg:  rsp.Message,
		})
	} else {
		rsptype, _ := rsp.Data.UnmarshalNew()
		c.JSON(http.StatusOK, SuccessResponse{
			Code: _const.Success,
			Msg:  "success",
			Data: rsptype,
		})
	}
}

func doResponseErr(c *gin.Context, err error) {
	c.JSON(http.StatusOK, SuccessResponse{
		Code: _const.GRpcError,
		Msg:  err.Error(),
	})
}

**

错误处理

**

自定义GRPC插件

// 1、打印出入参数
// 2、log 错误打印会打印出RPC方法
// 3、自动填充 code,message,默认值是 10000,ok
// 4、当 returnRpcErrorOrNot 是true,如果但是code 不是 10000,则返回对应 message 对应的error
// 5、插件没有强依赖性,不加的话,上述1,2点不会生效,不会对程序造成任何影响
// 6、returnRpcErrorOrNot需要讨论,对于现有服务只是使用err 判断,如果不返回err 信息会影响原先的服务, 这种情况下,要把returnRpcErrorOrNot置为true, 或者改造原先服务,或者统一都返回err 信息
func UnaryRPCMethodCallbackInterceptor(returnRpcErrorOrNot bool) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler) (interface{}, error) {
		// 入参打印
		log.Printf(ctx, "enter method:%s,req is: %v", info.FullMethod, req)

		// context 增加返回头部
		md := metadata.New(map[string]string{
			METADATA_METHOD: info.FullMethod,
		})
		ctx = metadata.NewOutgoingContext(ctx, md)

		rsp, err := handler(ctx, req)

		// 出参打印
		log.Printf(ctx, "leave method:%s,rsp is: %v,err is:%v", info.FullMethod, rsp, err)
		if returnRpcErrorOrNot {
			return rsp, err
		}
		return rsp, nil
	}
}

type responseOption struct {
	basersp *base.BaseResponse
	ro      responseOptions
	err     error
}

type responseOptions []func()

func Option() (r *responseOption) {
	return &responseOption{
		basersp: &base.BaseResponse{
			Code:    _const.Success,
			Message: "ok",
		},
		ro: responseOptions{},
	}
}

func (r *responseOption) Code(code int) *responseOption {
	if code != _const.Success {
		var message = ""
		var ok bool
		if message, ok = _const.CodeMessageMap[code]; !ok {
			message = _const.CodeMessageMap[_const.UnKnown]
		}
		r.codeAndMessage(code, message)
	}
	return r
}

func (r *responseOption) codeAndMessage(code int, message string) *responseOption {
	fn := func() {
		r.basersp.Code = uint32(code)
		r.basersp.Message = message
		r.err = errors.New(message)
	}
	r.Add(fn)
	return r
}

func (r *responseOption) Log(ctx context.Context, format string, args ...interface{}) *responseOption {
	fn := func() {
		md, _, ok := metadata.FromOutgoingContextRaw(ctx)
		if ok {
			// 增加打印method
			format = fmt.Sprintf("method:%s ,"+format, md[METADATA_METHOD])
		}

		if r.basersp.Code == _const.Success {
			log.Infof(ctx, format, args)
		} else {
			log.Errorf(ctx, format, args)
		}
	}
	r.Add(fn)
	return r
}

func (r *responseOption) Add(fns ...func()) *responseOption {
	r.ro = append(r.ro, fns...)
	return r
}

// 返回返回值
func (r *responseOption) End(pbTypes ...proto.Message) (*base.BaseResponse, error) {
	for _, fn := range r.ro {
		fn()
	}

	// 默认取第一个入参
	for _, pbType := range pbTypes {
		if pbType != (proto.Message)(nil) {
			pb, err := anypb.New(pbType)
			if err != nil {
				// 特定的返回Code
				r.basersp.Code = _const.RpcMethodDefinitionPlugeError
				r.basersp.Message = _const.CodeMessageMap[int(r.basersp.Code)]
				r.err = err
			} else {
				r.basersp.Data = pb
			}
		}
		break
	}

	return r.basersp, r.err
}

// 使用
sb := zgrpc.NewServerBuilder()
sb.WithServerOptions(grpc.ChainUnaryInterceptor(response.UnaryRPCMethodCallbackInterceptor(false))).WithRegister(authsvr.RegisterAuthServer, new(handler.Auth))

你可能感兴趣的:(go,go,后端)