gin框架实践连载五 | 搭建一个案例API2

1,818 阅读4分钟

引言

  • 今天我们继续完善上一章节
  • 使用github.com/go-playground/validator/v10 做数据校验,gin自带的非常方便
  • 将业务逻辑抽离至server层(服务层)
  • 增加接口签名校验(jwt自定义中间件)
  • github代码地址

1、加校验

type userId struct {
	ID int `uri:"id" binding:"required"`
}

type user struct {
	Name      string `json:"name" xml:"name" form:"name" binding:"required"`
	CreatedBy string `json:"created_by" xml:"created_by" form:"created_by" binding:"lowercase"`
}

func GetUser(c *gin.Context) {
	user := new(userId)
	if err := c.ShouldBindUri(user); err != nil {
		tool.JSONP(c, 40001, err.Error(), nil)
		return
	}
	res, err := models.GetUser(user.ID)
	if err != nil {
		tool.JSONP(c, 40001, "暂无数据", nil)
		return
	}
	tool.JSONP(c, 0, "查询成功", res)
}

校验提示中文化

在app/request新增 zh.go

package request

import (
	"errors"

	"github.com/gin-gonic/gin/binding"
	zh "github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"
)

var v *validator.Validate
var trans ut.Translator

func init() {
	zh_ch := zh.New()
	uni := ut.New(zh_ch)
	trans, _ = uni.GetTranslator("zh")

	v, ok := binding.Validator.Engine().(*validator.Validate)
	if ok {
		// 验证器注册翻译器
		zh_translations.RegisterDefaultTranslations(v, trans)
	}
}

func Translate(errs validator.ValidationErrors) string {
	var errList []string
	for _, e := range errs {
		// can translate each error one at a time.
		errList = append(errList, e.Translate(trans))

	}
	return errList[0] //strings.Join(errList, "|")
}

func GetError(err error) string {
	switch err.(type) {
	case validator.ValidationErrors:
		return Translate(err.(validator.ValidationErrors))
	default:
		return errors.New("unknown error.").Error()
	}

}

自定义错误信息

有些github.com/go-playground/validator定义的校验器没有翻译(我们可以自己定义)

// 自定义错误信息
  v.RegisterTranslation("lowercase", trans, func(ut ut.Translator) error {
      return ut.Add("lowercase", "{0}必须为小写字母", true) // see universal-translator for details
  }, func(ut ut.Translator, fe validator.FieldError) string {
      t, _ := ut.T("lowercase", fe.Field())
      return t
  })

自定义验证器

良好的接口扩展,可以自己定义某些校验方法

v.RegisterValidation("checkMobile", checkMobile)

func checkMobile(fl validator.FieldLevel) bool {
	mobile := fl.Field().String()
	if len(mobile) != 11 {
		return false
	}
	return true
}

2、抽离服务层

在app/services下新增文件、我们在服务层定义了接口,定义了标准,保证了我们如果替换服务的话,不会影响到控制器层

package services

import (
	"go-api/app/models"
	"go-api/tool"
)

type UserContract interface {
	UserGetsContract    //GET users
	UserGetContract     //GET user
	UserGetByIDContract //GETByID user
	UserAddContract     //Add user
	UserExistContract   //exist user
	UserDeleteContract  //delete user
	UserEditContract    //edit user
}

type UserGetsContract interface {
	GetUserList(maps interface{}, offset int, limit int) tool.M
}

type UserGetContract interface {
	GetUser(maps interface{}) tool.M
}

type UserGetByIDContract interface {
	GetUserByID(id int) tool.M
}

type UserAddContract interface {
	AddUser(maps map[string]interface{}, data map[string]interface{}) tool.M
}

type UserExistContract interface {
	ExistUser(maps map[string]interface{}) bool
}

type UserDeleteContract interface {
	DeleteUser(maps map[string]interface{}) tool.M
}

type UserEditContract interface {
	EditUser(id int, data interface{}) tool.M
}

type UserService struct {
}

func (T UserService) GetUserList(maps interface{}, offet int, limit int) tool.M {
	data := make(map[string]interface{})
	data["lists"] = models.GetUsers(offet, limit, maps)
	data["total"] = models.GetUserTotal(maps)
	return tool.DataReturn(true, "查询成功", data)
}

func (T UserService) GetUser(maps interface{}) tool.M {
	data, err := models.GetUser(maps)
	if err != nil {
		return tool.DataReturn(false, "暂无数据", err.Error())
	}
	return tool.DataReturn(true, "查询成功", data)
}

func (T UserService) GetUserByID(id int) tool.M {
	data, err := models.GetUserByID(id)
	if err != nil {
		return tool.DataReturn(false, "暂无数据", err.Error())
	}
	return tool.DataReturn(true, "查询成功", data)
}

func (T UserService) AddUser(maps map[string]interface{}, data map[string]interface{}) tool.M {
	//先通过maps查询数据是否存在
	if T.ExistUser(maps) {
		return tool.DataReturn(false, "该名称已存在", nil)
	} else {
		if isbool := models.AddUser(data); !isbool {
			return tool.DataReturn(false, "创建失败", nil)
		}
		return tool.DataReturn(true, "创建成功", nil)
	}
}

func (T UserService) ExistUser(maps map[string]interface{}) bool {

	return models.ExistUserByMaps(maps)
}

