第一章:RESTful API 设计原则回顾
1.1 资源导向设计
- URI 表示资源,而非动作
✅/users(用户集合)
✅/users/123(ID 为 123 的用户)
❌/getUsers、/deleteUser?id=123 - HTTP 方法表示操作
方法 语义 幂等 安全 GET| 获取资源 | 是 | 是POST| 创建子资源 | 否 | 否PUT| 全量更新 | 是 | 否PATCH| 部分更新 | 否 | 否DELETE| 删除 | 是 | 否
- 状态码标准化
场景 状态码 - 成功创建 |
201 Created - 请求成功 |
200 OK - 无内容返回 |
204 No Content - 资源不存在 |
404 Not Found - 输入无效 |
400 Bad Request - 未认证 |
401 Unauthorized - 无权限 |
403 Forbidden - 服务器错误 |
500 Internal Server Error
- 成功创建 |
第二章:中间件(Middleware)—— 请求处理的横切关注点
2.1 什么是中间件?
中间件是包装 HTTP 处理器的函数,用于处理与业务逻辑无关的通用功能:
- 日志记录
- 身份认证
- 请求限流
- CORS 头设置
- Panic 恢复
- 请求 ID 追踪
2.2 中间件签名(标准库风格)
// Middleware 是中间件类型 type Middleware func(http.Handler) http.Handler // Chain 组合多个中间件 func Chain(mw ...Middleware) Middleware { return func(next http.Handler) http.Handler { for i := len(mw) - 1; i >= 0; i-- { next = mw[i](next) } return next } }执行顺序:从右到左(最外层最先执行)
2.3 实现核心中间件
(1) LoggingMiddleware(结构化日志)
// internal/middleware/logging.go func LoggingMiddleware(logger *logrus.Logger) Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() // 生成唯一请求 ID reqID := uuid.New().String() ctx := context.WithValue(r.Context(), "req_id", reqID) // 调用下一个处理器 next.ServeHTTP(w, r.WithContext(ctx)) // 记录日志 logger.WithFields(logrus.Fields{ "req_id": reqID, "method": r.Method, "path": r.URL.Path, "ip": r.RemoteAddr, "latency": time.Since(start), "status": w.(*responseWrapper).StatusCode(), // 需包装 ResponseWriter }).Info("Request completed") }) } }关键:需包装
ResponseWriter以捕获状态码(见 2.4)。
(2) RecoveryMiddleware(Panic 恢复)
func RecoveryMiddleware(logger *logrus.Logger) Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { logger.WithField("panic", err).Error("Handler panicked") http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } }作用:防止单个请求 panic 导致整个服务崩溃。
(3) CORSMiddleware(跨域支持)
func CORSMiddleware() Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) } }2.4 包装 ResponseWriter 捕获状态码
标准库http.ResponseWriter不提供StatusCode()方法,需自定义:
// internal/middleware/response_wrapper.go type responseWrapper struct { http.ResponseWriter statusCode int } func (rw *responseWrapper) WriteHeader(code int) { rw.statusCode = code rw.ResponseWriter.WriteHeader(code) } func (rw *responseWrapper) StatusCode() int { if rw.statusCode == 0 { return http.StatusOK } return rw.statusCode } // 在中间件中使用 wrapped := &responseWrapper{ResponseWriter: w, statusCode: 0} next.ServeHTTP(wrapped, r)第三章:路由选择 —— 标准库 vs chi vs gin
3.1 标准库net/http的局限
- 无路径参数(如
/users/{id}) - 无嵌套路由
- 无中间件链原生支持
适用场景:极简服务、学习原理
3.2 推荐:chi —— 轻量、标准库兼容、中间件友好
- 特点:
- 基于标准库
http.Handler - 支持路径参数、中间件、子路由
- 无魔法,代码可读性强
- 社区活跃(K8s 生态广泛使用)
- 基于标准库
go get github.com/go-chi/chi/v5路由示例
r := chi.NewRouter() r.Use(middleware.Logger) // chi 内置中间件 r.Route("/api/v1", func(r chi.Router) { r.Get("/healthz", healthHandler) r.Route("/users", func(r chi.Router) { r.Use(authMiddleware) // 仅对 /users 子树生效 r.Get("/", listUsers) r.Post("/", createUser) r.Route("/{userID}", func(r chi.Router) { r.Get("/", getUser) r.Put("/", updateUser) r.Delete("/", deleteUser) }) }) })优势:中间件可作用于特定路由子树,权限控制更精细。
3.3 对比:gin —— 高性能但“非标准”
- 优点:性能略高(基准测试)、内置验证
- 缺点:
- 自定义
*gin.Context,与标准库不兼容 - “魔法”较多(如
c.BindJSON隐式处理) - 升级 Breaking Changes 频繁
- 自定义
建议:新项目优先选chi,除非有极致性能需求。
第四章:请求验证与结构化错误
4.1 使用 validator.v10 校验输入
go get github.com/go-playground/validator/v10定义带标签的结构体
// internal/model/user.go type CreateUserRequest struct { Name string `json:"name" validate:"required,min=2,max=50"` Email string `json:"email" validate:"required,email"` Role string `json:"role" validate:"required,oneof=admin user"` }在 Handler 中验证
// internal/handler/user.go func CreateUser(w http.ResponseWriter, r *http.Request) { var req CreateUserRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "Invalid JSON") return } validate := validator.New() if err := validate.Struct(req); err != nil { errs := make([]string, 0) for _, e := range err.(validator.ValidationErrors) { errs = append(errs, fmt.Sprintf("%s: %s", e.Field(), e.Tag())) } respondError(w, http.StatusBadRequest, strings.Join(errs, "; ")) return } // ... 业务逻辑 }4.2 统一错误响应格式
// internal/handler/error.go type ErrorResponse struct { Code int `json:"code"` Message string `json:"message"` } func respondError(w http.ResponseWriter, code int, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) json.NewEncoder(w).Encode(ErrorResponse{ Code: code, Message: message, }) }响应示例:
{ "code": 400, "message": "Email: email" }第五章:OpenAPI 文档自动化 —— swaggo
5.1 为什么需要自动生成文档?
- 手写 Swagger YAML 易过时
- 自动生成确保代码与文档一致
- 提供交互式 UI(Swagger UI)
5.2 集成 swaggo
go install github.com/swaggo/swag/cmd/swag@latest go get github.com/swaggo/http-swagger在代码中添加注释
// @Summary 创建用户 // @Description 创建新用户,需 admin 权限 // @Tags users // @Accept json // @Produce json // @Param user body CreateUserRequest true "用户信息" // @Success 201 {object} User // @Failure 400 {object} ErrorResponse // @Failure 401 {object} ErrorResponse // @Failure 403 {object} ErrorResponse // @Router /api/v1/users [post] func CreateUser(w http.ResponseWriter, r *http.Request) { // ... }生成文档
swag init --dir cmd/my-gateway,internal/handler --generalInfo cmd/my-gateway/main.go生成docs/目录,包含swagger.json和swagger.yaml。
暴露 Swagger UI
// main.go import "github.com/swaggo/http-swagger" func main() { r := chi.NewRouter() r.Get("/swagger/*", httpSwagger.WrapHandler) // ... }访问http://localhost:8080/swagger/index.html即可查看交互式文档。
第六章:实战 —— 带认证与权限的用户管理 API
6.1 功能清单
POST /api/v1/auth/login→ 返回 JWT TokenGET /api/v1/users→ 列出所有用户(admin only)POST /api/v1/users→ 创建用户(admin only)GET /api/v1/users/{id}→ 获取用户(本人或 admin)PUT /api/v1/users/{id}→ 更新用户(本人或 admin)DELETE /api/v1/users/{id}→ 删除用户(admin only)
6.2 JWT 认证实现
生成 Token
// internal/auth/jwt.go func GenerateToken(userID string, role string) (string, error) { claims := jwt.MapClaims{ "user_id": userID, "role": role, "exp": time.Now().Add(24 * time.Hour).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte("your-secret-key")) }认证中间件
func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { respondError(w, http.StatusUnauthorized, "Missing Authorization header") return } tokenStr := strings.TrimPrefix(authHeader, "Bearer ") token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { return []byte("your-secret-key"), nil }) if err != nil || !token.Valid { respondError(w, http.StatusUnauthorized, "Invalid token") return } claims := token.Claims.(jwt.MapClaims) ctx := context.WithValue(r.Context(), "user_id", claims["user_id"]) ctx = context.WithValue(ctx, "role", claims["role"]) next.ServeHTTP(w, r.WithContext(ctx)) }) }6.3 RBAC 权限中间件
func RequireRole(requiredRole string) Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { role := r.Context().Value("role").(string) if role != requiredRole { respondError(w, http.StatusForbidden, "Insufficient permissions") return } next.ServeHTTP(w, r) }) } }路由配置
r.Route("/api/v1", func(r chi.Router) { r.Post("/auth/login", loginHandler) r.Route("/users", func(r chi.Router) { r.Use(AuthMiddleware) // 所有 /users 需认证 r.Get("/", RequireRole("admin")(listUsers)) // 仅 admin 可列出 r.Post("/", RequireRole("admin")(createUser)) r.Route("/{userID}", func(r chi.Router) { r.Use(CheckOwnership) // 自定义中间件:检查是否本人 r.Get("/", getUser) r.Put("/", updateUser) r.Delete("/", RequireRole("admin")(deleteUser)) }) }) })CheckOwnership:比较
userID路径参数与ctx.Value("user_id")。
第七章:测试策略
7.1 Handler 单元测试
func TestCreateUser_InvalidEmail(t *testing.T) { reqBody := `{"name":"Alice","email":"invalid","role":"user"}` req := httptest.NewRequest("POST", "/api/v1/users", strings.NewReader(reqBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router := setupRouter() // 返回 chi.Router router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, w.Body.String(), "email") }7.2 集成测试(含数据库)
- 使用
testify/suite管理测试生命周期 - 在
TestMain中启动临时 PostgreSQL(Docker) - 每个测试用例运行前清空数据
结语:API 是系统的门面
一个设计良好的 API,不仅是功能的入口,更是用户体验、安全边界、系统契约的体现。
通过本篇,你已具备构建企业级 Go Web 服务的核心能力。