Appearance
19|Gin 路由与参数
go-gateway 的代理转发层用标准库 net/http 实现,代码轻量,能看清 HTTP 转发的底层机制。但管理后台需要处理多个 API 端点、路径参数、查询参数、请求体解析和统一响应,用标准库写会比较冗长。Gin 是国内 Go 后端开发最常用的 Web 框架,路由性能高、API 简洁、中间件生态丰富。本章用 Gin 搭建管理后台,运行在独立的 :8081 端口。
一、安装 Gin
bash
go get -u github.com/gin-gonic/gin
go mod tidy二、最小 Gin 服务
go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run(":8081")
}gin.Default() 创建一个带默认中间件(日志和恢复)的引擎。r.GET 注册 GET 路由,c.JSON 返回 JSON 响应。r.Run 等价于 http.ListenAndServe。
验证:
bash
go run main.go
curl http://127.0.0.1:8081/ping三、路由组
管理 API 通常以 /admin 为前缀,用路由组组织:
go
admin := r.Group("/admin")
{
admin.GET("/routes", listRoutes)
admin.POST("/routes", addRoute)
admin.DELETE("/routes/:id", deleteRoute)
}路由组可以批量设置中间件和路径前缀。上面的代码等价于:
go
r.GET("/admin/routes", listRoutes)
r.POST("/admin/routes", addRoute)
r.DELETE("/admin/routes/:id", deleteRoute)但路由组让代码结构更清晰,也方便后续给所有管理 API 统一加认证中间件。
四、路径参数
Gin 用 : 定义路径参数:
go
func getRoute(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"id": id})
}
admin.GET("/routes/:id", getRoute)请求 /admin/routes/abc123 时,id 的值是 abc123。
路径参数可以出现在任意位置:
go
r.GET("/api/:version/users", func(c *gin.Context) {
version := c.Param("version")
c.JSON(http.StatusOK, gin.H{"version": version})
})五、查询参数
查询参数用 c.Query 读取:
go
func listRoutes(c *gin.Context) {
page := c.DefaultQuery("page", "1")
limit := c.DefaultQuery("limit", "10")
c.JSON(http.StatusOK, gin.H{
"page": page,
"limit": limit,
})
}c.Query("page") 读取查询参数,不存在时返回空字符串。c.DefaultQuery 在参数不存在时返回默认值。
查询参数类型都是字符串,转成数字需要显式转换:
go
pageInt, _ := strconv.Atoi(c.DefaultQuery("page", "1"))不要忽略转换错误——如果用户传了 page=abc,Atoi 会返回错误,此时应该返回 400 而不是默默用 0。
六、请求体解析
POST 和 PUT 请求通常带 JSON 请求体。Gin 用 c.ShouldBindJSON 解析到结构体:
go
type AddRouteRequest struct {
Prefix string `json:"prefix" binding:"required"`
Backend string `json:"backend" binding:"required,url"`
}
func addRoute(c *gin.Context) {
var req AddRouteRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 添加路由逻辑...
c.JSON(http.StatusCreated, req)
}binding:"required" 表示字段必填,缺失时返回 400。binding:"url" 验证字段值是合法的 URL 格式。Gin 的验证基于 go-playground/validator,支持丰富的验证标签。
七、统一响应格式
管理 API 需要统一的响应结构,方便前端处理:
go
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
func OK(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{Code: 0, Msg: "ok", Data: data})
}
func Fail(c *gin.Context, code int, msg string) {
c.JSON(code, Response{Code: code, Msg: msg})
}使用:
go
func listRoutes(c *gin.Context) {
routes := router.ListRoutes()
OK(c, routes)
}
func addRoute(c *gin.Context) {
var req AddRouteRequest
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, http.StatusBadRequest, err.Error())
return
}
// ...
OK(c, req)
}八、当前管理 API 代码
go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 代理层跑在 :8080
go startProxy()
// 管理后台跑在 :8081
r := gin.Default()
admin := r.Group("/admin")
{
admin.GET("/routes", listRoutes)
admin.POST("/routes", addRoute)
admin.GET("/routes/:id", getRoute)
admin.DELETE("/routes/:id", deleteRoute)
}
r.Run(":8081")
}代理层和管理后台跑在同一个进程的不同端口上。go startProxy() 在新 goroutine 里启动代理,主 goroutine 启动 Gin 服务。
九、常见错误
绑定 JSON 时忽略错误
go
var req AddRouteRequest
c.ShouldBindJSON(&req) // 错误:忽略了返回值请求体格式错误时,ShouldBindJSON 返回错误,但上面的代码继续用 req 的零值处理,可能导致脏数据写入。
路径参数和查询参数混用
go
r.GET("/users/:id", func(c *gin.Context) {
id := c.Query("id") // 错误:应该用 c.Param("id")
})路径参数用 c.Param,查询参数用 c.Query。两者来源不同,不要混淆。
并发修改路由表不加锁
管理 API 修改路由表时,代理层可能正在读取路由表。如果不用锁保护,会出现数据竞争。下一讲会介绍如何用中间件统一处理这类问题。