# HTTP 接口层 - CLAUDE.md 本文件为 Claude Code 在 HTTP 接口层模块中工作时提供指导。 ## 🎯 模块概览 HTTP 接口层负责处理所有 HTTP 请求,包括路由定义、请求处理、响应格式化、中间件管理等。 ### 职责范围 - 🌐 HTTP 路由定义和管理 - 📝 请求处理和响应格式化 - 🛡️ 中间件管理和应用 - ✅ 请求参数验证 - 📊 HTTP 状态码管理 ### 文件结构 ``` internal/api/ ├── CLAUDE.md # 📋 当前文件 - API 接口指导 ├── handlers/ # 🎯 HTTP 处理器 │ ├── user.go # 用户相关处理器 │ ├── photo.go # 照片相关处理器 │ ├── category.go # 分类相关处理器 │ ├── tag.go # 标签相关处理器 │ ├── auth.go # 认证相关处理器 │ ├── upload.go # 上传相关处理器 │ └── health.go # 健康检查处理器 ├── middleware/ # 🛡️ 中间件 │ ├── auth.go # 认证中间件 │ ├── cors.go # CORS 中间件 │ ├── logger.go # 日志中间件 │ ├── rate_limit.go # 限流中间件 │ ├── recovery.go # 错误恢复中间件 │ └── validator.go # 验证中间件 ├── routes/ # 🗺️ 路由定义 │ ├── v1.go # API v1 路由 │ ├── auth.go # 认证路由 │ ├── public.go # 公共路由 │ └── admin.go # 管理员路由 └── validators/ # ✅ 请求验证器 ├── user.go # 用户验证器 ├── photo.go # 照片验证器 ├── category.go # 分类验证器 └── common.go # 通用验证器 ``` ## 🎯 处理器模式 ### 标准处理器结构 ```go type UserHandler struct { userService service.UserServiceInterface logger *zap.Logger } func NewUserHandler(userService service.UserServiceInterface, logger *zap.Logger) *UserHandler { return &UserHandler{ userService: userService, logger: logger, } } // 创建用户 func (h *UserHandler) Create(c *gin.Context) { var req validators.CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "请求参数无效", err) return } user, err := h.userService.Create(c.Request.Context(), req) if err != nil { h.logger.Error("创建用户失败", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "CREATE_USER_FAILED", "创建用户失败", err) return } response.Success(c, user, "用户创建成功") } ``` ### 处理器职责 1. **请求绑定**: 解析 HTTP 请求参数 2. **参数验证**: 验证请求参数的有效性 3. **服务调用**: 调用应用服务层处理业务逻辑 4. **响应格式化**: 格式化响应数据 5. **错误处理**: 处理和记录错误信息 ## 🗺️ 路由设计 ### API 版本管理 ```go // v1 路由组 v1 := router.Group("/api/v1") { // 公共路由 (无需认证) v1.POST("/auth/login", authHandler.Login) v1.POST("/auth/register", authHandler.Register) v1.GET("/photos/public", photoHandler.GetPublicPhotos) // 需要认证的路由 authenticated := v1.Group("") authenticated.Use(middleware.AuthRequired()) { authenticated.GET("/users/profile", userHandler.GetProfile) authenticated.PUT("/users/profile", userHandler.UpdateProfile) authenticated.POST("/photos", photoHandler.Create) authenticated.GET("/photos", photoHandler.List) } // 管理员路由 admin := v1.Group("/admin") admin.Use(middleware.AuthRequired(), middleware.AdminRequired()) { admin.GET("/users", userHandler.ListUsers) admin.DELETE("/users/:id", userHandler.DeleteUser) admin.GET("/photos/all", photoHandler.ListAllPhotos) } } ``` ### RESTful API 设计 ```go // 用户资源 GET /api/v1/users # 获取用户列表 POST /api/v1/users # 创建用户 GET /api/v1/users/:id # 获取用户详情 PUT /api/v1/users/:id # 更新用户 DELETE /api/v1/users/:id # 删除用户 // 照片资源 GET /api/v1/photos # 获取照片列表 POST /api/v1/photos # 创建照片 GET /api/v1/photos/:id # 获取照片详情 PUT /api/v1/photos/:id # 更新照片 DELETE /api/v1/photos/:id # 删除照片 // 分类资源 GET /api/v1/categories # 获取分类列表 POST /api/v1/categories # 创建分类 GET /api/v1/categories/:id # 获取分类详情 PUT /api/v1/categories/:id # 更新分类 DELETE /api/v1/categories/:id # 删除分类 ``` ## 🛡️ 中间件管理 ### 认证中间件 ```go func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { response.Error(c, http.StatusUnauthorized, "MISSING_TOKEN", "缺少认证令牌", nil) c.Abort() return } // 验证 JWT Token claims, err := jwt.VerifyToken(token) if err != nil { response.Error(c, http.StatusUnauthorized, "INVALID_TOKEN", "无效的认证令牌", err) c.Abort() return } // 设置用户信息到上下文 c.Set("user_id", claims.UserID) c.Set("user_role", claims.Role) c.Next() } } ``` ### 日志中间件 ```go func Logger(logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() duration := time.Since(start) logger.Info("HTTP Request", zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), zap.String("query", c.Request.URL.RawQuery), zap.Int("status", c.Writer.Status()), zap.Duration("duration", duration), zap.String("user_agent", c.Request.UserAgent()), zap.String("ip", c.ClientIP()), ) } } ``` ### CORS 中间件 ```go func CORS() gin.HandlerFunc { return func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") c.Header("Access-Control-Max-Age", "86400") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } ``` ### 限流中间件 ```go func RateLimit(limit int, window time.Duration) gin.HandlerFunc { limiter := rate.NewLimiter(rate.Every(window), limit) return func(c *gin.Context) { if !limiter.Allow() { response.Error(c, http.StatusTooManyRequests, "RATE_LIMIT_EXCEEDED", "请求过于频繁", nil) c.Abort() return } c.Next() } } ``` ## ✅ 请求验证 ### 验证器结构 ```go type CreateUserRequest struct { Username string `json:"username" binding:"required,min=3,max=20"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` Role string `json:"role" binding:"oneof=user editor admin"` } type UpdateUserRequest struct { Username string `json:"username,omitempty" binding:"omitempty,min=3,max=20"` Email string `json:"email,omitempty" binding:"omitempty,email"` Role string `json:"role,omitempty" binding:"omitempty,oneof=user editor admin"` } ``` ### 自定义验证器 ```go func ValidateUsername(fl validator.FieldLevel) bool { username := fl.Field().String() // 用户名只能包含字母、数字和下划线 matched, _ := regexp.MatchString(`^[a-zA-Z0-9_]+$`, username) return matched } func ValidatePhotoFormat(fl validator.FieldLevel) bool { format := fl.Field().String() allowedFormats := []string{"jpg", "jpeg", "png", "gif", "webp"} for _, allowed := range allowedFormats { if format == allowed { return true } } return false } ``` ### 验证错误处理 ```go func FormatValidationErrors(err error) map[string]string { errors := make(map[string]string) if validationErrors, ok := err.(validator.ValidationErrors); ok { for _, e := range validationErrors { field := strings.ToLower(e.Field()) switch e.Tag() { case "required": errors[field] = "此字段为必填项" case "email": errors[field] = "请输入有效的邮箱地址" case "min": errors[field] = fmt.Sprintf("最小长度为 %s", e.Param()) case "max": errors[field] = fmt.Sprintf("最大长度为 %s", e.Param()) default: errors[field] = "字段值无效" } } } return errors } ``` ## 📊 响应格式 ### 统一响应结构 ```go type Response struct { Success bool `json:"success"` Data interface{} `json:"data,omitempty"` Message string `json:"message"` Error *ErrorInfo `json:"error,omitempty"` Timestamp time.Time `json:"timestamp"` } type ErrorInfo struct { Code string `json:"code"` Message string `json:"message"` Details interface{} `json:"details,omitempty"` } ``` ### 响应帮助函数 ```go func Success(c *gin.Context, data interface{}, message string) { c.JSON(http.StatusOK, Response{ Success: true, Data: data, Message: message, Timestamp: time.Now(), }) } func Error(c *gin.Context, statusCode int, errorCode, message string, details interface{}) { c.JSON(statusCode, Response{ Success: false, Message: message, Error: &ErrorInfo{ Code: errorCode, Message: message, Details: details, }, Timestamp: time.Now(), }) } ``` ### 分页响应 ```go type PaginatedResponse struct { Data interface{} `json:"data"` Pagination Pagination `json:"pagination"` } type Pagination struct { Page int `json:"page"` PageSize int `json:"page_size"` Total int64 `json:"total"` TotalPages int `json:"total_pages"` HasNext bool `json:"has_next"` HasPrevious bool `json:"has_previous"` } ``` ## 🔍 错误处理 ### 错误分类 ```go const ( // 请求错误 ErrInvalidRequest = "INVALID_REQUEST" ErrMissingParameter = "MISSING_PARAMETER" ErrInvalidParameter = "INVALID_PARAMETER" // 认证错误 ErrMissingToken = "MISSING_TOKEN" ErrInvalidToken = "INVALID_TOKEN" ErrTokenExpired = "TOKEN_EXPIRED" ErrInsufficientPermission = "INSUFFICIENT_PERMISSION" // 业务错误 ErrUserNotFound = "USER_NOT_FOUND" ErrUserAlreadyExists = "USER_ALREADY_EXISTS" ErrPhotoNotFound = "PHOTO_NOT_FOUND" ErrCategoryNotFound = "CATEGORY_NOT_FOUND" // 系统错误 ErrInternalServer = "INTERNAL_SERVER_ERROR" ErrDatabaseError = "DATABASE_ERROR" ErrStorageError = "STORAGE_ERROR" ) ``` ### 错误恢复中间件 ```go func Recovery(logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { logger.Error("Panic recovered", zap.Any("error", err), zap.String("path", c.Request.URL.Path), zap.String("method", c.Request.Method), ) response.Error(c, http.StatusInternalServerError, "INTERNAL_SERVER_ERROR", "服务器内部错误", nil) } }() c.Next() } } ``` ## 📊 性能优化 ### 响应压缩 ```go func Compression() gin.HandlerFunc { return gzip.Gzip(gzip.DefaultCompression) } ``` ### 缓存控制 ```go func CacheControl(maxAge int) gin.HandlerFunc { return func(c *gin.Context) { c.Header("Cache-Control", fmt.Sprintf("max-age=%d", maxAge)) c.Next() } } ``` ### 请求大小限制 ```go func LimitRequestSize(maxSize int64) gin.HandlerFunc { return func(c *gin.Context) { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxSize) c.Next() } } ``` ## 🧪 测试策略 ### 处理器测试 ```go func TestUserHandler_Create(t *testing.T) { // 模拟服务 mockService := &MockUserService{} handler := NewUserHandler(mockService, zap.NewNop()) // 创建测试请求 reqBody := `{"username":"test","email":"test@example.com","password":"123456"}` req, _ := http.NewRequest("POST", "/api/v1/users", strings.NewReader(reqBody)) req.Header.Set("Content-Type", "application/json") // 执行请求 w := httptest.NewRecorder() router := gin.New() router.POST("/api/v1/users", handler.Create) router.ServeHTTP(w, req) // 验证结果 assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "success") } ``` ### 中间件测试 ```go func TestAuthMiddleware(t *testing.T) { middleware := AuthRequired() // 测试缺少 token req, _ := http.NewRequest("GET", "/api/v1/users", nil) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = req middleware(c) assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Contains(t, w.Body.String(), "MISSING_TOKEN") } ``` ## 📚 API 文档 ### Swagger 注释 ```go // CreateUser 创建用户 // @Summary 创建用户 // @Description 创建新用户账户 // @Tags 用户管理 // @Accept json // @Produce json // @Param user body validators.CreateUserRequest true "用户信息" // @Success 200 {object} response.Response{data=models.User} "成功创建用户" // @Failure 400 {object} response.Response "请求参数错误" // @Failure 500 {object} response.Response "服务器内部错误" // @Router /api/v1/users [post] func (h *UserHandler) Create(c *gin.Context) { // 处理器实现 } ``` ### API 文档生成 ```bash # 安装 swag go install github.com/swaggo/swag/cmd/swag@latest # 生成文档 swag init -g cmd/server/main.go # 启动时访问文档 # http://localhost:8080/swagger/index.html ``` ## 🔧 开发工具 ### 路由调试 ```go func PrintRoutes(router *gin.Engine) { routes := router.Routes() for _, route := range routes { fmt.Printf("[%s] %s -> %s\n", route.Method, route.Path, route.Handler) } } ``` ### 请求日志 ```go func RequestLogger() gin.HandlerFunc { return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { return fmt.Sprintf("[%s] %s %s %d %s\n", param.TimeStamp.Format("2006-01-02 15:04:05"), param.Method, param.Path, param.StatusCode, param.Latency, ) }) } ``` ## 💡 最佳实践 ### 处理器设计 - 保持处理器轻量,业务逻辑放在服务层 - 统一错误处理和响应格式 - 完善的参数验证和错误提示 - 适当的日志记录 ### 中间件使用 - 按需应用中间件,避免过度使用 - 注意中间件的执行顺序 - 错误处理中间件应该最先应用 - 认证中间件应该在业务中间件之前 ### 路由设计 - 遵循 RESTful 设计原则 - 合理的路由分组和版本管理 - 清晰的路由命名和结构 - 适当的权限控制 ### 性能考虑 - 使用响应压缩减少传输大小 - 适当的缓存控制 - 限制请求大小和频率 - 异步处理耗时操作 本模块是整个 API 的入口和门面,确保接口设计合理、响应格式统一、错误处理完善是项目成功的关键。