1. Go Web 框架对比

Gin

~140k req/s

最流行,生态最完善,文档丰富。适合大多数业务服务。

Echo

~120k req/s

API 设计优雅,内置 Validator,适合 API 服务开发。

Fiber

~300k req/s

基于 fasthttp,性能极致,Express 风格 API,适合高并发场景。

特性GinEchoFibernet/http
性能优秀优秀极佳良好
生态最丰富丰富增长中标准库
学习曲线很低(类Express)
内存兼容标准库兼容标准库兼容⚠️ fasthttp不兼容标准
推荐场景通用业务API服务高并发/低延迟工具/简单服务

2. Gin 框架实战

安装与基础路由

go get github.com/gin-gonic/gin
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func main() {
    // gin.Default() = gin.New() + Logger + Recovery 中间件
    r := gin.Default()

    // ─── 路由定义 ──────────────────────────────────────────
    // 基础路由
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })

    // 路径参数
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.JSON(200, gin.H{"user_id": id})
    })

    // 查询参数: /search?q=go&page=1
    r.GET("/search", func(c *gin.Context) {
        q := c.Query("q")
        page := c.DefaultQuery("page", "1")
        c.JSON(200, gin.H{"query": q, "page": page})
    })

    // 路由组 (便于统一前缀和中间件)
    api := r.Group("/api/v1")
    {
        api.GET("/users", listUsers)
        api.POST("/users", createUser)
        api.GET("/users/:id", getUser)
        api.PUT("/users/:id", updateUser)
        api.DELETE("/users/:id", deleteUser)
    }

    r.Run(":8080")
}

请求绑定与校验

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

// 请求结构体 (binding tag 驱动校验)
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=8"`
    Age      int    `json:"age"   binding:"omitempty,min=0,max=150"`
    Role     string `json:"role"  binding:"oneof=admin user guest"`
}

type UpdateUserRequest struct {
    Name  *string `json:"name"  binding:"omitempty,min=2,max=50"`
    Email *string `json:"email" binding:"omitempty,email"`
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        // 格式化校验错误
        var ve validator.ValidationErrors
        if errors.As(err, &ve) {
            errs := make([]string, len(ve))
            for i, e := range ve {
                errs[i] = fmt.Sprintf("字段 %s: %s", e.Field(), e.Tag())
            }
            c.JSON(http.StatusBadRequest, gin.H{
                "error":  "参数校验失败",
                "detail": errs,
            })
            return
        }
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // 处理业务逻辑...
    user := User{Name: req.Name, Email: req.Email}
    c.JSON(http.StatusCreated, gin.H{
        "success": true,
        "data":    user,
    })
}

// 路径参数 + 表单绑定
type UploadRequest struct {
    Description string `form:"description"`
    Category    string `form:"category" binding:"required"`
}

func uploadFile(c *gin.Context) {
    var req UploadRequest
    if err := c.ShouldBind(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(400, gin.H{"error": "文件不存在"})
        return
    }

    // 保存文件
    dst := "./uploads/" + file.Filename
    c.SaveUploadedFile(file, dst)
    c.JSON(200, gin.H{"filename": file.Filename, "size": file.Size})
}

中间件系统

package main

import (
    "net/http"
    "strings"
    "time"
    "github.com/gin-gonic/gin"
    "golang.org/x/time/rate"
)

// ─── 认证中间件 ────────────────────────────────────────────────
func AuthMiddleware(secretKey string) gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            token = c.Query("token")
        }
        token = strings.TrimPrefix(token, "Bearer ")

        if token == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
                "error": "未提供认证 Token",
            })
            return
        }

        // 验证 JWT (简化示例)
        claims, err := validateJWT(token, secretKey)
        if err != nil {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
                "error": "Token 无效或已过期",
            })
            return
        }

        // 将用户信息存入 context
        c.Set("user_id", claims.UserID)
        c.Set("user_role", claims.Role)
        c.Next()  // 继续处理
    }
}

// ─── 限流中间件 ────────────────────────────────────────────────
func RateLimitMiddleware(rps float64, burst int) gin.HandlerFunc {
    limiter := rate.NewLimiter(rate.Limit(rps), burst)
    return func(c *gin.Context) {
        if !limiter.Allow() {
            c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
                "error":       "请求频率超限",
                "retry_after": "1s",
            })
            return
        }
        c.Next()
    }
}

