4.1 KiB
4.1 KiB
第 6 章 Gin 请求绑定与响应
学完本章你将:优雅地绑定与校验请求参数,统一响应格式,处理错误。
6.1 参数绑定
Gin 提供 ShouldBind 系列,自动根据 Content-Type 选择绑定器:
// 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 |
定长 |
type LoginReq struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
}
自定义校验:
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 已封装:
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})
}
使用:
// 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,日志记录详情但不暴露给客户端
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 文件上传与下载
// 上传
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 完整示例
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 动手练习
- 新增
POST /api/v1/users,用ShouldBindJSON+required/email校验。 - 实现
GET /api/v1/users?page=&size=分页查询,用ShouldBindQuery。 - 为
BadRequest补充单元测试,验证错误信息是否友好。