Skip to content

其他配置 原创

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/govalidator

2.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, &param, requests.ValidHelloRequest); !ok {
		return
	}
	ctx.JSON(
		http.StatusOK, gin.H{
			"Data": fmt.Sprintf("Hello %s", param.Name),
		},
	)
}

最后,我们使用 go run main.go 启动项目,使用 apifox 进行测试。

先不给参数,看返回结果如下:

875828fb8ca415d4eafec1466195f243 MD5

然后再给一个 name 参数,看返回结果,如下:

c851ef2717d953493bed923f59c47656 MD5

3、代码版本

本节开发完成后,给代码打标记,如下:

go
$ git add .
$ git commit -m "响应处理和参数校验配置"
最近更新