下载工作台
Gin Web 开发

请求校验与统一响应

试读上半部分 · 解锁后可读全文

第 7 章 · 请求校验与统一响应

本章目标:使用 go-playground/validator 与 Gin binding 做请求校验;定义统一 JSON envelope {code, message, data};规范化数字错误码与 HTTP 状态映射,40301 无权限等与 flask-web ch07fastapi-web ch12 对齐;重构 ch05/ch06 handler 全面使用 response 包。

学时建议:3~4 小时(含 1.5 小时跟练)

前置:完成 gin-web ch06


7.1 场景说明:一致的前端契约

user-demo SPA 与 api-demo 前端共用 axios 拦截器,期望所有 API 返回一致结构:

{
  "code": 0,
  "message": "success",
  "data": { "items": [], "total": 0 }
}

错误示例:

{
  "code": 42201,
  "message": "slug is required",
  "data": null
}

无权限(与 flask-web 对齐):

{
  "code": 40301,
  "message": "无权限",
  "data": null
}
internal/
├── response/
│   ├── envelope.go
│   └── codes.go
└── handler/
    └── bind.go

对照章节

主题flask-web ch07fastapi-web ch12gin-web ch07
成功码0自定义0
无权限403014030140301
校验失败4220142242201
校验引擎marshmallowPydanticbinding + validator

7.2 逐步操作表

步骤操作验证
1创建 response/envelope.gocodes.go包编译
2创建 handler/bind.go 统一绑定校验错误友好
3重构 ProductHandler 使用 response成功含 code=0
4重构 AuthHandler / middleware40301 一致
5注册自定义 validator slug非法 slug 42201
6curl 成功列表data 含 items
7curl 非法 body42201
8user 写商品40301

7.3 Envelope 定义

// internal/response/envelope.go
package response

import (
    "net/http"

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

type Envelope struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    any    `json:"data"`
}

func OK(c *gin.Context, data any) {
    c.JSON(http.StatusOK, Envelope{Code: CodeOK, Message: "success", Data: data})
}

func Created(c *gin.Context, data any) {
    c.JSON(http.StatusCreated, Envelope{Code: CodeCreated, Message: "created", Data: data})
}

func Fail(c *gin.Context, httpStatus, code int, msg string) {
    c.JSON(httpStatus, Envelope{Code: code, Message: msg, Data: nil})
}

func NoContent(c *gin.Context) {
    c.Status(http.StatusNoContent)
}

7.4 错误码表(对齐 flask-web ch07)

// internal/response/codes.go
package response

const (
    CodeOK        = 0
    CodeCreated   = 0      // 创建成功仍用 0,HTTP 201 区分
    CodeUnauthorized = 40101
    CodeForbidden    = 40301  // 与 flask-web 一致
    CodeNotFound     = 40400
    CodeConflict     = 40901
    CodeValidation   = 42201
    CodeInternal     = 50000
)
codeHTTP场景flask-webapi-go-demo
0200/201成功00
40101401未登录/无效 token4010140101
40301403无权限4030140301
40400404slug 不存在4040040400
40901409slug 重复40901
42201400/422参数校验失败4220142201
50000500服务器错误5000050000
Gin 校验失败常用 HTTP 400;为与 flask 数字码一致,body 中 code 仍为 42201

7.5 统一绑定 helper

// internal/handler/bind.go
package handler

import (
    "fmt"
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
    "example.com/api-go-demo/internal/response"
)

func BindJSON(c *gin.Context, dst any) bool {
    if err := c.ShouldBindJSON(dst); err != nil {
        msg := formatBindError(err)
        response.Fail(c, http.StatusBadRequest, response.CodeValidation, msg)
        return false
    }
    return true
}

func BindQuery(c *gin.Context, dst any) bool {
    if err := c.ShouldBindQuery(dst); err != nil {
        msg := formatBindError(err)
        response.Fail(c, http.StatusBadRequest, response.CodeValidation, msg)
        return false
    }
    return true
}

func formatBindError(err error) string {
    if ve, ok := err.(validator.ValidationErrors); ok && len(ve) > 0 {
        fe := ve[0]
        field := strings.ToLower(fe.Field())
        switch fe.Tag() {
        case "required":
            return fmt.Sprintf("%s is required", field)
        case "email":
            return "invalid email format"
        case "min":
            return fmt.Sprintf("%s is too short", field)
        case "max":
            return fmt.Sprintf("%s is too long", field)
        case "gte":
            return fmt.Sprintf("%s must be >= %s", field, fe.Param())
        case "slug":
            return "slug must match [a-z0-9-]+"
        default:
            return fe.Error()
        }
    }
    return err.Error()
}

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。