1. 项目地址:

    # gin 使用文档地址
    https://www.kancloud.cn/shuangdeyu/gin_book/949439
    # gin 参数验证器文档地址
    http://liuqh.icu/2021/05/30/go/gin/11-validate/
    # 详细校验规则参考地址
    https://github.com/go-playground/validator
  2. 示例代码:

     package main
    
     import (
         "bytes"
         "context"
         "fmt"
         "github.com/gin-gonic/gin"
         "github.com/go-playground/locales/zh"
         ut "github.com/go-playground/universal-translator"
         "github.com/go-playground/validator/v10"
         zhs "github.com/go-playground/validator/v10/translations/zh"
         "github.com/sirupsen/logrus"
         "io"
         "net/http"
         "os"
         "os/signal"
         "reflect"
         "regexp"
         "strings"
         "syscall"
         "time"
     )
    
     const (
         Address = "localhost:8080"
         RunMode = "release" // debug / release
     )
    
     type validRequest struct {
         Name      string `json:"name" form:"name" validate:"required" comment:"名称"`           // 必填
         Email     string `json:"email" form:"email" validate:"required,email" comment:"电子邮件"` // 必填,格式必须为 email
         Age       int    `json:"age" form:"age" validate:"gte=18,lte=30" comment:"年龄"`        // 年龄范围
         Status    int    `json:"status" form:"status" validate:"oneof=1 2" comment:"状态"`      // 必须介于 1,2 之间
         Telephone string `json:"telephone" form:"telephone" validate:"tel" comment:"手机号"`     // 使用自定义验证规则
     }
    
     func init() {
         logrus.SetReportCaller(true) // 输出调用 logrus 的文件、方法名、行号
         logrus.SetFormatter(&logrus.JSONFormatter{
             TimestampFormat: "2006-01-02 15:03:04", // 设置日志输出时间格式
         })
         if RunMode == "debug" {
             logrus.SetOutput(os.Stdout)
         } else {
             file, err := os.OpenFile("./log/info.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
             if err == nil {
                 logrus.SetOutput(file)
             } else {
                 logrus.Info("Failed to log to file, using default stderr")
             }
         }
         logrus.SetLevel(logrus.TraceLevel)
     }
    
     func main() {
         quit := make(chan os.Signal, 1) // 退出信号
         signal.Notify(quit, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM)
    
         router := gin.New()
    
         if RunMode == "debug" {
             gin.DefaultWriter = io.MultiWriter(os.Stdout)
             router.Use(gin.Recovery())
             router.Use(gin.Logger())
         } else {
             infoLogFile, err := os.OpenFile("./log/info.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
             if err != nil {
                 logrus.Panic(err)
             }
             gin.DefaultWriter = io.MultiWriter(infoLogFile)
             panicLogFile, err := os.OpenFile("./log/panic.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
             if err != nil {
                 logrus.Panic(err)
             }
             router.Use(gin.RecoveryWithWriter(panicLogFile)) // 将 panic 记录到日志
         }
         // 使用自定义中间件,记录访问日志
         router.Use(func(ctx *gin.Context) {
             params, _ := ctx.GetRawData()
             if len(params) > 0 {
                 ctx.Request.Body = io.NopCloser(bytes.NewBuffer(params))
             }
             logrus.WithFields(logrus.Fields{
                 "【host】":       ctx.Request.Host,
                 "【url】":        ctx.Request.URL,
                 "【header】":     ctx.Request.Header,
                 "【method】":     ctx.Request.Method,
                 "【remoteAddr】": ctx.Request.RemoteAddr,
                 "【proto】":      ctx.Request.Proto,
                 "【params】":     string(params),
             }).Info("info message")
             ctx.Next()
         })
    
         h5 := router.Group("/h5")
         {
             // 访问静态页面
             h5.Static("/static", "./dist/static")
             // 访问 vue 前端页面
             router.LoadHTMLFiles("./dist/index.html")
             h5.GET("/", func(context *gin.Context) {
                 context.HTML(http.StatusOK, "index.html", nil)
             })
         }
    
         api := router.Group("/api")
         {
             api.POST("/valid", func(ctx *gin.Context) {
                 var param validRequest
                 if err := ctx.ShouldBind(&param); err != nil {
                     fail(ctx, 401, "bind error.", err)
                     return
                 }
                 if errStr, err := validateParam(param); err != nil {
                     fail(ctx, 402, errStr, err)
                     return
                 }
                 success(ctx, 200, nil)
                 return
             })
         }
    
         svc := &http.Server{
             Addr:    Address,
             Handler: router,
         }
    
         // 优雅的停止服务
         go shutdown(quit, svc)
    
         logrus.Infof("server running at: %s", Address)
         if err := svc.ListenAndServe(); err != nil && err != http.ErrServerClosed {
             panic(err)
         }
     }
    
     func shutdown(quit chan os.Signal, svc *http.Server) {
         <-quit
         ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
         defer cancel()
         logrus.Info("server shutdown...")
         if err := svc.Shutdown(ctx); err != nil {
             logrus.Println("shutdown error:", err)
         }
         os.Exit(0)
     }
    
     func success(ctx *gin.Context, code int, data interface{}) {
         // 记录日志
         logrus.WithFields(logrus.Fields{
             "code":    code,
             "message": "success",
             "data":    data,
             "err":     nil,
         }).Info("【请求成功日志】")
         ctx.JSON(http.StatusOK, map[string]interface{}{
             "code":    code,
             "message": "success",
             "data":    data,
             "err":     nil,
         })
     }
    
     func fail(ctx *gin.Context, code int, message string, err error) {
         // 记录日志
         logrus.WithFields(logrus.Fields{
             "code":    code,
             "message": message,
             "data":    nil,
             "err":     err.Error(),
         }).Error("【请求失败日志】")
         ctx.JSON(http.StatusOK, map[string]interface{}{
             "code":    code,
             "message": message,
             "data":    nil,
             "err":     err.Error(),
         })
     }
    
     func validateParam(param interface{}) (string, error) {
         chinese := zh.New()
         validate := validator.New()
         uni := ut.New(chinese, chinese)
         trans, _ := uni.GetTranslator("zh")
    
         if err := zhs.RegisterDefaultTranslations(validate, trans); err != nil {
             return "", err
         }
         errStrArr := make([]string, 0)
    
         // 自定义验证规则
         _ = validate.RegisterValidation("tel", func(fl validator.FieldLevel) bool {
             fieldStr := fl.Field().String()
             regular := "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"
             reg := regexp.MustCompile(regular)
             isValid := reg.MatchString(fieldStr)
             return isValid
         })
         // 翻译自定义验证规则
         _ = validate.RegisterTranslation("tel", trans, func(ut ut.Translator) error {
             return ut.Add("tel", "{0} 不符合规则", true)
         }, func(ut ut.Translator, fe validator.FieldError) string {
             t, _ := ut.T("tel", fe.Field(), fe.Value().(string))
             return t
         })
    
         // 获取 comment 标签,做为翻译的字段,如:把 ”name不能为空“ 翻译为 ”【姓名】不能为空“
         validate.RegisterTagNameFunc(func(field reflect.StructField) string {
             tag := field.Tag.Get("comment")
             return fmt.Sprintf("【%s】", tag)
         })
    
         err := validate.Struct(param)
         if err != nil {
             if errs, ok := err.(validator.ValidationErrors); ok {
                 for _, err := range errs {
                     errStrArr = append(errStrArr, err.Translate(trans))
                 }
             }
             errStr := strings.Join(errStrArr, ",")
             return errStr, err
         }
    
         return "", nil
     }
  3. 常见校验标签:

标签名 描述 示例
required 必填项 validate:”required”
min 字符最小长度 validate:”min=1”
max 字符最大长度 validate:”max=2”
gte 大于或等于 validate:”gte=0”
lte 小于或等于 validate:”lte=10”
gt 大于 validate:”gt=0”
lt 小于 validate:”lt=10”
oneof 必须是指定值中的一个 validate:”oneof=1 2 3”
email 邮箱地址验证 validate:”email”
unique 保证唯一性(用于 map 或 slice) validate:”unique”
文档更新时间: 2024-03-24 15:25   作者:lee