HTTP方法 #

一、HTTP方法概述 #

1.1 常用HTTP方法 #

方法 说明 幂等性 安全性
GET 获取资源
POST 创建资源
PUT 更新资源(完整替换)
PATCH 更新资源(部分更新)
DELETE 删除资源
HEAD 获取响应头
OPTIONS 获取支持的方法

1.2 幂等性与安全性 #

text
幂等性:多次执行相同请求,结果相同
安全性:请求不会修改服务器资源

GET     ✓幂等  ✓安全
POST    ✗幂等  ✗安全
PUT     ✓幂等  ✗安全
PATCH   ✗幂等  ✗安全
DELETE  ✓幂等  ✗安全

二、GET方法 #

2.1 基本用法 #

go
func main() {
    r := gin.Default()
    
    // 获取用户列表
    r.GET("/users", func(c *gin.Context) {
        users := []User{
            {ID: 1, Name: "Alice"},
            {ID: 2, Name: "Bob"},
        }
        c.JSON(200, users)
    })
    
    // 获取单个用户
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        user := User{ID: 1, Name: "Alice"}
        c.JSON(200, user)
    })
    
    r.Run()
}

2.2 带查询参数 #

go
type QueryParams struct {
    Page    int    `form:"page" binding:"min=1"`
    Size    int    `form:"size" binding:"min=1,max=100"`
    Keyword string `form:"keyword"`
    Status  string `form:"status"`
}

func main() {
    r := gin.Default()
    
    r.GET("/users", func(c *gin.Context) {
        var params QueryParams
        if err := c.ShouldBindQuery(&params); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 默认值处理
        if params.Page == 0 {
            params.Page = 1
        }
        if params.Size == 0 {
            params.Size = 10
        }
        
        users := getUsers(params)
        c.JSON(200, gin.H{
            "data": users,
            "page": params.Page,
            "size": params.Size,
        })
    })
    
    r.Run()
}

2.3 条件查询 #

go
func main() {
    r := gin.Default()
    
    r.GET("/users", func(c *gin.Context) {
        db := getDB()
        query := db.Model(&User{})
        
        // 条件筛选
        if name := c.Query("name"); name != "" {
            query = query.Where("name LIKE ?", "%"+name+"%")
        }
        
        if status := c.Query("status"); status != "" {
            query = query.Where("status = ?", status)
        }
        
        // 日期范围
        if startDate := c.Query("start_date"); startDate != "" {
            query = query.Where("created_at >= ?", startDate)
        }
        
        if endDate := c.Query("end_date"); endDate != "" {
            query = query.Where("created_at <= ?", endDate)
        }
        
        var users []User
        query.Find(&users)
        
        c.JSON(200, users)
    })
    
    r.Run()
}

三、POST方法 #

3.1 创建资源 #

go
type CreateUserRequest struct {
    Name     string `json:"name" binding:"required,min=2,max=50"`
    Email    string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required,min=6"`
    Age      int    `json:"age" binding:"gte=0,lte=150"`
}

func main() {
    r := gin.Default()
    
    r.POST("/users", func(c *gin.Context) {
        var req CreateUserRequest
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 创建用户
        user := User{
            Name:  req.Name,
            Email: req.Email,
            Age:   req.Age,
        }
        
        if err := createUser(&user); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(201, user)
    })
    
    r.Run()
}

3.2 表单提交 #

go
func main() {
    r := gin.Default()
    
    r.POST("/login", func(c *gin.Context) {
        username := c.PostForm("username")
        password := c.PostForm("password")
        
        if username == "" || password == "" {
            c.JSON(400, gin.H{"error": "用户名和密码不能为空"})
            return
        }
        
        // 验证登录
        user, err := authenticate(username, password)
        if err != nil {
            c.JSON(401, gin.H{"error": "用户名或密码错误"})
            return
        }
        
        // 生成Token
        token := generateToken(user)
        
        c.JSON(200, gin.H{
            "token": token,
            "user":  user,
        })
    })
    
    r.Run()
}