// ─── 请求 ID 中间件 ────────────────────────────────────────────
func RequestIDMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = generateID() // UUID
        }
        c.Set("request_id", requestID)
        c.Header("X-Request-ID", requestID)
        c.Next()
    }
}

// ─── CORS 中间件 ───────────────────────────────────────────────
func CORSMiddleware(allowOrigins []string) gin.HandlerFunc {
    return func(c *gin.Context) {
        origin := c.GetHeader("Origin")
        allowed := false
        for _, o := range allowOrigins {
            if o == "*" || o == origin {
                allowed = true
                break
            }
        }
        if allowed {
            c.Header("Access-Control-Allow-Origin", origin)
            c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
            c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Request-ID")
            c.Header("Access-Control-Max-Age", "86400")
        }
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }
        c.Next()
    }
}

// ─── 路由注册 ──────────────────────────────────────────────────
func setupRouter() *gin.Engine {
    r := gin.New()
    r.Use(gin.Recovery())                             // panic 恢复
    r.Use(RequestIDMiddleware())                      // 请求 ID
    r.Use(CORSMiddleware([]string{"*"}))              // CORS

    // 公开路由
    public := r.Group("/api/v1")
    public.Use(RateLimitMiddleware(100, 200))         // 100 req/s
    {
        public.POST("/auth/login", loginHandler)
        public.POST("/auth/register", registerHandler)
        public.GET("/health", healthHandler)
    }

    // 需要认证的路由
    protected := r.Group("/api/v1")
    protected.Use(AuthMiddleware("your-secret-key"))
    protected.Use(RateLimitMiddleware(50, 100))
    {
        protected.GET("/users/me", getMeHandler)
        protected.GET("/users", listUsers)
        protected.POST("/users", createUser)
    }

    return r
}

3. RESTful API 设计规范

资源命名与 URL 设计

操作方法URL状态码
获取用户列表 GET /api/v1/users 200
创建用户 POST /api/v1/users 201
获取指定用户 GET /api/v1/users/{id} 200
全量更新用户 PUT /api/v1/users/{id} 200
部分更新用户 PATCH /api/v1/users/{id} 200
删除用户 DELETE /api/v1/users/{id} 204
获取用户文章 GET /api/v1/users/{id}/posts 200

统一响应格式

package response

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

// 统一响应结构
type Response struct {
    Success   bool        `json:"success"`
    Data      any         `json:"data,omitempty"`
    Error     *ErrorInfo  `json:"error,omitempty"`
    Meta      *Meta       `json:"meta,omitempty"`
    RequestID string      `json:"request_id,omitempty"`
}

type ErrorInfo struct {
    Code    string `json:"code"`
    Message string `json:"message"`
    Detail  any    `json:"detail,omitempty"`
}

type Meta struct {
    Page       int   `json:"page"`
    PageSize   int   `json:"page_size"`
    Total      int64 `json:"total"`
    TotalPages int   `json:"total_pages"`
}

// 成功响应
func OK(c *gin.Context, data any) {
    c.JSON(http.StatusOK, Response{
        Success:   true,
        Data:      data,
        RequestID: c.GetString("request_id"),
    })
}

// 创建成功
func Created(c *gin.Context, data any) {
    c.JSON(http.StatusCreated, Response{
        Success:   true,
        Data:      data,
        RequestID: c.GetString("request_id"),
    })
}

// 分页响应
func Paginated(c *gin.Context, data any, page, pageSize int, total int64) {
    totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
    c.JSON(http.StatusOK, Response{
        Success: true,
        Data:    data,
        Meta: &Meta{
            Page:       page,
            PageSize:   pageSize,
            Total:      total,
            TotalPages: totalPages,
        },
        RequestID: c.GetString("request_id"),
    })
}

// 错误响应
func BadRequest(c *gin.Context, code, msg string, detail ...any) {
    var d any
    if len(detail) > 0 {
        d = detail[0]
    }
    c.JSON(http.StatusBadRequest, Response{
        Success: false,
        Error: &ErrorInfo{Code: code, Message: msg, Detail: d},
        RequestID: c.GetString("request_id"),
    })
}

func Unauthorized(c *gin.Context, msg string) {
    c.AbortWithStatusJSON(http.StatusUnauthorized, Response{
        Success: false,
        Error: &ErrorInfo{Code: "UNAUTHORIZED", Message: msg},
    })
}

func InternalError(c *gin.Context) {
    c.JSON(http.StatusInternalServerError, Response{
        Success: false,
        Error: &ErrorInfo{Code: "INTERNAL_ERROR", Message: "服务器内部错误"},
        RequestID: c.GetString("request_id"),
    })
}

