首页 > 其他分享 >GoFrame 优化接口的错误码和异常的思路

GoFrame 优化接口的错误码和异常的思路

时间:2024-03-13 16:59:17浏览次数:30  
标签:code return string err 接口 GoFrame 错误码 func msg

前言

你是否想在使用 GoFrame 的过程中,拥有一个能打印异常堆栈,能自定义响应状态码,能统一处理响应数据的接口。如果你回答是,那么,请耐心看完本文,或许会对你有所启发。若文中由表达不当之处,恳请不吝赐教。

异常都需要错误堆栈吗

为什么会问这个问题呢,所有的接口错误都会向日志中抛出堆栈信息,这不是好事吗?答案是否定的。

业务开发中,通常有业务异常系统异常两种 err,我这里暂且这么称呼,也有的称为业务异常为"错误",系统异常为"异常"。业务异常是由用户输入不当引起的,比如说账号密码错误,这种 err 通常只返回给用户即可,不需要打印堆栈信息。而系统异常是由系统内部自发引起的,比如说 SQL 语句不当,这种错误需要打印堆栈信息,且不能把 err 返回到用户那里,不然会暴露代码结构,严重的可能会暴露数据库结构。

GoFrame 中,因为有着强大的 gerror 组件,所以只要接收了任何组件方法中的 err,不论业务异常系统异常,都会打印堆栈信息,这与我们的设计目标不符合,需要解决它。

状态码

此处的状态码区别与 HTTP 状态码,它是我们自定义的一套业务码,比如这样:

{
	"code": 10001,
	"message": "用户名密码错误",
	"data": null
}

{
	"code": 10002,
	"message": "用户不存在",
	"data": null
}

它们的 HTTP 状态码都是 200,代表响应成功,但是业务状态码不同,用以区分不同的业务异常。

一个例子

我们来编写一个完整的示例:

接口文件:/api/exception/v1/exception.go:

// 模拟业务异常
type BusinessReq struct {
	g.Meta `path:"/business" method:"get"`
}

type BusinessRes struct {
	Name string
	Age  int
}

// 模拟系统异常
type SystemReq struct {
	g.Meta `path:"/system" method:"get"`
}

type SystemRes struct {
	Name string
	Age  int
}

控制器文件:/internal/controller/exception/exception_v1_*.go:

func (c *ControllerV1) Business(ctx context.Context, req *v1.BusinessReq) (res *v1.BusinessRes, err error) {
	err = service.Exception().Business()
	if err != nil {
		return nil, err
	}
	return &v1.BusinessRes{
		Name: "business",
		Age:  1,
	}, nil
}

func (c *ControllerV1) System(ctx context.Context, req *v1.SystemReq) (res *v1.SystemRes, err error) {
	err = service.Exception().System()
	if err != nil {
		return nil, err
	}
	return &v1.SystemRes{
		Name: "system",
		Age:  1,
	}, nil
}

服务文件:/internal/logic/exception/exception.go:

func (s *sException) Business() error {
	return gerror.New("用户名密码错误")
}

// System 这里我们对 gjson.Decode() 传入错误数据,用来模拟组件内部抛出err
func (s *sException) System() error {
	_, err := gjson.Decode("")
	if err != nil {
		return err
	}
	return nil
}

这个例子模拟了一个完整的接口,从 apicontroller 到 logic,然后我们请求它们,分别从响应信息和控制台两个角度看看它们的结果。

业务异常 Business

curl http://127.0.0.1:8000/business

控制台:

接口响应:

{
	"code": 50,
	"message": "用户名密码错误",
	"data": null
}

系统异常 System

curl http://127.0.0.1:8000/system

控制台:

接口响应:

{
	"code": 50,
	"message": "json Decode failed: EOF",
	"data": null
}

优化方案

此时,我们的接口中有三个不足:

  1. 业务异常不应该抛出堆栈,因为用户名或密码错误的堆栈没有意义;
  2. 系统异常的响应信息中, message 不应该抛出 "json Decode failed: EOF",应该使用 未知错误 或者 系统错误 这类字眼;
  3. 业务异常和系统异常的业务码,也就是响应信息中的 code,不应该都使用 50,应当做以区分。

设计统一 err

