gin
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGin Core Knowledge
Gin核心知识
Deep Knowledge: Usewith technology:mcp__documentation__fetch_docsfor comprehensive documentation.gin
深度知识:使用工具,指定technology为mcp__documentation__fetch_docs以获取完整文档。gin
Basic Setup
基础配置
go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default() // Logger and Recovery middleware
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello, World!")
})
r.Run(":8080")
}go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default() // Logger and Recovery middleware
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello, World!")
})
r.Run(":8080")
}Routing
路由
Basic Routes
基础路由
go
r := gin.Default()
r.GET("/users", listUsers)
r.GET("/users/:id", getUser)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)
// Any HTTP method
r.Any("/any", handleAny)
// NoRoute handler
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
})go
r := gin.Default()
r.GET("/users", listUsers)
r.GET("/users/:id", getUser)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)
// Any HTTP method
r.Any("/any", handleAny)
// NoRoute handler
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
})Path Parameters
路径参数
go
// Single parameter
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"id": id})
})
// Wildcard
r.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
c.String(http.StatusOK, "File: %s", filepath)
})go
// Single parameter
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"id": id})
})
// Wildcard
r.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
c.String(http.StatusOK, "File: %s", filepath)
})Route Groups
路由分组
go
api := r.Group("/api")
{
v1 := api.Group("/v1")
{
v1.GET("/users", listUsersV1)
v1.POST("/users", createUserV1)
}
v2 := api.Group("/v2")
{
v2.GET("/users", listUsersV2)
v2.POST("/users", createUserV2)
}
}go
api := r.Group("/api")
{
v1 := api.Group("/v1")
{
v1.GET("/users", listUsersV1)
v1.POST("/users", createUserV1)
}
v2 := api.Group("/v2")
{
v2.GET("/users", listUsersV2)
v2.POST("/users", createUserV2)
}
}Request Binding
请求绑定
JSON Binding
JSON绑定
go
type CreateUserRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=130"`
}
func createUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"name": req.Name,
"email": req.Email,
})
}go
type CreateUserRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=130"`
}
func createUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"name": req.Name,
"email": req.Email,
})
}Query Binding
查询参数绑定
go
type ListUsersQuery struct {
Page int `form:"page" binding:"gte=1"`
PerPage int `form:"per_page" binding:"gte=1,lte=100"`
Sort string `form:"sort"`
}
func listUsers(c *gin.Context) {
var query ListUsersQuery
query.Page = 1
query.PerPage = 20
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"page": query.Page,
"per_page": query.PerPage,
})
}go
type ListUsersQuery struct {
Page int `form:"page" binding:"gte=1"`
PerPage int `form:"per_page" binding:"gte=1,lte=100"`
Sort string `form:"sort"`
}
func listUsers(c *gin.Context) {
var query ListUsersQuery
query.Page = 1
query.PerPage = 20
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"page": query.Page,
"per_page": query.PerPage,
})
}Form Binding
表单绑定
go
type LoginForm struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func login(c *gin.Context) {
var form LoginForm
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"username": form.Username})
}go
type LoginForm struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func login(c *gin.Context) {
var form LoginForm
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"username": form.Username})
}URI Binding
URI绑定
go
type UserURI struct {
ID uint `uri:"id" binding:"required"`
}
func getUser(c *gin.Context) {
var uri UserURI
if err := c.ShouldBindUri(&uri); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"id": uri.ID})
}go
type UserURI struct {
ID uint `uri:"id" binding:"required"`
}
func getUser(c *gin.Context) {
var uri UserURI
if err := c.ShouldBindUri(&uri); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"id": uri.ID})
}Validation
校验
Custom Validator
自定义校验器
go
import "github.com/go-playground/validator/v10"
var validateUsername validator.Func = func(fl validator.FieldLevel) bool {
username := fl.Field().String()
return len(username) >= 3 && len(username) <= 20
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("username", validateUsername)
}
r.Run(":8080")
}
type RegisterRequest struct {
Username string `json:"username" binding:"required,username"`
Email string `json:"email" binding:"required,email"`
}go
import "github.com/go-playground/validator/v10"
var validateUsername validator.Func = func(fl validator.FieldLevel) bool {
username := fl.Field().String()
return len(username) >= 3 && len(username) <= 20
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("username", validateUsername)
}
r.Run(":8080")
}
type RegisterRequest struct {
Username string `json:"username" binding:"required,username"`
Email string `json:"email" binding:"required,email"`
}Middleware
中间件
Built-in Middleware
内置中间件
go
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] %s %s %d %s\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.StatusCode,
param.Latency,
)
}))go
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] %s %s %d %s\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.StatusCode,
param.Latency,
)
}))Custom Middleware
自定义中间件
go
func TimingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
c.Header("X-Response-Time", duration.String())
}
}
func main() {
r := gin.Default()
r.Use(TimingMiddleware())
}go
func TimingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
c.Header("X-Response-Time", duration.String())
}
}
func main() {
r := gin.Default()
r.Use(TimingMiddleware())
}Authentication Middleware
认证中间件
go
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
c.Abort()
return
}
if !strings.HasPrefix(token, "Bearer ") {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid format"})
c.Abort()
return
}
tokenString := token[7:]
user, err := validateToken(tokenString)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
c.Abort()
return
}
c.Set("user", user)
c.Next()
}
}
protected := r.Group("/api")
protected.Use(AuthMiddleware())
{
protected.GET("/me", getMe)
}
func getMe(c *gin.Context) {
user, _ := c.Get("user")
c.JSON(http.StatusOK, user)
}go
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
c.Abort()
return
}
if !strings.HasPrefix(token, "Bearer ") {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid format"})
c.Abort()
return
}
tokenString := token[7:]
user, err := validateToken(tokenString)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
c.Abort()
return
}
c.Set("user", user)
c.Next()
}
}
protected := r.Group("/api")
protected.Use(AuthMiddleware())
{
protected.GET("/me", getMe)
}
func getMe(c *gin.Context) {
user, _ := c.Get("user")
c.JSON(http.StatusOK, user)
}CORS Middleware
CORS中间件
go
import "github.com/gin-contrib/cors"
func main() {
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
r.Run(":8080")
}go
import "github.com/gin-contrib/cors"
func main() {
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
r.Run(":8080")
}Response Rendering
响应渲染
JSON Response
JSON响应
go
func getUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"id": 1,
"name": "Alice",
})
}
type User struct {
ID uint `json:"id"`
Name string `json:"name"`
}
func getUser(c *gin.Context) {
user := User{ID: 1, Name: "Alice"}
c.JSON(http.StatusOK, user)
}go
func getUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"id": 1,
"name": "Alice",
})
}
type User struct {
ID uint `json:"id"`
Name string `json:"name"`
}
func getUser(c *gin.Context) {
user := User{ID: 1, Name: "Alice"}
c.JSON(http.StatusOK, user)
}Other Formats
其他格式
go
c.XML(http.StatusOK, gin.H{"message": "hello"})
c.YAML(http.StatusOK, gin.H{"message": "hello"})
c.String(http.StatusOK, "Hello, %s!", name)
c.Redirect(http.StatusMovedPermanently, "https://example.com")
c.File("./static/file.txt")go
c.XML(http.StatusOK, gin.H{"message": "hello"})
c.YAML(http.StatusOK, gin.H{"message": "hello"})
c.String(http.StatusOK, "Hello, %s!", name)
c.Redirect(http.StatusMovedPermanently, "https://example.com")
c.File("./static/file.txt")Production Readiness
生产环境就绪
Configuration
配置
go
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
Formatter: jsonLogFormatter,
Output: os.Stdout,
}))
r.Run(":8080")
}
func jsonLogFormatter(param gin.LogFormatterParams) string {
log := map[string]interface{}{
"timestamp": param.TimeStamp.Format(time.RFC3339),
"status": param.StatusCode,
"latency": param.Latency.Milliseconds(),
"method": param.Method,
"path": param.Path,
}
b, _ := json.Marshal(log)
return string(b) + "\n"
}go
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
Formatter: jsonLogFormatter,
Output: os.Stdout,
}))
r.Run(":8080")
}
func jsonLogFormatter(param gin.LogFormatterParams) string {
log := map[string]interface{}{
"timestamp": param.TimeStamp.Format(time.RFC3339),
"status": param.StatusCode,
"latency": param.Latency.Milliseconds(),
"method": param.Method,
"path": param.Path,
}
b, _ := json.Marshal(log)
return string(b) + "\n"
}Health Checks
健康检查
go
func healthHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
}
func readyHandler(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"status": "not ready",
"database": "disconnected",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "ready",
"database": "connected",
})
}
}go
func healthHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
}
func readyHandler(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"status": "not ready",
"database": "disconnected",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "ready",
"database": "connected",
})
}
}Graceful Shutdown
优雅停机
go
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello")
})
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
}go
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello")
})
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
}Checklist
检查清单
- gin.ReleaseMode for production
- CORS configured
- Authentication middleware
- Request validation
- Structured JSON logging
- Health/readiness endpoints
- Graceful shutdown
- Rate limiting
- 使用gin.ReleaseMode运行生产环境
- 配置CORS
- 实现认证中间件
- 启用请求校验
- 结构化JSON日志
- 健康/就绪检查端点
- 优雅停机
- 限流
When NOT to Use This Skill
不适用场景
- Echo projects - Echo has different middleware patterns
- Fiber projects - Fiber is Express-like with different API
- Chi projects - Chi is stdlib-compatible router
- gRPC services - Use gRPC framework directly
- Minimal stdlib apps - Gin adds dependency overhead
- Echo项目:Echo拥有不同的中间件模式
- Fiber项目:Fiber类Express框架,API不同
- Chi项目:Chi是兼容标准库的路由
- gRPC服务:直接使用gRPC框架
- 轻量标准库应用:Gin会增加依赖开销
Anti-Patterns
反模式
| Anti-Pattern | Why It's Bad | Solution |
|---|---|---|
Using | Includes logger/recovery you may not want | Use |
| Not checking binding errors | Invalid data processed | Always check |
Using | Manual serialization error-prone | Use |
| Global variables for config | Hard to test | Pass config via context or dependency injection |
| Missing validation tags | No input validation | Use |
| Not setting gin.ReleaseMode | Debug logs in production | Set |
| 反模式 | 问题所在 | 解决方案 |
|---|---|---|
盲目使用 | 包含可能不需要的日志/恢复中间件 | 使用 |
| 不检查绑定错误 | 会处理无效数据 | 务必检查 |
使用 | 手动序列化容易出错 | 使用 |
| 使用全局变量存储配置 | 难以测试 | 通过上下文或依赖注入传递配置 |
| 缺失校验标签 | 没有输入校验 | 使用 |
| 未设置gin.ReleaseMode | 生产环境输出调试日志 | 设置 |
Quick Troubleshooting
快速故障排查
| Problem | Diagnosis | Fix |
|---|---|---|
| Route not found (404) | Route not registered | Check router setup and method |
| Binding fails silently | Not checking error | Check |
| CORS errors | Not configured | Use |
| Middleware not executing | Wrong order | Place middleware before routes |
| Panic in handler | No recovery middleware | Use |
| Slow JSON serialization | Large response | Use |
| 问题 | 诊断 | 解决方法 |
|---|---|---|
| 路由未找到(404) | 路由未注册 | 检查路由配置和请求方法 |
| 绑定失败无提示 | 未检查错误 | 检查 |
| CORS错误 | 未配置CORS | 使用 |
| 中间件未执行 | 顺序错误 | 将中间件添加在路由之前 |
| 处理器发生panic | 未使用恢复中间件 | 使用 |
| JSON序列化缓慢 | 响应数据过大 | 使用 |
Reference Documentation
参考文档
- Routing
- Binding
- Middleware
- 路由
- 绑定
- 中间件