问题修复
在 Go 中构建 RESTful API 时选择…
适用于构建或优化 Go HTTP API、选择框架、实现认证/限流/CORS/校验/分页/客户端重试与测试的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Go 中构建 RESTful API通用
解决思路
- net/http 路由:用 http.NewServeMux() 创建 mux;用 mux.HandleFunc(“GET /users/{id}”, getUserHandler)、“POST /users”、“GET /users” 注册;在处理器中用 r.PathValue(“id”) 取路径参数;设置 w.Header().Set(“Content-Type”, “application/json”);用 json.NewEncoder(w).Encode(user) 返回 JSON。
- net/http 中间件:写 loggingMiddleware(next http.Handler) http.Handler,返回 http.HandlerFunc,在其中记录 r.Method 和 r.URL.Path,再调用 next.ServeHTTP(w, r);用 handler := loggingMiddleware(mux) 包装处理器。
- Chi 安装与路由:执行 go get -u github.com/go-chi/chi/v5;用 chi.NewRouter() 创建路由器;用 r.Use(middleware.Logger)、middleware.Recoverer、middleware.RequestID;用 r.Route(“/api/v1”, func(r chi.Router){...}) 编写分组和嵌套路由;在组内用 r.Get、r.Post、r.Put、r.Delete 注册。
- Chi 参数与上下文:在嵌套路由挂载 r.Use(UserContext);UserContext 中用 chi.URLParam(r, “userID”) 取参数;用 context.WithValue(r.Context(), “user”, userID) 创建 ctx;调用 next.ServeHTTP(w, r.WithContext(ctx));在处理器中用 r.Context().Value(“user”).(string) 读取。
- Gin 安装与路由:执行 go get -u github.com/gin-gonic/gin;用 r := gin.Default() 创建带 Logger 和 Recovery 的引擎;用 r.Group(“/api/v1”) 和 api.Group(“/users”) 分组;用 users.GET(“”)、users.POST(“”)、users.GET(“/:id”)、users.PUT(“/:id”)、users.DELETE(“/:id”) 注册;调用 r.Run(“:8080”)。
- Gin 绑定校验与取参:定义请求结构体字段并加 tag,如 json:“name” binding:“required,min=3”、json:“email” binding:“required,email”、json:“age” binding:“required,gte=18”;在处理器中调用 c.ShouldBindJSON(&req),失败返回 c.JSON(http.StatusBadRequest, gin.H{“error”: err.Error()});用 c.Param(“id”) 取路径参数;用 c.JSON 返回。
- Echo 安装与路由:执行 go get -u github.com/labstack/echo/v4;用 e := echo.New() 创建;用 e.Use(middleware.Logger())、middleware.Recover()、middleware.CORS();用 e.GET(“/users/:id”, getUser)、e.POST(“/users”, createUser)、e.PUT、e.DELETE 注册;调用 e.Logger.Fatal(e.Start(“:8080”))。
- Echo 绑定校验与错误:处理器返回 error;用 c.Param(“id”) 取路径参数;用 c.Bind(&user) 绑定;用 c.Validate(&user) 校验;错误时返回 echo.NewHTTPError(http.StatusBadRequest, err.Error());自定义 customErrorHandler 判断 *echo.HTTPError,读取 he.Code 和 he.Message,用 c.JSON(code, map[string]string{“error”: message}) 输出。
- Fiber 安装与路由:执行 go get -u github.com/gofiber/fiber/v2;用 app := fiber.New() 创建;用 app.Use(logger.New())、cors.New();用 app.Group(“/api/v1”) 和 api.Group(“/users”) 分组;用 users.Get(“/”)、users.Post(“/”)、users.Get(“/:id”)、users.Put(“/:id”)、users.Delete(“/:id”) 注册;调用 app.Listen(“:8080”)。
- Fiber 取参与解析:用 c.Params(“id”) 取路径参数;用 c.BodyParser(&user) 解析请求体;失败返回 c.Status(400).JSON(fiber.Map{“error”: err.Error()});成功返回 c.Status(201).JSON(user)。
- 框架决策:简单/少依赖用 net/http;Go 1.22+ 用新版 ServeMux 模式路由;Go < 1.22 或需要更好路由用 Chi;最大性能 JSON API 用 Gin;企业 OpenAPI/类型安全用 Echo;Express.js 团队且需要 WebSocket 用 Fiber,但注意其基于 fasthttp。
- 请求校验:引入 github.com/go-playground/validator/v10;定义 struct tag validate:“required,min=3,max=50”、“required,email”、“required,gte=18,lte=120”、“required,min=8”;var validate = validator.New();调用 validate.Struct(req);先 json.NewDecoder(r.Body).Decode(&req),再校验,失败用 http.Error(..., http.StatusBadRequest)。
- 自定义校验:调用 validate.RegisterValidation(“username”, validateUsername);在 validateUsername(fl validator.FieldLevel) 中取 fl.Field().String() 并写长度/字符逻辑;在结构体 tag 中用 validate:“required,username”。
- 认证中间件:从 r.Header.Get(“Authorization”) 取 token;token 为空或 validateToken(token) 失败时用 http.Error(..., http.StatusUnauthorized);成功用 context.WithValue(r.Context(), “userID”, userID) 和 r.WithContext(ctx) 传递。
- 限流中间件:使用 golang.org/x/time/rate;RateLimiter 包含 map[string]*rate.Limiter、sync.RWMutex、rate、burst;getLimiter(ip) 加锁查找或创建 rate.NewLimiter(rl.rate, rl.burst);中间件取 r.RemoteAddr,limiter.Allow() 失败时 http.Error(“Rate limit exceeded”, http.StatusTooManyRequests)。
- CORS 中间件:设置 Access-Control-Allow-Origin: *、Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS、Access-Control-Allow-Headers: Content-Type, Authorization;遇到 OPTIONS 时 w.WriteHeader(http.StatusOK) 并返回;否则调用 next.ServeHTTP。
- 自定义 API 错误:定义 APIError{Code int, Message string, Details string},实现 Error() string;提供 NewBadRequestError、NewNotFoundError、NewInternalError 构造函数。
- 错误处理中间件:定义 APIHandler func(w, r) error;ErrorHandlerMiddleware(h APIHandler) 调用 h,err 为 nil 直接返回;用 errors.As(err, &apiErr) 判断 *APIError,设置 Content-Type,WriteHeader(apiErr.Code),Encode(apiErr);未知错误 log.Printf 并返回 500 通用消息;注册 mux.Handle(“GET /users/{id}”, ErrorHandlerMiddleware(getUserHandler))。
- REST 资源命名:集合用复数名词;用 GET /api/v1/users、POST /api/v1/users、GET/PUT/PATCH/DELETE /api/v1/users/{id};嵌套资源用 GET/POST /api/v1/users/{id}/posts、GET /api/v1/users/{id}/posts/{pid};避免 URL 中动词,如 /users/create 或 /users/get/{id}。
- 分页:解析 page 和 page_size 查询参数;page < 1 设 1;pageSize < 1 或 > 100 设 20;查询数据后计算 totalPages := (totalCount + pageSize - 1) / pageSize;返回 PaginatedResponse{Data, Page, PageSize, TotalCount, TotalPages}。
- 查询过滤:定义 UserFilter{Status, Role, CreatedAt, Search};parseFilters 用 r.URL.Query().Get(“status”) 等读取;调用 db.GetUsersWithFilters(filters);示例 GET /api/v1/users?status=active&role=admin&search=john。
- HTTP 客户端:NewHTTPClient 设置 Timeout: 30 * time.Second;Transport 设置 MaxIdleConns: 100、MaxIdleConnsPerHost: 10、IdleConnTimeout: 90 * time.Second、DisableKeepAlives: false;用 http.NewRequestWithContext(ctx, “GET”, url, nil),设置 Accept: application/json 和 Authorization: Bearer + getToken(),client.Do,defer resp.Body.Close,检查 StatusOK,json.NewDecoder(resp.Body).Decode。
- 重试:RetryConfig{MaxRetries, BaseDelay, MaxDelay};DoWithRetry 循环 attempt 0..MaxRetries,成功返回;失败且未到最大时 delay := time.Duration(math.Pow(2, float64(attempt))) * BaseDelay,超过 MaxDelay 取 MaxDelay;select ctx.Done 或 time.After(delay);超过最大返回 fmt.Errorf(“max retries exceeded: %w”, err)。
- 测试处理器:用 httptest.NewServer(http.HandlerFunc(handler));defer ts.Close();http.Get(ts.URL + “/users/123”);检查 resp.StatusCode;decode 响应并断言字段。
- 测试中间件:用 httptest.NewRequest(“GET”, “/test”, nil);设置 Authorization header;用 httptest.NewRecorder();wrapped.ServeHTTP(rr, req);用表驱动测试检查 valid/missing/invalid token 对应 200/401。
- 响应压缩:GzipMiddleware 检查 r.Header.Get(“Accept-Encoding”) 是否包含 gzip;设置 Content-Encoding: gzip;gz := gzip.NewWriter(w);defer gz.Close();用 gzipResponseWriter 包装 ResponseWriter 并 next.ServeHTTP(gzw, r)。
- 生产连接池:NewProductionHTTPClient 设置 Timeout: 30s;Transport 设置 Proxy: http.ProxyFromEnvironment;DialContext 用 net.Dialer{Timeout:30s, KeepAlive:30s}.DialContext;ForceAttemptHTTP2: true;MaxIdleConns:100;MaxIdleConnsPerHost:10;IdleConnTimeout:90s;TLSHandshakeTimeout:10s;ExpectContinueTimeout:1s。
- 常见坑 1:不要不关闭响应体;http.Get 后 defer resp.Body.Close() 再 io.ReadAll。
- 常见坑 2:不要不用 context 控制超时;用 context.WithTimeout(context.Background(), 5*time.Second) 和 http.NewRequestWithContext。
- 常见坑 3:不要忽略 HTTP 状态码;检查 resp.StatusCode != http.StatusOK 返回错误。
- 常见坑 4:不要每个请求新建 http.Client;复用全局 httpClient = &http.Client{Timeout:30*time.Second} 以启用连接池。
- Go 惯用法质量:局部有初值用 :=,var 留给零值和包级声明;未使用 receiver 不命名;结构体指针用 &T{} 而非 new(T);处理 Decode/Write/仓库调用返回的错误;用 gofmt -s、staticcheck、errcheck、revive、golangci-lint 检查。
适用边界 · 注意事项
- 需要与 net/http 标准生态完全兼容时,不要选 Fiber,因为它基于 fasthttp,不兼容标准 http.Handler 接口。
- 简单 API 且可接受标准库写法时,不要为少量路由引入 Gin/Echo/Fiber 等重框架,以降低依赖和升级成本。
- 未使用 Go 1.22+ 且需要路径参数/方法路由时,不要依赖 net/http 新版 ServeMux,改用 Chi 等路由器。