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(¶ms); 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