package
7.28.3
Repository: https://github.com/lun-zhang/zlutils.git
Documentation: pkg.go.dev

# README

用bind.Wrap后你就不用再写接口层了!

我认为接口就要像函数一样,只要申明了入参和出参结构就行了!
当我们wiki这样定义一个接口(假设叫POST /info/:u)的请求参数的时候:
body参数:

字段类型必传
bint

query参数:

字段类型必传
qint

uri参数:

字段类型必传
uint

header参数:

字段类型必传
Hint

那么就在代码中定义入参:

struct {
	Body struct {
		B int `json:"b" binding:"required"`
	}
	Uri struct {
		U int `uri:"u" binding:"required"`
	}
	Query struct {
		Q int `form:"q" binding:"required"`
	}
	Header struct {
		H int `header:"h" binding:"required"`
	}
}

wiki上定义响应字段:

字段类型必返
rint

定义出参

struct {
	R int `json:"r"`
}

组装在一起,完成info接口,功能很简单,就是把入参求和:

func Info(ctx context.Context, req struct {
	Body struct {
		B int `json:"b" binding:"required"`
	}
	Uri struct {
		U int `uri:"u" binding:"required"`
	}
	Query struct {
		Q int `form:"q" binding:"required"`
	}
	Header struct {
		H int `header:"h" binding:"required"`
	}
}) (resp struct {
	R int `json:"r"`
}, err error) {
	resp.R = req.Body.B + req.Uri.U + req.Query.Q + req.Header.H
	return
}

最后用bind.Wrap将info接口变成gin.HandlerFunc

router := gin.New()
router.POST("info/:u",bind.Wrap(Info))
router.Run(":11151")

原理

遍历req对象的成员,当发现存在例如Body成员则解析Body参数,Info函数接口等价于如下OriginalInfo写法:

func OriginalInfo(c *gin.Context) {
	var req struct {
		Body struct {
			B int `json:"b" binding:"required"`
		}
		Uri struct {
			U int `uri:"u" binding:"required"`
		}
		Query struct {
			Q int `form:"q" binding:"required"`
		}
		Header struct {
			H int `header:"h" binding:"required"`
		}
	}
	if err := c.ShouldBindJSON(&req.Body); err != nil {
		code.Send(c, nil, code.ClientErrBody.WithError(err))
		//code.Send看起来等价于:
		//c.JSON(http.StatusOK, gin.H{
		//	"ret": 4004,
		//	"msg": "verify body params failed",
		//})
		return
	}
	if err := c.ShouldBindUri(&req.Uri); err != nil {
		code.Send(c, nil, code.ClientErrUri.WithError(err))
		return
	}
	if err := c.ShouldBindQuery(&req.Query); err != nil {
		code.Send(c, nil, code.ClientErrQuery.WithError(err))
		return
	}
	if err := bind.ShouldBindHeader(c.Request.Header, &req.Header); err != nil {
		code.Send(c, nil, code.ClientErrHeader.WithError(err))
		return
	}

	var resp struct {
		R int `json:"r"`
	}

	var err error
	resp.R, err = OriginalInfoBiz(c.Request.Context(), req.Body.B, req.Uri.U, req.Query.Q, req.Header.H)
	code.Send(c, resp, err)
	//此处的code.Send看起来等价于
	//if err != nil {
	//	c.JSON(http.StatusOK, gin.H{
	//		"ret": 5000,
	//		"msg": "server error",
	//	})
	//} else {
	//	c.JSON(http.StatusOK, gin.H{
	//		"ret":  0,
	//		"msg":  "success",
	//		"data": resp,
	//	})
	//}
}

func OriginalInfoBiz(ctx context.Context, b, u, q, h int) (r int, err error) {
	r = b + u + q + h
	return
}

自由!随意定义响应!

无论你的响应结构是用:

{
  "ret":0,
  "msg":"success",
  "data":"业务数据"
}

还是用:

{
  "code":0,
  "result":"ok",
  "xxx":"业务数据"
}

甚至是:

{
  "xxx":???,
  "yyy":???
}

都是你的自由;
无论你的http状态码是用200400500区分,还是全部用200然后用业务code区分,都是你的自由!

示例:

定义请求、响应结构

func UseMySender(ctx context.Context, req struct {
	Query struct {
		Q int `form:"q" binding:"required"` //请求的query结构
	}
	Body struct {
		B int `json:"b" binding:"required"` //请求的body结构
	}
}) (code int, obj interface{}) { //code是http状态码,obj会被转成成json作为响应
	switch req.Body.B {
	case 1:
		return http.StatusInternalServerError, gin.H{
			"ret": 5,
			"msg": "server error when use sender",
		}
	case 2:
		return http.StatusOK, gin.H{
			"ret":  0,
			"msg":  "success when use sender",
			"data": 123,
			"tile": 456, //随意定义
		}
	default:
		return http.StatusOK, 1
	}
}

定义你自己的入参绑定错误处理Sender、结果处理Sender,然后跑起来

router := gin.New()
router.POST("sender", WithSender(
	func(c *gin.Context, reqFieldName string, bindErr error) {
		c.JSON(http.StatusBadRequest, gin.H{
			"field":  reqFieldName,
			"result": bindErr.Error(),
		})
	},
	func(c *gin.Context, code int, obj interface{}) {
		c.JSON(code, obj)
	}).Wrap(UseMySender))
router.Run(":11152")