fix
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 5m8s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped

This commit is contained in:
xujiang
2025-07-10 18:09:11 +08:00
parent 35004f224e
commit 010fe2a8c7
96 changed files with 23709 additions and 19 deletions

View File

@ -0,0 +1,683 @@
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/model/entity"
"photography-backend/internal/model/dto"
"photography-backend/internal/service/upload"
"photography-backend/internal/service/auth"
"photography-backend/internal/api/handlers"
"photography-backend/internal/api/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 dto.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 查找用户
var user entity.User
db := database.GetDB()
// 可以使用用户名或邮箱登录
if err := db.Where("username = ? OR email = ?", req.Email, req.Email).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 dto.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 检查用户名是否已存在
var existingUser entity.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 := entity.User{
Username: req.Username,
Email: req.Email,
Password: req.Password, // 实际项目中应该加密
Name: req.Name,
Role: entity.UserRoleUser,
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 []entity.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 entity.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 entity.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 entity.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(&entity.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 []entity.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 entity.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 entity.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 entity.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(&entity.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 []entity.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 entity.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 entity.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 entity.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(&entity.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 []entity.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 entity.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 entity.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 entity.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(&entity.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 []entity.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 entity.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 entity.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 entity.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(&entity.Photo{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "照片删除成功"})
}