跳转至

# 6.4 路由设计与RESTful API

作为有三十年Go语言开发经验的老师,我将带你深入理解RESTful API设计规范和路由系统的实现。让我们从基础开始,逐步构建一个完整的路由系统。

1. RESTful设计原则

资源导向与方法语义

RESTful API的核心思想是将一切视为资源,使用HTTP方法表达操作意图:

package main

import (
    "fmt"
    "log"
    "net/http"
    "strconv"

    "github.com/gin-gonic/gin"
)

// User 模型
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// 模拟数据库
var users = []User{
    {ID: 1, Name: "张三", Email: "zhangsan@example.com"},
    {ID: 2, Name: "李四", Email: "lisi@example.com"},
}

func main() {
    r := gin.Default()

    // 获取用户列表
    r.GET("/users", func(c *gin.Context) {
        c.JSON(http.StatusOK, users)
    })

    // 获取单个用户
    r.GET("/users/:id", func(c *gin.Context) {
        id, err := strconv.Atoi(c.Param("id"))
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
            return
        }

        for _, user := range users {
            if user.ID == id {
                c.JSON(http.StatusOK, user)
                return
            }
        }

        c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
    })

    // 创建用户
    r.POST("/users", func(c *gin.Context) {
        var newUser User
        if err := c.BindJSON(&newUser); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户数据"})
            return
        }

        // 生成ID
        newUser.ID = len(users) + 1
        users = append(users, newUser)

        c.JSON(http.StatusCreated, newUser)
    })

    // 更新用户
    r.PUT("/users/:id", func(c *gin.Context) {
        id, err := strconv.Atoi(c.Param("id"))
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
            return
        }

        var updatedUser User
        if err := c.BindJSON(&updatedUser); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户数据"})
            return
        }

        for i, user := range users {
            if user.ID == id {
                updatedUser.ID = id
                users[i] = updatedUser
                c.JSON(http.StatusOK, updatedUser)
                return
            }
        }

        c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
    })

    // 删除用户
    r.DELETE("/users/:id", func(c *gin.Context) {
        id, err := strconv.Atoi(c.Param("id"))
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
            return
        }

        for i, user := range users {
            if user.ID == id {
                users = append(users[:i], users[i+1:]...)
                c.Status(http.StatusNoContent)
                return
            }
        }

        c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
    })

    fmt.Println("服务器启动在 http://localhost:8080")
    log.Fatal(r.Run(":8080"))
}

状态码使用规范

HTTP状态码是API与客户端通信的重要方式:

  • 200 OK: 请求成功
  • 201 Created: 资源创建成功
  • 204 No Content: 请求成功,无返回内容
  • 400 Bad Request: 客户端请求错误
  • 404 Not Found: 资源不存在
  • 500 Internal Server Error: 服务器内部错误

2. 路由参数处理

路径参数与查询参数

package main

import (
    "fmt"
    "log"
    "net/http"
    "strconv"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    // 路径参数示例
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id") // 获取路径参数
        c.String(http.StatusOK, "用户ID: %s", id)
    })

    // 查询参数示例
    r.GET("/users", func(c *gin.Context) {
        page := c.DefaultQuery("page", "1")    // 默认值
        size := c.DefaultQuery("size", "10")   // 默认值
        search := c.Query("search")            // 无默认值

        c.String(http.StatusOK, "页码: %s, 大小: %s, 搜索: %s", page, size, search)
    })

    // 查询参数验证
    r.GET("/products", func(c *gin.Context) {
        pageStr := c.DefaultQuery("page", "1")
        page, err := strconv.Atoi(pageStr)
        if err != nil || page < 1 {
            c.JSON(http.StatusBadRequest, gin.H{"error": "页码必须是大于0的整数"})
            return
        }

        sizeStr := c.DefaultQuery("size", "10")
        size, err := strconv.Atoi(sizeStr)
        if err != nil || size < 1 || size > 100 {
            c.JSON(http.StatusBadRequest, gin.H{"error": "每页大小必须是1-100之间的整数"})
            return
        }

        c.JSON(http.StatusOK, gin.H{
            "page":  page,
            "size":  size,
            "data":  []string{"产品1", "产品2", "产品3"},
        })
    })

    // 通配符路由
    r.GET("/static/*filepath", func(c *gin.Context) {
        filepath := c.Param("filepath")
        c.String(http.StatusOK, "请求的文件路径: %s", filepath)
    })

    fmt.Println("服务器启动在 http://localhost:8080")
    log.Fatal(r.Run(":8080"))
}

