StreamBox/docs/06-gin-请求绑定与响应.md

165 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 第 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` 补充单元测试,验证错误信息是否友好。