func (T UserService) DeleteUser(maps map[string]interface{}) tool.M {
	if T.ExistUser(maps) {
		isbool, err := models.DeleteUser(maps)
		if err != nil {
			return tool.DataReturn(false, "删除失败", err.Error())
		}
		return tool.DataReturn(isbool, "删除成功", nil)
	} else {
		return tool.DataReturn(false, "记录不存在", nil)
	}
}

func (T UserService) EditUser(id int, data interface{}) tool.M {
	if models.ExistTagByID(id) {
		_, err := models.EditUser(id, data)
		if err != nil {
			return tool.DataReturn(false, "编辑失败", err.Error())
		}
		return tool.DataReturn(true, "编辑成功", nil)
	} else {
		return tool.DataReturn(false, "ID记录不存在", nil)
	}
}


2.1 新增统一服务层返回格式

type M map[string]interface{}

type RetData struct{
	Status bool `json:"status"`
	Msg  string `json:"msg"`
	Data  interface{} `json:"data"`
}


func DataReturn(status bool,msg string,data interface{}) M{
	result :=M{
		"status" : status,
		"msg" : msg,
		"data" : data,
	}
	return result
}

func (m M) GetStatus() bool{
	return m["status"].(bool)
}

func (m M) GetMsg() string{
	return m["msg"].(string)
}

2.2 改造接口控制器层

var UserService services.UserContract

func init() {
	UserService = &services.UserService{}
}

func GetUser(c *gin.Context) {
	user := new(userId)
	if err := c.ShouldBindUri(user); err != nil {
		tool.JSONP(c, 40001, request.GetError(err), nil)
		return
	}

	ret := UserService.GetUserByID(user.ID)

	if !ret.GetStatus() {
		tool.JSONP(c, 40001, ret.GetMsg(), ret["data"])
		return
	}
	tool.JSONP(c, 0, ret.GetMsg(), ret["data"])
}

截止到目前为止我们的接口分层(控制器->参数校验->服务层->model层)、接下来为接口加上接口签名校验

3、接口签名校验

我们采用jwt方式来做接口校验,自定义一个中间件

3.1、在tool下新建jwt.go工具包

package tool

import (
	"time"

	jwt "github.com/dgrijalva/jwt-go"

	"go-api/config"
)

var (
	JwtSecret     = []byte(config.AppSetting.JwtSecret)
	JwtExpiresAt  = config.AppSetting.JwtExpiresAt
	SigningMethod = config.AppSetting.SigningMethod
)

type Claims struct {
	Appkey    string `json:"app_key"`
	AppSecret string `json:"app_secret"`
	jwt.StandardClaims
}

func GenerateToken(appkey, app_secret string) (string, error) {
	nowTime := time.Now()
	expireTime := nowTime.Add(JwtExpiresAt)

	claims := Claims{
		appkey,     //可以考虑加密
		app_secret, //可以考虑加密
		jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(),
			Issuer:    "go-api",
		},
	}

	tokenClaims := jwt.NewWithClaims(jwt.GetSigningMethod(SigningMethod), claims)
	token, err := tokenClaims.SignedString(JwtSecret)

	return token, err
}

func ParseToken(token string) (*Claims, error) {
	tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return JwtSecret, nil
	})

	if tokenClaims != nil {
		if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
			return claims, nil
		}
	}

	return nil, err
}

3.2、自定义中间件

在app/middleware


package middleware

import (
	"net/http"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"

	"go-api/tool"
)

func JWT() gin.HandlerFunc {
	return func(c *gin.Context) {
		var (
			code  = tool.SUCCESS
			token string
		)
		//支持header和get方式
		if s, ok := c.GetQuery("token"); ok {
			token = s
		} else {
			token = c.GetHeader("token")
		}
		if token == "" {
			code = tool.ERROR_AUTH_CHECK_TOKEN_EMPTY
		} else {
			_, err := tool.ParseToken(token)
			if err != nil {
				switch err.(*jwt.ValidationError).Errors {
				case jwt.ValidationErrorExpired:
					code = tool.ERROR_AUTH_CHECK_TOKEN_TIMEOUT
				default:
					code = tool.ERROR_AUTH_CHECK_TOKEN_FAIL
				}
			}
		}

		if code != tool.SUCCESS {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": code,
				"msg":  tool.GetMsg(code),
				"data": nil,
			})

			c.Abort()
			return
		}

		c.Next()
	}
}

···


### 3.2 路由组应用jwt中间件

```golang
package routes

import (
	"go-api/app/controller"
	"go-api/app/middleware"
	"go-api/tool"

	"github.com/gin-gonic/gin"
)

func apiRoute(r *gin.Engine) {
	r.GET("/auth/:key", func(c *gin.Context) {
		appkey := c.Param("key")
		token, err := tool.GenerateToken(appkey, "12312323")
		if err != nil {
			tool.JSONP(c, tool.ERROR_AUTH_TOKEN, "", nil)
			return
		}
		tool.JSONP(c, 0, "成功", token)
	})

	apiv1 := r.Group("/api/v1", middleware.JWT())
	{
		//获取用户列表
		apiv1.GET("/users", controller.GetUsers)
		//获取指定用户
		apiv1.GET("/user/:id", controller.GetUser)
		//新增用户
		apiv1.POST("/users", controller.AddUser)
		//更新指定用户
		apiv1.PUT("/users/:id", controller.EditUser)
		//删除指定用户
		apiv1.DELETE("/users/:id", controller.DeleteUser)
	}
}

结尾,编译代码去校验或者单元测试

4、系列文章