3. 路由分组与版本管理

按业务模块分组

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    // 公共路由组(无需认证)
    public := r.Group("/public")
    {
        public.GET("/info", func(c *gin.Context) {
            c.String(http.StatusOK, "公共信息")
        })

        public.GET("/docs", func(c *gin.Context) {
            c.String(http.StatusOK, "API文档")
        })
    }

    // 私有路由组(需要认证)
    private := r.Group("/private")
    // 这里可以添加认证中间件
    // private.Use(authMiddleware())
    {
        private.GET("/profile", func(c *gin.Context) {
            c.String(http.StatusOK, "用户资料")
        })

        private.GET("/settings", func(c *gin.Context) {
            c.String(http.StatusOK, "用户设置")
        })
    }

    // API版本控制 - URL路径方式
    v1 := r.Group("/api/v1")
    {
        v1.GET("/users", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"version": "v1", "message": "用户列表"})
        })

        v1.GET("/products", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"version": "v1", "message": "产品列表"})
        })
    }

    v2 := r.Group("/api/v2")
    {
        v2.GET("/users", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"version": "v2", "message": "新版用户列表"})
        })

        v2.GET("/products", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"version": "v2", "message": "新版产品列表"})
        })
    }

    fmt.Println("服务器启动在 http://localhost:8080")
    log.Fatal(r.Run(":8080"))
}

请求头版本控制

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"
)

// 版本控制中间件
func versionMiddleware(c *gin.Context) {
    version := c.GetHeader("Accept-Version")

    // 根据版本头重定向到不同的处理函数
    switch version {
    case "v1":
        c.Set("api_version", "v1")
    case "v2":
        c.Set("api_version", "v2")
    default:
        c.Set("api_version", "v1") // 默认版本
    }

    c.Next()
}

func main() {
    r := gin.Default()

    // 应用版本中间件
    api := r.Group("/api")
    api.Use(versionMiddleware)
    {
        api.GET("/users", func(c *gin.Context) {
            version := c.MustGet("api_version").(string)

            switch version {
            case "v1":
                c.JSON(http.StatusOK, gin.H{
                    "version": version,
                    "message": "v1用户API",
                    "data":    []string{"用户1", "用户2"},
                })
            case "v2":
                c.JSON(http.StatusOK, gin.H{
                    "version": version,
                    "message": "v2用户API",
                    "data":    []string{"用户1", "用户2", "用户3"},
                    "metadata": gin.H{
                        "total": 3,
                        "page":  1,
                    },
                })
            }
        })

        api.GET("/products", func(c *gin.Context) {
            version := c.MustGet("api_version").(string)
            c.JSON(http.StatusOK, gin.H{"version": version, "message": "产品API"})
        })
    }

    fmt.Println("服务器启动在 http://localhost:8080")
    log.Fatal(r.Run(":8080"))
}

4. 路由注册最佳实践

路由集中管理

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
)

// UserHandler 用户相关路由处理
type UserHandler struct{}

// RegisterRoutes 注册用户路由
func (h *UserHandler) RegisterRoutes(r *gin.RouterGroup) {
    userGroup := r.Group("/users")
    {
        userGroup.GET("", h.GetUsers)
        userGroup.GET("/:id", h.GetUser)
        userGroup.POST("", h.CreateUser)
        userGroup.PUT("/:id", h.UpdateUser)
        userGroup.DELETE("/:id", h.DeleteUser)
    }
}

func (h *UserHandler) GetUsers(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "获取用户列表"})
}

func (h *UserHandler) GetUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"message": "获取用户", "id": id})
}

func (h *UserHandler) CreateUser(c *gin.Context) {
    c.JSON(http.StatusCreated, gin.H{"message": "创建用户"})
}

func (h *UserHandler) UpdateUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"message": "更新用户", "id": id})
}

func (h *UserHandler) DeleteUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"message": "删除用户", "id": id})
}

