Files
photography/backend/internal/api/CLAUDE.md
xujiang a2f2f66f88
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 1m37s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped
refactor: 重构后端架构,采用 Go 风格四层设计模式
## 主要变更

### 🏗️ 架构重构
- 采用简洁的四层架构:API → Service → Repository → Model
- 遵循 Go 语言最佳实践和命名规范
- 实现依赖注入和接口导向设计
- 统一错误处理和响应格式

### 📁 目录结构优化
- 删除重复模块 (application/, domain/, infrastructure/ 等)
- 规范化命名 (使用 Go 风格的 snake_case)
- 清理无关文件 (package.json, node_modules/ 等)
- 新增规范化的测试目录结构

### 📚 文档系统
- 为每个模块创建详细的 CLAUDE.md 指导文件
- 包含开发规范、最佳实践和使用示例
- 支持模块化开发,缩短上下文长度

### 🔧 开发规范
- 统一接口命名规范 (UserServicer, PhotoRepositoryr)
- 标准化错误处理机制
- 完善的测试策略 (单元测试、集成测试、性能测试)
- 规范化的配置管理

### 🗂️ 新增文件
- cmd/server/ - 服务启动入口和配置
- internal/model/ - 数据模型层 (entity, dto, request)
- pkg/ - 共享工具包 (logger, response, validator)
- tests/ - 完整测试结构
- docs/ - API 文档和架构设计
- .gitignore - Git 忽略文件配置

### 🗑️ 清理内容
- 删除 Node.js 相关文件 (package.json, node_modules/)
- 移除重复的架构目录
- 清理临时文件和构建产物
- 删除重复的文档文件

## 影响
- 提高代码可维护性和可扩展性
- 统一开发规范,提升团队协作效率
- 优化项目结构,符合 Go 语言生态标准
- 完善文档体系,降低上手难度
2025-07-10 11:20:59 +08:00

16 KiB

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       # 通用验证器

🎯 处理器模式

标准处理器结构

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 版本管理

// 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 设计

// 用户资源
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 # 删除分类

🛡️ 中间件管理

认证中间件

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()
    }
}

日志中间件

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 中间件

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()
    }
}

限流中间件

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()
    }
}

请求验证

验证器结构

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"`
}

自定义验证器

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
}

验证错误处理

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
}

📊 响应格式

统一响应结构

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"`
}

响应帮助函数

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(),
    })
}

分页响应

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"`
}

🔍 错误处理

错误分类

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"
)

错误恢复中间件

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()
    }
}

📊 性能优化

响应压缩

func Compression() gin.HandlerFunc {
    return gzip.Gzip(gzip.DefaultCompression)
}

缓存控制

func CacheControl(maxAge int) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("Cache-Control", fmt.Sprintf("max-age=%d", maxAge))
        c.Next()
    }
}

请求大小限制

func LimitRequestSize(maxSize int64) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxSize)
        c.Next()
    }
}

🧪 测试策略

处理器测试

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")
}

中间件测试

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 注释

// 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 文档生成

# 安装 swag
go install github.com/swaggo/swag/cmd/swag@latest

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

# 启动时访问文档
# http://localhost:8080/swagger/index.html

🔧 开发工具

路由调试

func PrintRoutes(router *gin.Engine) {
    routes := router.Routes()
    for _, route := range routes {
        fmt.Printf("[%s] %s -> %s\n", route.Method, route.Path, route.Handler)
    }
}

请求日志

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 的入口和门面,确保接口设计合理、响应格式统一、错误处理完善是项目成功的关键。