# 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工具并生成文档:
总结¶
通过本教程,你学习了:
- RESTful设计原则:资源导向、HTTP方法语义、状态码使用
- 路由参数处理:路径参数、查询参数和通配符路由
- 路由分组与版本管理:按业务模块分组和多种版本控制策略
- 路由注册最佳实践:集中管理和API文档生成
这些知识将帮助你构建灵活、可扩展且符合标准的API系统。记住,良好的API设计不仅仅是技术实现,更是对用户需求的深刻理解和良好的用户体验设计。
本节重点:掌握RESTful API设计的核心原则,学会使用Gin框架构建规范、高效的Web API接口。