683 lines
16 KiB
Go
683 lines
16 KiB
Go
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": "照片删除成功"})
|
|
} |