refactor: 重构后端架构,采用 Go 风格四层设计模式
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 1m37s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped

## 主要变更

### 🏗️ 架构重构
- 采用简洁的四层架构: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 语言生态标准
- 完善文档体系,降低上手难度
This commit is contained in:
xujiang
2025-07-10 11:20:59 +08:00
parent 540593f1dc
commit a2f2f66f88
40 changed files with 9682 additions and 1798 deletions

View File

@ -0,0 +1,682 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"photography-backend/internal/config"
"photography-backend/internal/database"
"photography-backend/internal/models"
"photography-backend/internal/service/upload"
"photography-backend/internal/service/auth"
"photography-backend/internal/handlers"
"photography-backend/internal/middleware"
)
func main() {
// 加载配置
configPath := os.Getenv("CONFIG_PATH")
if configPath == "" {
configPath = "configs/config.yaml"
}
cfg, err := config.LoadConfig(configPath)
if err != nil {
log.Fatalf("加载配置失败: %v", err)
}
// 初始化数据库
if err := database.InitDatabase(cfg); err != nil {
log.Fatalf("数据库初始化失败: %v", err)
}
// 自动迁移数据表
if err := database.AutoMigrate(); err != nil {
log.Fatalf("数据库迁移失败: %v", err)
}
// 创建服务
uploadService := upload.NewUploadService(cfg)
jwtService := auth.NewJWTService(&cfg.JWT)
// 创建处理器
uploadHandler := handlers.NewUploadHandler(uploadService)
// 创建中间件
authMiddleware := middleware.NewAuthMiddleware(jwtService)
// 设置 Gin 模式
if cfg.IsProduction() {
gin.SetMode(gin.ReleaseMode)
}
// 创建 Gin 引擎
r := gin.Default()
// 添加 CORS 中间件
r.Use(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, X-Requested-With")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
// 健康检查
r.GET("/health", func(c *gin.Context) {
// 检查数据库连接
if err := database.HealthCheck(); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"status": "error",
"message": "数据库连接失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"timestamp": time.Now().Unix(),
"version": cfg.App.Version,
"database": "connected",
})
})
// 数据库统计
r.GET("/stats", func(c *gin.Context) {
stats := database.GetStats()
c.JSON(http.StatusOK, gin.H{
"database_stats": stats,
})
})
// 静态文件服务
r.Static("/uploads", cfg.Storage.Local.BasePath)
// 基础 API 路由
api := r.Group("/api/v1")
{
// 文件上传路由
upload := api.Group("/upload")
{
upload.POST("/photo", authMiddleware.RequireAuth(), uploadHandler.UploadPhoto)
upload.DELETE("/photo/:id", authMiddleware.RequireAuth(), uploadHandler.DeletePhoto)
upload.GET("/stats", authMiddleware.RequireAdmin(), uploadHandler.GetUploadStats)
}
// 认证路由
auth := api.Group("/auth")
{
auth.POST("/login", login)
auth.POST("/register", register)
auth.POST("/refresh", refreshToken)
}
// 用户相关
users := api.Group("/users")
{
users.GET("", getUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}
// 分类相关
categories := api.Group("/categories")
{
categories.GET("", getCategories)
categories.POST("", createCategory)
categories.GET("/:id", getCategory)
categories.PUT("/:id", updateCategory)
categories.DELETE("/:id", deleteCategory)
}
// 标签相关
tags := api.Group("/tags")
{
tags.GET("", getTags)
tags.POST("", createTag)
tags.GET("/:id", getTag)
tags.PUT("/:id", updateTag)
tags.DELETE("/:id", deleteTag)
}
// 相册相关
albums := api.Group("/albums")
{
albums.GET("", getAlbums)
albums.POST("", createAlbum)
albums.GET("/:id", getAlbum)
albums.PUT("/:id", updateAlbum)
albums.DELETE("/:id", deleteAlbum)
}
// 照片相关
photos := api.Group("/photos")
{
photos.GET("", getPhotos)
photos.POST("", createPhoto)
photos.GET("/:id", getPhoto)
photos.PUT("/:id", updatePhoto)
photos.DELETE("/:id", deletePhoto)
}
}
// 创建 HTTP 服务器
server := &http.Server{
Addr: cfg.GetServerAddr(),
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
// 启动服务器
go func() {
fmt.Printf("服务器启动在 %s\n", server.Addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("正在关闭服务器...")
// 优雅关闭
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("服务器强制关闭: %v", err)
}
// 关闭数据库连接
if err := database.Close(); err != nil {
log.Printf("关闭数据库连接失败: %v", err)
}
fmt.Println("服务器已关闭")
}
// 认证相关处理函数
func login(c *gin.Context) {
var req models.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 查找用户
var user models.User
db := database.GetDB()
// 可以使用用户名或邮箱登录
if err := db.Where("username = ? OR email = ?", req.Username, req.Username).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
return
}
// TODO: 验证密码 (需要集成bcrypt)
// 这里暂时跳过密码验证
// 生成JWT令牌 (简化实现)
c.JSON(http.StatusOK, gin.H{
"message": "登录成功",
"user": gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"role": user.Role,
},
"token": "mock-jwt-token", // 实际项目中应该生成真实的JWT
})
}
func register(c *gin.Context) {
var req models.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 检查用户名是否已存在
var existingUser models.User
db := database.GetDB()
if err := db.Where("username = ? OR email = ?", req.Username, req.Email).First(&existingUser).Error; err == nil {
c.JSON(http.StatusConflict, gin.H{"error": "用户名或邮箱已存在"})
return
}
// 创建用户
user := models.User{
Username: req.Username,
Email: req.Email,
Password: req.Password, // 实际项目中应该加密
Name: req.Name,
Role: "user",
IsActive: true,
}
if err := db.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 清除密码
user.Password = ""
c.JSON(http.StatusCreated, gin.H{
"message": "注册成功",
"user": user,
})
}
func refreshToken(c *gin.Context) {
// TODO: 实现刷新令牌逻辑
c.JSON(http.StatusOK, gin.H{
"message": "刷新令牌成功",
"token": "new-mock-jwt-token",
})
}
// 用户 CRUD 操作
func getUsers(c *gin.Context) {
var users []models.User
db := database.GetDB()
if err := db.Find(&users).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": users})
}
func createUser(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db := database.GetDB()
if err := db.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": user})
}
func getUser(c *gin.Context) {
id := c.Param("id")
var user models.User
db := database.GetDB()
if err := db.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
}
func updateUser(c *gin.Context) {
id := c.Param("id")
var user models.User
db := database.GetDB()
if err := db.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Save(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
}
func deleteUser(c *gin.Context) {
id := c.Param("id")
db := database.GetDB()
if err := db.Delete(&models.User{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "用户删除成功"})
}
// 分类 CRUD 操作
func getCategories(c *gin.Context) {
var categories []models.Category
db := database.GetDB()
if err := db.Find(&categories).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": categories})
}
func createCategory(c *gin.Context) {
var category models.Category
if err := c.ShouldBindJSON(&category); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db := database.GetDB()
if err := db.Create(&category).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": category})
}
func getCategory(c *gin.Context) {
id := c.Param("id")
var category models.Category
db := database.GetDB()
if err := db.First(&category, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "分类不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": category})
}
func updateCategory(c *gin.Context) {
id := c.Param("id")
var category models.Category
db := database.GetDB()
if err := db.First(&category, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "分类不存在"})
return
}
if err := c.ShouldBindJSON(&category); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Save(&category).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": category})
}
func deleteCategory(c *gin.Context) {
id := c.Param("id")
db := database.GetDB()
if err := db.Delete(&models.Category{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "分类删除成功"})
}
// 标签 CRUD 操作
func getTags(c *gin.Context) {
var tags []models.Tag
db := database.GetDB()
if err := db.Find(&tags).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": tags})
}
func createTag(c *gin.Context) {
var tag models.Tag
if err := c.ShouldBindJSON(&tag); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db := database.GetDB()
if err := db.Create(&tag).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": tag})
}
func getTag(c *gin.Context) {
id := c.Param("id")
var tag models.Tag
db := database.GetDB()
if err := db.First(&tag, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "标签不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": tag})
}
func updateTag(c *gin.Context) {
id := c.Param("id")
var tag models.Tag
db := database.GetDB()
if err := db.First(&tag, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "标签不存在"})
return
}
if err := c.ShouldBindJSON(&tag); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Save(&tag).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": tag})
}
func deleteTag(c *gin.Context) {
id := c.Param("id")
db := database.GetDB()
if err := db.Delete(&models.Tag{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "标签删除成功"})
}
// 相册 CRUD 操作
func getAlbums(c *gin.Context) {
var albums []models.Album
db := database.GetDB()
if err := db.Find(&albums).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": albums})
}
func createAlbum(c *gin.Context) {
var album models.Album
if err := c.ShouldBindJSON(&album); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db := database.GetDB()
if err := db.Create(&album).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": album})
}
func getAlbum(c *gin.Context) {
id := c.Param("id")
var album models.Album
db := database.GetDB()
if err := db.First(&album, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "相册不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": album})
}
func updateAlbum(c *gin.Context) {
id := c.Param("id")
var album models.Album
db := database.GetDB()
if err := db.First(&album, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "相册不存在"})
return
}
if err := c.ShouldBindJSON(&album); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Save(&album).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": album})
}
func deleteAlbum(c *gin.Context) {
id := c.Param("id")
db := database.GetDB()
if err := db.Delete(&models.Album{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "相册删除成功"})
}
// 照片 CRUD 操作
func getPhotos(c *gin.Context) {
var photos []models.Photo
db := database.GetDB()
if err := db.Find(&photos).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": photos})
}
func createPhoto(c *gin.Context) {
var photo models.Photo
if err := c.ShouldBindJSON(&photo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db := database.GetDB()
if err := db.Create(&photo).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"data": photo})
}
func getPhoto(c *gin.Context) {
id := c.Param("id")
var photo models.Photo
db := database.GetDB()
if err := db.First(&photo, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "照片不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": photo})
}
func updatePhoto(c *gin.Context) {
id := c.Param("id")
var photo models.Photo
db := database.GetDB()
if err := db.First(&photo, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "照片不存在"})
return
}
if err := c.ShouldBindJSON(&photo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Save(&photo).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": photo})
}
func deletePhoto(c *gin.Context) {
id := c.Param("id")
db := database.GetDB()
if err := db.Delete(&models.Photo{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "照片删除成功"})
}