在 GoFrame 的工程目录中,有一个包 /internal/packed,我们可以在此处编写我们自己的 err 处理,后面的代码可以做为参考,也可以直接复制过去用:

/internal/packed/err.go:

type pErr struct {
	maps map[int]string
}

var Err = &pErr{
	maps: map[int]string{
		0:     "请求成功",
		10001: "用户名或密码错误",
		10002: "用户不存在",
		99999: "未知错误",
	},
}

// GetMsg 获取code码对应的msg
func (c *pErr) GetMsg(code int) string {
	return c.maps[code]
}

// Skip 抛出一个业务级别的错误,不会打印错误堆栈信息
func (c *pErr) Skip(code int, msg ...string) (err error) {
	var msgStr string
	if len(msg) == 0 {
		msgStr = c.GetMsg(code)
	} else {
		msg = append([]string{c.GetMsg(code)}, msg...)
		msgStr = strings.Join(msg, ", ")
	}
	// NewWithOption 在低版本的 gf 上不存在,请改用 NewOption
	return gerror.NewWithOption(gerror.Option{
		Stack: false,
		Text:  msgStr,
		Code:  gcode.New(code, "", nil),
	})
}

// Sys 抛出一个系统级别的错误,使用code码:99999,会打印错误堆栈信息
// msg 接受string和error类型
// !!! 使用该方法传入error类型时,一定要注意不要泄露系统信息
func (c *pErr) Sys(msg ...interface{}) error {
	var (
		code     = 99999
		msgSlice = []string{
			c.GetMsg(code),
		}
	)

	if len(msg) != 0 {
		for _, v := range msg {
			switch a := v.(type) {
			case error:
				msgSlice = append(msgSlice, a.Error())
			case string:
				msgSlice = append(msgSlice, a)
			}
		}
	}

	msgStr := strings.Join(msgSlice, ", ")
	return gerror.NewCode(gcode.New(code, "", nil), msgStr)
}

统一响应数据中间件

设计统一响应数据的中间件,并且注入到 HTTP 请求流程中:

/internal/logic/middleware/response.go

type sMiddleware struct {
}

func init() {
	service.RegisterMiddleware(New())
}

func New() *sMiddleware {
	return &sMiddleware{}
}

type Response struct {
	Code    int         `json:"code"    dc:"业务码"`
	Message string      `json:"message" dc:"业务码说明"`
	Data    interface{} `json:"data"    dc:"返回的数据"`
}

func (s *sMiddleware) Response(r *ghttp.Request) {
	r.Middleware.Next()

	if r.Response.BufferLength() > 0 {
		return
	}

	// 先过滤掉服务器内部错误
	if r.Response.Status >= http.StatusInternalServerError {
		// 清除掉缓存区,防止服务器信息泄露到客户端
		r.Response.ClearBuffer()
		r.Response.Writeln("服务器打盹了,请稍后再来找他!")
	}

	var (
		res  = r.GetHandlerResponse()
		err  = r.GetError()
		code = gerror.Code(err)
		msg  string
	)

	if err != nil {
		msg = err.Error()
	} else {
		code = gcode.CodeOK
		msg = packed.Err.GetMsg(code.Code())
	}

	r.Response.WriteJson(Response{
		Code:    code.Code(),
		Message: msg,
		Data:    res,
	})
}

/internal/cmd/cmd.go

s.Group("/", func(group *ghttp.RouterGroup) {
	group.Middleware(service.Middleware().Response)
	group.Bind(
		exception.NewV1(),
	)
})

结果

然后在服务文件中调用 packed/err

/internal/logic/exception/exception.go:

func (s *sException) Business() error {
	return packed.Err.Skip(10001)
}

// System 这里我们对 gjson.Decode() 传入错误数据,用来模拟组件内部抛出err
func (s *sException) System() error {
	_, err := gjson.Decode("")
	if err != nil {
		return packed.Err.Sys("可选自定义信息")
	}
	return nil
}

结果展示:

Business
{
	"code": 10001,
	"message": "用户名或密码错误",
	"data": null
}

System
{
	"code": 99999,
	"message": "未知错误, 可选自定义信息",
	"data": null
}