// ProductHandler 产品相关路由处理
type ProductHandler struct{}

// RegisterRoutes 注册产品路由
func (h *ProductHandler) RegisterRoutes(r *gin.RouterGroup) {
    productGroup := r.Group("/products")
    {
        productGroup.GET("", h.GetProducts)
        productGroup.GET("/:id", h.GetProduct)
    }
}

func (h *ProductHandler) GetProducts(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "获取产品列表"})
}

func (h *ProductHandler) GetProduct(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"message": "获取产品", "id": id})
}

func main() {
    r := gin.Default()

    // API路由组
    api := r.Group("/api")
    {
        // 注册用户路由
        userHandler := &UserHandler{}
        userHandler.RegisterRoutes(api)

        // 注册产品路由
        productHandler := &ProductHandler{}
        productHandler.RegisterRoutes(api)
    }

    // 健康检查路由
    r.GET("/health", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })

    fmt.Println("服务器启动在 http://localhost:8080")
    log.Fatal(r.Run(":8080"))
}

Swagger API文档集成

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
)

// @title 用户管理API
// @version 1.0
// @description 这是一个用户管理系统的API文档
// @host localhost:8080
// @BasePath /api/v1
func main() {
    r := gin.Default()

    // API v1 路由组
    v1 := r.Group("/api/v1")
    {
        v1.GET("/users", getUsers)
        v1.GET("/users/:id", getUser)
        v1.POST("/users", createUser)
        v1.PUT("/users/:id", updateUser)
        v1.DELETE("/users/:id", deleteUser)
    }

    // 配置Swagger路由
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

    fmt.Println("API文档访问: http://localhost:8080/swagger/index.html")
    log.Fatal(r.Run(":8080"))
}

// @Summary 获取用户列表
// @Description 获取所有用户信息
// @Tags users
// @Produce json
// @Success 200 {array} User
// @Router /users [get]
func getUsers(c *gin.Context) {
    c.JSON(http.StatusOK, []gin.H{
        {"id": 1, "name": "张三"},
        {"id": 2, "name": "李四"},
    })
}

// @Summary 获取用户详情
// @Description 根据ID获取用户详细信息
// @Tags users
// @Produce json
// @Param id path int true "用户ID"
// @Success 200 {object} User
// @Failure 404 {object} map[string]string
// @Router /users/{id} [get]
func getUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"id": id, "name": "用户" + id})
}

// @Summary 创建用户
// @Description 创建新用户
// @Tags users
// @Accept json
// @Produce json
// @Param user body User true "用户信息"
// @Success 201 {object} User
// @Failure 400 {object} map[string]string
// @Router /users [post]
func createUser(c *gin.Context) {
    c.JSON(http.StatusCreated, gin.H{"message": "用户创建成功"})
}

// @Summary 更新用户
// @Description 根据ID更新用户信息
// @Tags users
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Param user body User true "用户信息"
// @Success 200 {object} User
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Router /users/{id} [put]
func updateUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"message": "用户更新成功", "id": id})
}

// @Summary 删除用户
// @Description 根据ID删除用户
// @Tags users
// @Produce json
// @Param id path int true "用户ID"
// @Success 204
// @Failure 404 {object} map[string]string
// @Router /users/{id} [delete]
func deleteUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusNoContent, gin.H{"message": "用户删除成功", "id": id})
}

// User 用户模型
type User struct {
    ID   int    `json:"id" example:"1"`
    Name string `json:"name" example:"张三"`
}

注意:要使用Swagger,你需要安装swag工具并生成文档:

go install github.com/swaggo/swag/cmd/swag@latest
swag init

总结

通过本教程,你学习了:

  1. RESTful设计原则:资源导向、HTTP方法语义、状态码使用
  2. 路由参数处理:路径参数、查询参数和通配符路由
  3. 路由分组与版本管理:按业务模块分组和多种版本控制策略
  4. 路由注册最佳实践:集中管理和API文档生成

这些知识将帮助你构建灵活、可扩展且符合标准的API系统。记住,良好的API设计不仅仅是技术实现,更是对用户需求的深刻理解和良好的用户体验设计。


本节重点:掌握RESTful API设计的核心原则,学会使用Gin框架构建规范、高效的Web API接口。