# 第 6 章 Gin 请求绑定与响应 > 学完本章你将:优雅地绑定与校验请求参数,统一响应格式,处理错误。 ## 6.1 参数绑定 Gin 提供 `ShouldBind` 系列,自动根据 Content-Type 选择绑定器: ```go // JSON: POST {"name":"alice","age":18} type CreateUserReq struct { Name string `json:"name" binding:"required"` Age int `json:"age" binding:"gte=0,lte=120"` } var req CreateUserReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } // Query: GET /users?page=1&size=10 type ListReq struct { Page int `form:"page" binding:"gte=1"` Size int `form:"size" binding:"gte=1,lte=100"` } var q ListReq c.ShouldBindQuery(&q) // Path + Query + Form 混合:ShouldBind 自动识别 c.ShouldBind(&req) // Header token := c.GetHeader("Authorization") // 默认值 page := c.DefaultQuery("page", "1") ``` 校验标签基于 `go-playground/validator`,本项目已间接依赖 `validator/v10`。 ## 6.2 常用校验标签 | 标签 | 说明 | |------|------| | `required` | 必填 | | `gte=0` `lte=120` | 数值范围 | | `oneof=debug release` | 枚举 | | `email` `url` | 格式 | | `min=3` `max=20` | 字符串长度 | | `len=6` | 定长 | ```go type LoginReq struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` } ``` 自定义校验: ```go if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("port", func(fl validator.FieldLevel) bool { p := fl.Field().Int() return p > 0 && p < 65535 }) } ``` ## 6.3 统一响应 本项目 `pkg/response/response.go` 已封装: ```go type Result struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data,omitempty"` } func Success(c *gin.Context, data interface{}) { c.JSON(200, Result{Code: 0, Msg: "success", Data: data}) } func BadRequest(c *gin.Context, msg string) { c.JSON(400, Result{Code: 400, Msg: msg}) } ``` 使用: ```go // handler 中 func (h *PingHandler) Ping(c *gin.Context) { msg := h.svc.Ping() response.Success(c, gin.H{"message": msg}) } // => {"code":0,"msg":"success","data":{"message":"pong"}} func CreateUser(c *gin.Context) { var req CreateUserReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } response.Success(c, gin.H{"id": 1}) } ``` 约定:`Code 0` 为成功,非 0 为业务错误码;HTTP 状态码反映传输层语义(400 参数错误、401 未认证、500 服务错误)。 ## 6.4 错误处理策略 - 参数错误:`400 BadRequest`,返回校验信息 - 业务错误:按需定义错误码,如 `10001 用户不存在` - 系统错误:`500 InternalError`,日志记录详情但不暴露给客户端 ```go func Fail(c *gin.Context, httpCode, code int, msg string) { c.JSON(httpCode, Result{Code: code, Msg: msg}) } ``` 建议结合 `errors.Is/As` 判断错误类型,在 service 层返回 `error`,handler 层映射为响应。 ## 6.5 文件上传与下载 ```go // 上传 r.POST("/upload", func(c *gin.Context) { file, _ := c.FormFile("file") c.SaveUploadedFile(file, "./uploads/"+file.Filename) response.Success(c, gin.H{"filename": file.Filename}) }) // 下载/返回文件 r.GET("/download/:name", func(c *gin.Context) { c.File("./uploads/" + c.Param("name")) }) ``` ## 6.6 完整示例 ```go type CreateBookReq struct { Title string `json:"title" binding:"required"` Author string `json:"author" binding:"required"` Price int `json:"price" binding:"gte=0"` } func CreateBook(c *gin.Context) { var req CreateBookReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } // 调用 service... response.Success(c, gin.H{"title": req.Title}) } ``` ## 6.7 动手练习 1. 新增 `POST /api/v1/users`,用 `ShouldBindJSON` + `required/email` 校验。 2. 实现 `GET /api/v1/users?page=&size=` 分页查询,用 `ShouldBindQuery`。 3. 为 `BadRequest` 补充单元测试,验证错误信息是否友好。