用户名或密码错误的业务异常也不会再抛出堆栈异常了:

尾声

上述的代码例子已经开源在:Github

本博客源码使用的也是这种 err 设计,想要了解更多可以查看:Github/oldme-api

标签:code,return,string,err,接口,GoFrame,错误码,func,msg
From: https://www.cnblogs.com/oldme/p/18070994

相关文章

  • 免费实名认证接口python语言-身份核验-身份证二、三要素
    翔云身份证实名认证接口,实时联网,可快速、精准核验用户所提供信息的真伪,且为更好的服务广大新老用户,现购买翔云身份证实名认证接口即赠送同等的身份证识别接口条数,旨在帮助用户摆脱手动录入的繁琐,提高用户体验,让您的平台远离冒名顶替、欺诈的风险,更有助于您的企业树立起诚信、可靠......
  • 增强版实名认证接口-Java身份证实名认证接口代码-身份认证
    数字化时代,信息安全如同金盾,而身份验证则是这面盾牌的核心环节。每一次登录的背后,都是您对隐私保护的渴望;每一次交易的信任,都源于对身份真实性的确认。现如今,随着网络平台的不断增多,实名认证接口的需求也在不断提升。以下是Java语言调用翔云身份实名认证API的代码:packagecom.te......
  • JMeter接口性能压测之线程组(Thread Group)
    一、添加线程组测试计划右键--添加--线程(用户)--线程组  二、线程组设置延迟创建线程直到需要(Delay Thread creation until needed):此选项和Ramp-up时间(秒)设置配合使用,如果选择此项,则所有线程会在需要的时候启动,即会在Ramp-up时间(秒)时间结束后启动所有线程;如果不选择这......
  • 使用@FeignClient中的fallback属性处理接口调用异常问题
    说明当使用feign远程调用接口是,如果接口返回异常或者超时时,我们可以统一返回异常信息,这样调用者就不用再显式的try-catch处理异常了开启接口fallback处理想要使用fallback处理接口异常须在配置文件开启feign:hystrix:enabled:true定义feign接口在@FeignClient上......
  • RGMII 接口调试
    目录 硬件检查软件检查调试步骤 硬件检查硬件工程师检查原理图和PCB,核查RGMII线路连接是否正确,PHY的TX连接对端RX,PHY的RX连接对端TX,原理图上以引脚序号+引脚名+引脚类型(输入还是输出)逐一核查RGMII接口各个网络,确保接口两侧均为收发对接;不要以引脚名称或网络......
  • 电商商品搬家业务必备京东商品详情数据接口
    对于电商商品搬家业务来说,京东商品详情数据接口是必不可少的工具。通过这个接口,你可以轻松获取京东平台上商品的详细信息,包括商品ID、标题、价格、优惠信息、库存、销量等关键数据。这些数据对于商品搬家业务至关重要,因为它们能帮助你了解商品的市场表现、竞争状况以及潜在的销......
  • 电商商品搬家业务必备淘宝商品详情数据接口
    电商商品搬家业务在进行淘宝商品详情数据迁移时,可以使用淘宝提供的API接口来实现。以下是一些必备的淘宝商品详情数据接口:获取商品详情:使用淘宝商品详情接口,您可以通过商品的ID来获取商品的详细信息,包括商品标题、价格、卖家昵称、卖家ID等。这个接口支持HTTPGET请求,非常方便......
  • 基于FPGA各种视频接口转换的国产化设计
    随着国产化进程推进,现在许多项目需要实现国产化设计,本博主通过器件选型/原理图设计,到视频接口输入,DDR3缓存,再到图像输出,使用者可在此基础实现二次开发,功能实现通过verilog,操作简单,添加功能方便。接口包含lvds/camelink/bt1120/hdmi/sdi等等常用视频接口,也可定制其他接口,带......
  • go语言接口转换 go语言接口详解
    go语言接口转换go语言接口详解 转载文章标签go语言接口转换Go方法名嵌套文章分类Go语言后端开发阅读数38 一、接口1.1接口类型在Go语言中接口(interface)是一种类型,一种抽象的类型。interface是一组method的集合,是duck-typeprogramming的一种体现。接口做的事情......
  • 【C++】string类(介绍、常用接口)
    ......