4. 完整的用户 CRUD 示例

package handler

import (
    "strconv"
    "github.com/gin-gonic/gin"
    "myapp/internal/service"
    "myapp/internal/response"
)

type UserHandler struct {
    svc service.UserService
}

func NewUserHandler(svc service.UserService) *UserHandler {
    return &UserHandler{svc: svc}
}

// GET /api/v1/users?page=1&page_size=20&search=alice
func (h *UserHandler) List(c *gin.Context) {
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
    search := c.Query("search")

    if page < 1 { page = 1 }
    if pageSize < 1 || pageSize > 100 { pageSize = 20 }

    users, total, err := h.svc.ListUsers(c.Request.Context(), service.ListUsersParams{
        Page:     page,
        PageSize: pageSize,
        Search:   search,
    })
    if err != nil {
        response.InternalError(c)
        return
    }
    response.Paginated(c, users, page, pageSize, total)
}

// GET /api/v1/users/:id
func (h *UserHandler) Get(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
        response.BadRequest(c, "INVALID_ID", "用户 ID 格式错误")
        return
    }

    user, err := h.svc.GetUser(c.Request.Context(), id)
    if err != nil {
        if errors.Is(err, service.ErrNotFound) {
            c.JSON(404, gin.H{"error": "用户不存在"})
            return
        }
        response.InternalError(c)
        return
    }
    response.OK(c, user)
}

// POST /api/v1/users
func (h *UserHandler) Create(c *gin.Context) {
    var req service.CreateUserInput
    if err := c.ShouldBindJSON(&req); err != nil {
        response.BadRequest(c, "VALIDATION_ERROR", "参数校验失败", err.Error())
        return
    }

    user, err := h.svc.CreateUser(c.Request.Context(), req)
    if err != nil {
        if errors.Is(err, service.ErrEmailExists) {
            response.BadRequest(c, "EMAIL_EXISTS", "邮箱已被注册")
            return
        }
        response.InternalError(c)
        return
    }
    response.Created(c, user)
}

// PATCH /api/v1/users/:id
func (h *UserHandler) Update(c *gin.Context) {
    id, _ := strconv.ParseInt(c.Param("id"), 10, 64)

    // 确认只更新自己 (或管理员)
    currentUserID := c.GetInt64("user_id")
    if id != currentUserID {
        response.Unauthorized(c, "无权修改其他用户")
        return
    }

    var req service.UpdateUserInput
    if err := c.ShouldBindJSON(&req); err != nil {
        response.BadRequest(c, "VALIDATION_ERROR", "参数错误", err.Error())
        return
    }

    user, err := h.svc.UpdateUser(c.Request.Context(), id, req)
    if err != nil {
        response.InternalError(c)
        return
    }
    response.OK(c, user)
}

// DELETE /api/v1/users/:id
func (h *UserHandler) Delete(c *gin.Context) {
    id, _ := strconv.ParseInt(c.Param("id"), 10, 64)

    if err := h.svc.DeleteUser(c.Request.Context(), id); err != nil {
        if errors.Is(err, service.ErrNotFound) {
            c.JSON(404, gin.H{"error": "用户不存在"})
            return
        }
        response.InternalError(c)
        return
    }
    c.Status(204)
}

5. API 文档:Swagger/OpenAPI

# 安装 swag 工具
go install github.com/swaggo/swag/cmd/swag@latest
go get github.com/swaggo/gin-swagger
go get github.com/swaggo/files
// @title           用户管理 API
// @version         1.0
// @description     用户管理服务 API 文档
// @host            localhost:8080
// @BasePath        /api/v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization

package main

// CreateUser godoc
// @Summary      创建用户
// @Description  创建一个新用户账户
// @Tags         users
// @Accept       json
// @Produce      json
// @Param        request  body      CreateUserRequest  true  "用户信息"
// @Success      201      {object}  Response{data=User}
// @Failure      400      {object}  Response
// @Failure      500      {object}  Response
// @Security     BearerAuth
// @Router       /users [post]
func createUser(c *gin.Context) { /* ... */ }

// 在路由中注册文档
import (
    ginSwagger "github.com/swaggo/gin-swagger"
    swaggerFiles "github.com/swaggo/files"
    _ "myapp/docs"  // swagger 生成的文档
)

