# 后端模块 - CLAUDE.md 此文件为 Claude Code 在后端模块工作时提供指导。 ## 🎯 模块概览 后端模块提供摄影作品集项目的 API 服务,包括照片管理、用户认证、数据存储等核心功能。 ### 功能特性 - 🔐 用户认证和授权管理 - 📸 照片和相册 CRUD 操作 - 📁 文件上传和存储管理 - 🎨 主题和配置管理 - 📊 访问统计和数据分析 - 🔄 数据同步和缓存 ### 技术栈 - **语言**: Go 1.21+ - **框架**: Gin + GORM - **数据库**: PostgreSQL / MySQL - **缓存**: Redis - **存储**: MinIO / AWS S3 - **认证**: JWT Token - **部署**: Docker + Docker Compose ## 📁 项目结构 ``` backend/ ├── CLAUDE.md # 🔍 当前文件 - 后端开发指导 ├── cmd/ # 应用入口 │ └── server/ # 服务器启动 │ └── main.go ├── internal/ # 内部模块 │ ├── api/ # API 层 │ │ ├── handlers/ # 请求处理器 │ │ ├── middleware/ # 中间件 │ │ └── routes/ # 路由定义 │ ├── service/ # 业务逻辑层 │ │ ├── auth/ # 认证服务 │ │ ├── photo/ # 照片服务 │ │ ├── album/ # 相册服务 │ │ └── user/ # 用户服务 │ ├── repository/ # 数据访问层 │ │ ├── postgres/ # PostgreSQL 实现 │ │ └── redis/ # Redis 实现 │ ├── models/ # 数据模型 │ ├── config/ # 配置管理 │ └── utils/ # 工具函数 ├── pkg/ # 公共包 │ ├── logger/ # 日志包 │ ├── validator/ # 验证包 │ └── response/ # 响应包 ├── migrations/ # 数据库迁移 ├── docs/ # API 文档 ├── scripts/ # 部署脚本 ├── configs/ # 配置文件 │ ├── config.yaml # 主配置 │ ├── config.dev.yaml # 开发环境 │ └── config.prod.yaml # 生产环境 ├── docker-compose.yml # 容器编排 ├── Dockerfile # 容器构建 ├── Makefile # 构建脚本 ├── go.mod # Go 模块定义 ├── go.sum # 依赖校验 └── README.md # 模块说明 ``` ## 🚀 开发环境配置 ### 环境要求 - **Go**: 1.21+ - **Docker**: 20.10+ - **Docker Compose**: 2.0+ - **PostgreSQL**: 14+ - **Redis**: 6.2+ ### 快速启动 ```bash # 进入后端目录 cd backend/ # 启动开发环境 (Docker) make dev-up # 运行数据库迁移 make migrate # 启动开发服务器 make dev # 或者直接运行 go run cmd/server/main.go ``` ### 环境变量配置 ```bash # .env # 数据库配置 DB_HOST=localhost DB_PORT=5432 DB_USER=postgres DB_PASSWORD=password DB_NAME=photography # Redis 配置 REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= # JWT 配置 JWT_SECRET=your-secret-key JWT_EXPIRES_IN=24h # 文件存储配置 STORAGE_TYPE=local # local, s3, minio STORAGE_PATH=./uploads AWS_REGION=us-east-1 AWS_BUCKET=photography-bucket # 服务器配置 PORT=8080 GIN_MODE=debug ``` ## 🏗️ 项目架构 ### 分层架构 ``` ┌─────────────────┐ │ API Layer │ ← handlers, middleware, routes ├─────────────────┤ │ Service Layer │ ← business logic ├─────────────────┤ │Repository Layer │ ← data access ├─────────────────┤ │ Models Layer │ ← data structures └─────────────────┘ ``` ### 目录结构详解 #### 🎯 API 层 ``` internal/api/ ├── handlers/ # 请求处理器 │ ├── auth.go # 认证相关 │ ├── photo.go # 照片管理 │ ├── album.go # 相册管理 │ ├── user.go # 用户管理 │ └── upload.go # 文件上传 ├── middleware/ # 中间件 │ ├── auth.go # 认证中间件 │ ├── cors.go # CORS 处理 │ ├── logger.go # 日志中间件 │ └── rate_limit.go # 限流中间件 └── routes/ # 路由定义 ├── api_v1.go # API v1 路由 └── admin.go # 管理后台路由 ``` #### 💼 业务逻辑层 ``` internal/service/ ├── auth/ # 认证服务 │ ├── auth.go # 认证逻辑 │ ├── jwt.go # JWT 处理 │ └── permission.go # 权限控制 ├── photo/ # 照片服务 │ ├── photo.go # 照片操作 │ ├── metadata.go # 元数据处理 │ └── thumbnail.go # 缩略图生成 ├── album/ # 相册服务 │ ├── album.go # 相册操作 │ └── organization.go # 组织管理 └── user/ # 用户服务 ├── user.go # 用户管理 └── profile.go # 用户资料 ``` #### 🗄️ 数据访问层 ``` internal/repository/ ├── postgres/ # PostgreSQL 实现 │ ├── photo.go # 照片数据操作 │ ├── album.go # 相册数据操作 │ ├── user.go # 用户数据操作 │ └── migration.go # 数据迁移 └── redis/ # Redis 实现 ├── cache.go # 缓存操作 └── session.go # 会话管理 ``` ## 🗃️ 数据库设计 ### 数据模型定义 ```go // internal/models/photo.go type Photo struct { ID uint `gorm:"primaryKey" json:"id"` Title string `gorm:"not null" json:"title"` Description string `json:"description"` URL string `gorm:"not null" json:"url"` ThumbnailURL string `json:"thumbnail_url"` AlbumID uint `json:"album_id"` UserID uint `json:"user_id"` Tags []Tag `gorm:"many2many:photo_tags" json:"tags"` Metadata PhotoMetadata `gorm:"type:jsonb" json:"metadata"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // internal/models/album.go type Album struct { ID uint `gorm:"primaryKey" json:"id"` Name string `gorm:"not null" json:"name"` Description string `json:"description"` CoverPhotoID *uint `json:"cover_photo_id"` CoverPhoto *Photo `gorm:"foreignKey:CoverPhotoID" json:"cover_photo"` Photos []Photo `gorm:"foreignKey:AlbumID" json:"photos"` IsPublic bool `gorm:"default:true" json:"is_public"` UserID uint `json:"user_id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // internal/models/user.go type User struct { ID uint `gorm:"primaryKey" json:"id"` Username string `gorm:"unique;not null" json:"username"` Email string `gorm:"unique;not null" json:"email"` Password string `gorm:"not null" json:"-"` Role string `gorm:"default:user" json:"role"` IsActive bool `gorm:"default:true" json:"is_active"` Profile UserProfile `gorm:"foreignKey:UserID" json:"profile"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } ``` ### 数据库迁移 ```sql -- migrations/001_create_users.sql CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, role VARCHAR(20) DEFAULT 'user', is_active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- migrations/002_create_albums.sql CREATE TABLE albums ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, description TEXT, cover_photo_id INTEGER, is_public BOOLEAN DEFAULT true, user_id INTEGER REFERENCES users(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- migrations/003_create_photos.sql CREATE TABLE photos ( id SERIAL PRIMARY KEY, title VARCHAR(200) NOT NULL, description TEXT, url VARCHAR(500) NOT NULL, thumbnail_url VARCHAR(500), album_id INTEGER REFERENCES albums(id), user_id INTEGER REFERENCES users(id), metadata JSONB, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` ## 🔌 API 接口设计 ### RESTful API 规范 ```go // internal/api/handlers/photo.go type PhotoHandler struct { photoService *service.PhotoService } // GET /api/v1/photos func (h *PhotoHandler) GetPhotos(c *gin.Context) { params := &service.PhotoListParams{} if err := c.ShouldBindQuery(params); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } photos, total, err := h.photoService.GetPhotos(params) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{ "data": photos, "total": total, "page": params.Page, "per_page": params.PerPage, }) } // POST /api/v1/photos func (h *PhotoHandler) CreatePhoto(c *gin.Context) { var req service.CreatePhotoRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } userID := c.GetUint("user_id") req.UserID = userID photo, err := h.photoService.CreatePhoto(&req) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(201, gin.H{"data": photo}) } // PUT /api/v1/photos/:id func (h *PhotoHandler) UpdatePhoto(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 32) var req service.UpdatePhotoRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } photo, err := h.photoService.UpdatePhoto(uint(id), &req) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"data": photo}) } // DELETE /api/v1/photos/:id func (h *PhotoHandler) DeletePhoto(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 32) err := h.photoService.DeletePhoto(uint(id)) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(204, nil) } ``` ### API 路由配置 ```go // internal/api/routes/api_v1.go func SetupAPIV1Routes(r *gin.Engine, handlers *Handlers) { api := r.Group("/api/v1") // 公开接口 api.GET("/photos", handlers.Photo.GetPhotos) api.GET("/photos/:id", handlers.Photo.GetPhoto) api.GET("/albums", handlers.Album.GetAlbums) api.GET("/albums/:id", handlers.Album.GetAlbum) // 认证相关 auth := api.Group("/auth") { auth.POST("/login", handlers.Auth.Login) auth.POST("/register", handlers.Auth.Register) auth.POST("/refresh", handlers.Auth.RefreshToken) } // 需要认证的接口 protected := api.Group("/", middleware.AuthRequired()) { protected.POST("/photos", handlers.Photo.CreatePhoto) protected.PUT("/photos/:id", handlers.Photo.UpdatePhoto) protected.DELETE("/photos/:id", handlers.Photo.DeletePhoto) protected.POST("/upload", handlers.Upload.UploadFile) } // 管理员接口 admin := api.Group("/admin", middleware.AdminRequired()) { admin.GET("/users", handlers.User.GetUsers) admin.PUT("/users/:id", handlers.User.UpdateUser) admin.DELETE("/users/:id", handlers.User.DeleteUser) } } ``` ## 🔐 认证和授权 ### JWT 认证实现 ```go // internal/service/auth/jwt.go type JWTService struct { secretKey []byte expiresIn time.Duration } func NewJWTService(secretKey string, expiresIn time.Duration) *JWTService { return &JWTService{ secretKey: []byte(secretKey), expiresIn: expiresIn, } } func (s *JWTService) GenerateToken(userID uint, role string) (string, error) { claims := jwt.MapClaims{ "user_id": userID, "role": role, "exp": time.Now().Add(s.expiresIn).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(s.secretKey) } func (s *JWTService) ValidateToken(tokenString string) (*jwt.Token, error) { return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return s.secretKey, nil }) } ``` ### 权限控制中间件 ```go // internal/api/middleware/auth.go func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { tokenString := c.GetHeader("Authorization") if tokenString == "" { c.JSON(401, gin.H{"error": "Authorization header required"}) c.Abort() return } // 移除 "Bearer " 前缀 if strings.HasPrefix(tokenString, "Bearer ") { tokenString = tokenString[7:] } token, err := jwtService.ValidateToken(tokenString) if err != nil || !token.Valid { c.JSON(401, gin.H{"error": "Invalid token"}) c.Abort() return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { c.JSON(401, gin.H{"error": "Invalid token claims"}) c.Abort() return } c.Set("user_id", uint(claims["user_id"].(float64))) c.Set("role", claims["role"].(string)) c.Next() } } func AdminRequired() gin.HandlerFunc { return gin.HandlerFunc(func(c *gin.Context) { AuthRequired()(c) if c.IsAborted() { return } role := c.GetString("role") if role != "admin" { c.JSON(403, gin.H{"error": "Admin access required"}) c.Abort() return } c.Next() }) } ``` ## 📁 文件存储管理 ### 存储接口设计 ```go // internal/service/storage/interface.go type StorageService interface { Upload(file multipart.File, filename string) (string, error) Delete(filename string) error GetURL(filename string) string } // internal/service/storage/local.go type LocalStorage struct { basePath string baseURL string } func (s *LocalStorage) Upload(file multipart.File, filename string) (string, error) { filepath := path.Join(s.basePath, filename) out, err := os.Create(filepath) if err != nil { return "", err } defer out.Close() _, err = io.Copy(out, file) if err != nil { return "", err } return s.GetURL(filename), nil } func (s *LocalStorage) GetURL(filename string) string { return fmt.Sprintf("%s/%s", s.baseURL, filename) } // internal/service/storage/s3.go type S3Storage struct { client *s3.Client bucket string region string } func (s *S3Storage) Upload(file multipart.File, filename string) (string, error) { _, err := s.client.PutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String(s.bucket), Key: aws.String(filename), Body: file, }) if err != nil { return "", err } return s.GetURL(filename), nil } ``` ### 图片处理 ```go // internal/service/photo/thumbnail.go import ( "image" "image/jpeg" "image/png" "golang.org/x/image/draw" ) func GenerateThumbnail(src image.Image, width, height int) (image.Image, error) { bounds := src.Bounds() srcWidth := bounds.Max.X srcHeight := bounds.Max.Y // 计算缩放比例 scaleX := float64(width) / float64(srcWidth) scaleY := float64(height) / float64(srcHeight) scale := math.Min(scaleX, scaleY) newWidth := int(float64(srcWidth) * scale) newHeight := int(float64(srcHeight) * scale) // 创建新图像 dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight)) // 缩放图像 draw.BiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil) return dst, nil } ``` ## 🚀 构建和部署 ### Makefile 配置 ```makefile # Makefile .PHONY: build run test clean docker-build docker-run # 构建 build: go build -o bin/server cmd/server/main.go # 运行 run: go run cmd/server/main.go # 测试 test: go test ./... # 代码检查 lint: golangci-lint run # 清理 clean: rm -rf bin/ # 数据库迁移 migrate: migrate -path migrations -database "postgres://user:pass@localhost/dbname?sslmode=disable" up # Docker 构建 docker-build: docker build -t photography-backend . # Docker 运行 docker-run: docker-compose up -d # 开发环境 dev-up: docker-compose -f docker-compose.dev.yml up -d dev-down: docker-compose -f docker-compose.dev.yml down # 生产环境 prod-up: docker-compose -f docker-compose.prod.yml up -d prod-down: docker-compose -f docker-compose.prod.yml down ``` ### Docker 配置 ```dockerfile # Dockerfile FROM golang:1.21-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main cmd/server/main.go FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /app/main . COPY --from=builder /app/configs ./configs COPY --from=builder /app/migrations ./migrations EXPOSE 8080 CMD ["./main"] ``` ### Docker Compose 配置 ```yaml # docker-compose.yml version: '3.8' services: backend: build: . ports: - "8080:8080" depends_on: - postgres - redis environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USER=postgres - DB_PASSWORD=password - DB_NAME=photography - REDIS_HOST=redis - REDIS_PORT=6379 volumes: - ./uploads:/app/uploads - ./configs:/app/configs postgres: image: postgres:14 environment: - POSTGRES_DB=photography - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:6.2 ports: - "6379:6379" volumes: - redis_data:/data volumes: postgres_data: redis_data: ``` ## 🔧 开发指南 ### 代码规范 - 使用 Go 标准格式化工具 `go fmt` - 遵循 Go 命名规范 - 使用 `golangci-lint` 进行代码检查 - 编写单元测试,覆盖率不低于 80% ### 错误处理 ```go // pkg/response/response.go type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } func Success(data interface{}) *Response { return &Response{ Code: 200, Message: "success", Data: data, } } func Error(code int, message string) *Response { return &Response{ Code: code, Message: message, } } // internal/utils/errors.go var ( ErrUserNotFound = errors.New("user not found") ErrPhotoNotFound = errors.New("photo not found") ErrAlbumNotFound = errors.New("album not found") ErrInvalidPassword = errors.New("invalid password") ErrUnauthorized = errors.New("unauthorized") ErrPermissionDenied = errors.New("permission denied") ) ``` ### 日志管理 ```go // pkg/logger/logger.go import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var Logger *zap.Logger func init() { config := zap.NewProductionConfig() config.EncoderConfig.TimeKey = "timestamp" config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder Logger, _ = config.Build() } func Info(msg string, fields ...zap.Field) { Logger.Info(msg, fields...) } func Error(msg string, fields ...zap.Field) { Logger.Error(msg, fields...) } func Debug(msg string, fields ...zap.Field) { Logger.Debug(msg, fields...) } ``` ### 测试编写 ```go // internal/service/photo/photo_test.go func TestPhotoService_CreatePhoto(t *testing.T) { mockRepo := &MockPhotoRepository{} service := NewPhotoService(mockRepo) req := &CreatePhotoRequest{ Title: "Test Photo", Description: "Test Description", URL: "https://example.com/photo.jpg", AlbumID: 1, UserID: 1, } expectedPhoto := &models.Photo{ ID: 1, Title: req.Title, Description: req.Description, URL: req.URL, AlbumID: req.AlbumID, UserID: req.UserID, } mockRepo.On("Create", mock.AnythingOfType("*models.Photo")).Return(expectedPhoto, nil) photo, err := service.CreatePhoto(req) assert.NoError(t, err) assert.Equal(t, expectedPhoto.Title, photo.Title) assert.Equal(t, expectedPhoto.Description, photo.Description) mockRepo.AssertExpectations(t) } ``` ## 🔄 与其他模块的集成 ### 与前端的集成 - 提供 RESTful API 供前端调用 - 支持 CORS 跨域请求 - 返回标准化的 JSON 响应格式 - 提供 API 文档 (Swagger) ### 与管理后台的集成 - 提供管理员专用的 API 接口 - 支持批量操作和数据导入导出 - 提供系统监控和统计信息 - 支持权限管理和用户管理 ### 与部署模块的集成 - 提供 Docker 容器化部署 - 支持负载均衡和水平扩展 - 集成健康检查和监控 - 支持优雅关闭和重启 ## 🐛 问题排查 ### 常见问题 1. **数据库连接失败**: 检查数据库配置和网络连接 2. **JWT 认证失败**: 检查密钥配置和 Token 格式 3. **文件上传失败**: 检查存储配置和权限设置 4. **API 响应慢**: 检查数据库查询和缓存配置 ### 调试技巧 ```bash # 查看日志 docker-compose logs -f backend # 进入容器 docker-compose exec backend sh # 数据库查询 docker-compose exec postgres psql -U postgres -d photography # Redis 查询 docker-compose exec redis redis-cli ``` ## 📊 性能优化 ### 数据库优化 ```sql -- 添加索引 CREATE INDEX idx_photos_album_id ON photos(album_id); CREATE INDEX idx_photos_user_id ON photos(user_id); CREATE INDEX idx_photos_created_at ON photos(created_at); -- 查询优化 EXPLAIN ANALYZE SELECT * FROM photos WHERE album_id = 1; ``` ### 缓存策略 ```go // internal/service/photo/photo.go func (s *PhotoService) GetPhoto(id uint) (*models.Photo, error) { cacheKey := fmt.Sprintf("photo:%d", id) // 从缓存获取 if cached, err := s.cache.Get(cacheKey); err == nil { var photo models.Photo if err := json.Unmarshal(cached, &photo); err == nil { return &photo, nil } } // 从数据库获取 photo, err := s.repo.GetByID(id) if err != nil { return nil, err } // 存入缓存 if data, err := json.Marshal(photo); err == nil { s.cache.Set(cacheKey, data, 5*time.Minute) } return photo, nil } ``` ## 📈 监控和日志 ### 健康检查 ```go // internal/api/handlers/health.go func (h *HealthHandler) Check(c *gin.Context) { status := gin.H{ "status": "ok", "timestamp": time.Now().Unix(), "version": "1.0.0", } // 检查数据库连接 if err := h.db.Ping(); err != nil { status["database"] = "error" status["status"] = "error" } else { status["database"] = "ok" } // 检查 Redis 连接 if err := h.redis.Ping().Err(); err != nil { status["redis"] = "error" status["status"] = "error" } else { status["redis"] = "ok" } if status["status"] == "error" { c.JSON(500, status) } else { c.JSON(200, status) } } ``` ### 指标收集 ```go // internal/middleware/metrics.go func PrometheusMiddleware() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() duration := time.Since(start) status := c.Writer.Status() // 记录请求指标 requestDuration.WithLabelValues(c.Request.Method, c.FullPath()).Observe(duration.Seconds()) requestCount.WithLabelValues(c.Request.Method, c.FullPath(), fmt.Sprintf("%d", status)).Inc() } } ``` ## 🔮 未来规划 ### 功能扩展 - 📊 高级搜索和过滤 - 🔄 数据同步和备份 - 📱 移动端 API 优化 - 🤖 AI 图像识别和标记 - 📈 实时分析和推荐 ### 技术升级 - 迁移到微服务架构 - 集成消息队列 (RabbitMQ/Kafka) - 支持分布式存储 - 集成 Elasticsearch 搜索 - 支持 GraphQL API ## 📚 参考资料 - [Go 官方文档](https://golang.org/doc/) - [Gin 框架文档](https://gin-gonic.com/docs/) - [GORM 文档](https://gorm.io/docs/) - [PostgreSQL 文档](https://www.postgresql.org/docs/) - [Docker 最佳实践](https://docs.docker.com/develop/dev-best-practices/) --- 💡 **开发提示**: 开始开发前,请确保已经阅读根目录的 CLAUDE.md 文件,了解项目整体架构。开发过程中建议先实现核心功能,再逐步完善其他特性。务必编写单元测试和集成测试,确保代码质量。