其他配置 原创
1、响应处理
在应用程序中,与客户端对接的通常是服务端的接口,那么客户端是如何知道这一次的接口调用的结果是怎样的呢?一般来说,主要是通过对返回的 HTTP 状态码和接口返回的响应结果进行判断的,而判断的依据则是事先按规范定义好响应结果。
1.1 类型转换
在 tools 目录中新建 convert.go 文件,写入如下代码:
go
package tools
import "strconv"
// 处理接口返回的响应
type StrTo string
// 类型转换
// 字符串
func (s StrTo) String() string {
return string(s)
}
// int
func (s StrTo) Int() (int, error) {
i, err := strconv.Atoi(s.String())
return i, err
}
func (s StrTo) MustInt() int {
i, _ := s.Int()
return i
}
// UInt32
func (s StrTo) UInt32() (uint32, error) {
i, err := strconv.Atoi(s.String())
return uint32(i), err
}
func (s StrTo) MustUInt32() uint32 {
i, _ := s.UInt32()
return i
}
1.2、分页处理
在pkg/app
目录中新建pagination.go
文件,代码如下:
go
package app
import (
"github.com/gin-gonic/gin"
"github.com/joker-bai/kubemana/global"
"github.com/joker-bai/kubemana/pkg/tools"
)
// 分页处理
func GetPage(ctx *gin.Context) int {
page := tools.StrTo(ctx.Query("page")).MustInt()
if page <= 0 {
return 1
}
return page
}
func GetPageSize(ctx *gin.Context) int {
pageSize := tools.StrTo(ctx.Query("page_size")).MustInt()
if pageSize <= 0 {
return global.DefaultPageSize
}
if pageSize > global.MaxPageSize {
return global.MaxPageSize
}
return pageSize
}
func GetPageOffSet(page, pageSize int) int {
result := 0
if page > 0 {
result = (page - 1) * pageSize
}
return result
}
然后在 global
目录中创建 pagination.go
文件,写入如下全局参数:
go
package global
var (
// 默认页面大小
DefaultPageSize = 10
// 最大页面
MaxPageSize = 100
)
1.3、响应处理
在 pkg/app/
中创建 response.go
文件,写入以下内容:
go
package app
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/joker-bai/kubemana/pkg/errorcode"
)
// 响应处理
type Response struct {
Ctx *gin.Context
}
type Pager struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalRows int `json:"total_rows"`
}
// 初始化响应
func NewResponse(ctx *gin.Context) *Response {
return &Response{
Ctx: ctx,
}
}
func (r *Response) ToResponse(data interface{}) {
if data == nil {
data = gin.H{}
}
r.Ctx.JSON(http.StatusOK, data)
}
func (r *Response) ToResponseList(list interface{}, totalRows int) {
r.Ctx.JSON(
http.StatusOK, gin.H{
"items": list,
"total": totalRows,
},
)
}
func (r *Response) ToErrorResponse(err *errorcode.Error) {
response := gin.H{"code": err.Code(), "msg": err.Msg()}
details := err.Details()
if len(details) > 0 {
response["details"] = details
}
r.Ctx.JSON(err.Code(), response)
}
2、参数校验
开发对应的业务模块时,第一步要考虑的问题就是如何进行入参校验。我们需要将整个项目,甚至整个团队的组件给定下来,形成一个通用规范。
github.com/thedevsaddam/govalidator2.1、安装依赖
go
$ go get github.com/thedevsaddam/govalidator
2.2、定义验证器
在pkg/app
中添加valid.go
文件,写入以下内容:
go
package app
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/thedevsaddam/govalidator"
)
// ValidatorFunc 验证函数类型
type ValidatorFunc func(interface{}, *gin.Context) map[string][]string
// Validate 控制器里调用示例:
//
// if ok := requests.Validate(c, &requests.UserSaveRequest{}, requests.UserSave); !ok {
// return
// }
func Validate(c *gin.Context, obj interface{}, handler ValidatorFunc) bool {
// 1. 解析请求,支持 JSON 数据、表单请求和 URL Query
if err := c.ShouldBind(obj); err != nil {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "请求解析错误,请确认请求格式是否正确。上传文件请使用 multipart 标头,参数请使用 JSON 格式。",
"error": err.Error(),
})
fmt.Println(err.Error())
return false
}
// 2. 表单验证
errs := handler(obj, c)
// 3. 判断验证是否通过
if len(errs) > 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "请求验证不通过,具体请查看 errors",
"errors": errs,
})
return false
}
return true
}
// ValidateOptions 校验配置选项
func ValidateOptions(data interface{}, rules govalidator.MapData, messages govalidator.MapData) map[string][]string {
// 配置选项
opts := govalidator.Options{
Data: data,
Rules: rules,
TagIdentifier: "valid", // 模型中的 Struct 标签标识符
Messages: messages,
}
// 开始验证
return govalidator.New(opts).ValidateStruct()
}
我们需要在结构体中增加 valid
的声明才会进行校验,如下:
go
type TestRequest struct {
Phone string `json:"phone,omitempty" valid:"phone"`
}
2.3、测试一下
首先,我们在 internal/app/requests
目录中新建 hello.go
,写入一下内容:
go
package requests
import (
"github.com/gin-gonic/gin"
"github.com/joker-bai/kubemana/pkg/app"
"github.com/thedevsaddam/govalidator"
)
type HelloRequest struct {
Name string `json:"name,omitempty" valid:"name" form:"name"`
}
func ValidHelloRequest(data interface{}, ctx *gin.Context) map[string][]string {
// 自定义验证规则
rules := govalidator.MapData{
"name": []string{"required"},
}
// 自定义验证出错时的提示
messages := govalidator.MapData{
"name": []string{
"required: 名字为必填项,参数名称为name",
},
}
// 进行校验
return app.ValidateOptions(data, rules, messages)
}
我们在这个文件中定义要校验的参数规则以及错误提示。
常用的标签如下:
标签 | 含义 |
---|---|
required | 必填项 |
gt | 大于 |
gte | 大于或等于 |
le | 小于 |
lte | 小于或等于 |
min | 最小值 |
max | 最大值 |
len | 长度要求 |
然后修改 internal/app/controllers/api/v1
中的helloworld.go
文件,内容如下:
go
package v1
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/joker-bai/kubemana/internal/app/requests"
"github.com/joker-bai/kubemana/pkg/app"
)
type HelloWorldController struct{}
// @Summary HelloWorld
// @Produce json
// @Tags HelloWorld
// @Success 200
// @Router /api/ [get]
func (s *HelloWorldController) Get(ctx *gin.Context) {
param := requests.HelloRequest{}
if ok := app.Validate(ctx, ¶m, requests.ValidHelloRequest); !ok {
return
}
ctx.JSON(
http.StatusOK, gin.H{
"Data": fmt.Sprintf("Hello %s", param.Name),
},
)
}
最后,我们使用 go run main.go
启动项目,使用 apifox
进行测试。
先不给参数,看返回结果如下:
然后再给一个 name
参数,看返回结果,如下:
3、代码版本
本节开发完成后,给代码打标记,如下:
go
$ git add .
$ git commit -m "响应处理和参数校验配置"