引言
- 今天我们继续完善上一章节
- 使用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)
}
}
结尾,编译代码去校验或者单元测试