项目地址:
# 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
示例代码:
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(¶m); 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 }
常见校验标签:
标签名 | 描述 | 示例 |
---|---|---|
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” |
邮箱地址验证 | validate:”email” | |
unique | 保证唯一性(用于 map 或 slice) | validate:”unique” |
文档更新时间: 2024-03-24 15:25 作者:lee