3.3 文件上传 #

go
func main() {
    r := gin.Default()
    
    // 单文件上传
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 保存文件
        dst := "./uploads/" + file.Filename
        if err := c.SaveUploadedFile(file, dst); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, gin.H{
            "filename": file.Filename,
            "size":     file.Size,
            "message":  "上传成功",
        })
    })
    
    // 多文件上传
    r.POST("/uploads", func(c *gin.Context) {
        form, err := c.MultipartForm()
        if err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        files := form.File["files"]
        var results []gin.H
        
        for _, file := range files {
            dst := "./uploads/" + file.Filename
            if err := c.SaveUploadedFile(file, dst); err != nil {
                results = append(results, gin.H{
                    "filename": file.Filename,
                    "error":    err.Error(),
                })
                continue
            }
            results = append(results, gin.H{
                "filename": file.Filename,
                "size":     file.Size,
                "status":   "success",
            })
        }
        
        c.JSON(200, gin.H{"files": results})
    })
    
    r.Run()
}

四、PUT方法 #

4.1 完整更新 #

go
type UpdateUserRequest struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"gte=0,lte=150"`
}

func main() {
    r := gin.Default()
    
    r.PUT("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        var req UpdateUserRequest
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 查找用户
        user, err := getUserByID(id)
        if err != nil {
            c.JSON(404, gin.H{"error": "用户不存在"})
            return
        }
        
        // 完整更新(替换所有字段)
        user.Name = req.Name
        user.Email = req.Email
        user.Age = req.Age
        
        if err := saveUser(user); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

4.2 PUT vs POST #

go
// POST:创建新资源,非幂等
POST /users
{"name": "Alice"}
→ 创建新用户,每次调用都会创建新资源

// PUT:更新或创建指定资源,幂等
PUT /users/123
{"name": "Alice"}
→ 更新ID为123的用户,多次调用结果相同

五、PATCH方法 #

5.1 部分更新 #

go
type PatchUserRequest struct {
    Name  *string `json:"name"`
    Email *string `json:"email"`
    Age   *int    `json:"age"`
}

func main() {
    r := gin.Default()
    
    r.PATCH("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        var req PatchUserRequest
        if err := c.ShouldBindJSON(&req); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        // 查找用户
        user, err := getUserByID(id)
        if err != nil {
            c.JSON(404, gin.H{"error": "用户不存在"})
            return
        }
        
        // 只更新提供的字段
        if req.Name != nil {
            user.Name = *req.Name
        }
        if req.Email != nil {
            user.Email = *req.Email
        }
        if req.Age != nil {
            user.Age = *req.Age
        }
        
        if err := saveUser(user); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

5.2 PUT vs PATCH #

go
// PUT:完整更新,需要提供所有字段
PUT /users/123
{"name": "Alice", "email": "alice@example.com", "age": 25}
→ 所有字段都会被更新

// PATCH:部分更新,只更新提供的字段
PATCH /users/123
{"name": "Alice Updated"}
→ 只更新name字段,其他字段保持不变

六、DELETE方法 #

6.1 删除资源 #

go
func main() {
    r := gin.Default()
    
    r.DELETE("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        // 查找用户
        user, err := getUserByID(id)
        if err != nil {
            c.JSON(404, gin.H{"error": "用户不存在"})
            return
        }
        
        // 删除用户
        if err := deleteUser(id); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        // 返回204 No Content
        c.Status(204)
    })
    
    // 批量删除
    r.DELETE("/users", func(c *gin.Context) {
        var ids []string
        if err := c.ShouldBindJSON(&ids); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        if err := deleteUsers(ids); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        c.Status(204)
    })
    
    r.Run()
}

6.2 软删除 #

go
func main() {
    r := gin.Default()
    
    r.DELETE("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        // 软删除(设置deleted_at字段)
        if err := softDeleteUser(id); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, gin.H{"message": "删除成功"})
    })
    
    r.Run()
}

七、HEAD和OPTIONS #

7.1 HEAD方法 #

go
func main() {
    r := gin.Default()
    
    // HEAD只返回响应头,不返回响应体
    r.HEAD("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        exists, err := userExists(id)
        if err != nil {
            c.Status(500)
            return
        }
        
        if !exists {
            c.Status(404)
            return
        }
        
        // 设置响应头
        c.Header("Content-Type", "application/json")
        c.Header("X-Resource-Id", id)
        c.Status(200)
    })
    
    r.Run()
}

7.2 OPTIONS方法 #

go
func main() {
    r := gin.Default()
    
    // CORS预检请求
    r.OPTIONS("/users", func(c *gin.Context) {
        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
        c.Header("Access-Control-Max-Age", "86400")
        c.Status(204)
    })
    
    r.Run()
}

八、完整RESTful示例 #

8.1 用户CRUD #

go
package main

import (
    "net/http"
    "strconv"
    
    "github.com/gin-gonic/gin"
)

type User struct {
    ID    uint   `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