func setupRouter() *gin.Engine {
    r := gin.Default()
    // 访问 http://localhost:8080/swagger/index.html
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    return r
}

// 生成文档
// $ swag init -g cmd/server/main.go
🎯

REST API 设计要点

① 使用名词复数表示资源,不用动词(/users 不用 /getUsers)  ② 版本化 API(/api/v1)  ③ 统一错误响应格式  ④ 分页用 cursor 或 page-size,不用 offset-limit(大数据集)  ⑤ 始终返回 Content-Type: application/json

6. 标准库 net/http 替代 Gin 的实践指南

Go 1.22 的 ServeMux 增强后,对于不需要 Gin 的表单绑定、渲染引擎、复杂中间件链的项目,可以完全使用标准库构建生产级 REST API,减少依赖。以下展示等价的对照实现。

Gin 写法

r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"id": id})
})
r.Run(":8080")

标准库写法(Go 1.22+)

mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    json.NewEncoder(w).Encode(map[string]string{"id": id})
})
http.ListenAndServe(":8080", mux)

构建完整的标准库中间件链

package main

import (
    "context"
    "encoding/json"
    "log/slog"
    "net/http"
    "os"
    "time"
)

// ─── 中间件类型别名(清晰表达意图) ──────────────────────────
type Middleware func(http.Handler) http.Handler

// ─── 链式组合中间件 ───────────────────────────────────────────
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
    // 从右向左包装,保证从左向右执行
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}

// ─── 请求 ID 中间件 ───────────────────────────────────────────
type ctxKey string
const RequestIDKey ctxKey = "request_id"

func RequestID(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        id := r.Header.Get("X-Request-Id")
        if id == "" {
            id = fmt.Sprintf("%d", time.Now().UnixNano()) // 简化版,生产用 UUID
        }
        ctx := context.WithValue(r.Context(), RequestIDKey, id)
        w.Header().Set("X-Request-Id", id)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// ─── 日志中间件(配合 slog) ──────────────────────────────────
func Logger(logger *slog.Logger) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            rw := &responseRecorder{ResponseWriter: w, status: 200}
            next.ServeHTTP(rw, r)
            reqID, _ := r.Context().Value(RequestIDKey).(string)
            logger.Info("http",
                "request_id", reqID,
                "method",     r.Method,
                "path",       r.URL.Path,
                "status",     rw.status,
                "latency",    time.Since(start).String(),
                "bytes",      rw.bytes,
            )
        })
    }
}

type responseRecorder struct {
    http.ResponseWriter
    status int
    bytes  int
}
func (rw *responseRecorder) WriteHeader(s int) { rw.status = s; rw.ResponseWriter.WriteHeader(s) }
func (rw *responseRecorder) Write(b []byte) (int, error) {
    n, err := rw.ResponseWriter.Write(b); rw.bytes += n; return n, err
}

// ─── 速率限制中间件(令牌桶,无外部依赖) ────────────────────
func RateLimit(rps int) Middleware {
    // 简化实现:生产用 golang.org/x/time/rate
    tokens := make(chan struct{}, rps)
    for i := 0; i < rps; i++ { tokens <- struct{}{} }

    go func() {
        ticker := time.NewTicker(time.Second / time.Duration(rps))
        for range ticker.C {
            select {
            case tokens <- struct{}{}:
            default:
            }
        }
    }()

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            select {
            case <-tokens:
                next.ServeHTTP(w, r)
            default:
                w.Header().Set("Retry-After", "1")
                http.Error(w, `{"error":"rate limit exceeded"}`, http.StatusTooManyRequests)
            }
        })
    }
}

// ─── 主入口:组合所有中间件 ───────────────────────────────────
func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    mux := http.NewServeMux()

    // 注册路由
    mux.HandleFunc("GET /api/v1/users", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode([]map[string]any{{"id": 1, "name": "Alice"}})
    })

    // 按顺序应用中间件:RequestID → Logger → RateLimit → mux
    handler := Chain(mux,
        RequestID,
        Logger(logger),
        RateLimit(100),  // 每秒 100 请求
    )

    http.ListenAndServe(":8080", handler)
}
⚠️

常见误区:忘记设置 Content-Type

使用标准库时,json.NewEncoder(w).Encode(data) 不会自动设置 Content-Type: application/json,必须在 Encode 之前调用 w.Header().Set("Content-Type", "application/json")。一旦调用了 WriteHeaderWrite,Header 就无法再修改。Gin 的 c.JSON() 会自动处理这一点。