var users = []User{
    {ID: 1, Name: "Alice", Email: "alice@example.com", Age: 25},
    {ID: 2, Name: "Bob", Email: "bob@example.com", Age: 30},
}

func main() {
    r := gin.Default()
    
    users := r.Group("/users")
    {
        // GET /users - 获取用户列表
        users.GET("", listUsers)
        
        // GET /users/:id - 获取单个用户
        users.GET("/:id", getUser)
        
        // POST /users - 创建用户
        users.POST("", createUser)
        
        // PUT /users/:id - 完整更新用户
        users.PUT("/:id", updateUser)
        
        // PATCH /users/:id - 部分更新用户
        users.PATCH("/:id", patchUser)
        
        // DELETE /users/:id - 删除用户
        users.DELETE("/:id", deleteUser)
    }
    
    r.Run(":8080")
}

func listUsers(c *gin.Context) {
    c.JSON(http.StatusOK, users)
}

func getUser(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))
    
    for _, user := range users {
        if user.ID == uint(id) {
            c.JSON(http.StatusOK, user)
            return
        }
    }
    
    c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}

func createUser(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    user.ID = uint(len(users) + 1)
    users = append(users, user)
    
    c.JSON(http.StatusCreated, user)
}

func updateUser(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))
    
    var req User
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    for i, user := range users {
        if user.ID == uint(id) {
            req.ID = uint(id)
            users[i] = req
            c.JSON(http.StatusOK, req)
            return
        }
    }
    
    c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}

func patchUser(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))
    
    var req map[string]interface{}
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    for i, user := range users {
        if user.ID == uint(id) {
            if name, ok := req["name"].(string); ok {
                users[i].Name = name
            }
            if email, ok := req["email"].(string); ok {
                users[i].Email = email
            }
            if age, ok := req["age"].(float64); ok {
                users[i].Age = int(age)
            }
            c.JSON(http.StatusOK, users[i])
            return
        }
    }
    
    c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}

func deleteUser(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))
    
    for i, user := range users {
        if user.ID == uint(id) {
            users = append(users[:i], users[i+1:]...)
            c.Status(http.StatusNoContent)
            return
        }
    }
    
    c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}

九、总结 #

9.1 核心要点 #

方法 用途 状态码
GET 获取资源 200
POST 创建资源 201
PUT 完整更新 200
PATCH 部分更新 200
DELETE 删除资源 204

9.2 最佳实践 #

实践 说明
遵循RESTful 使用正确的HTTP方法
状态码规范 使用合适的HTTP状态码
幂等性 PUT和DELETE应幂等
响应格式 统一JSON响应格式

9.3 下一步 #

现在你已经掌握了HTTP方法,接下来让我们学习 路由中间件,深入了解路由级中间件的应用!

最后更新:2026-03-28