Files
backend/cmd/server/main.go
Wu XiangYu 44f007936e Merge remote-tracking branch 'origin/feature/redis-auth-integration' into dev
# Conflicts:
#	go.mod
#	go.sum
#	internal/container/container.go
#	internal/repository/interfaces.go
#	internal/service/mocks_test.go
#	internal/service/texture_service_test.go
#	internal/service/token_service_test.go
#	pkg/redis/manager.go
2025-12-25 22:45:58 +08:00

182 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @title CarrotSkin API
// @version 1.0
// @description Minecraft皮肤站后端API
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"carrotskin/internal/container"
"carrotskin/internal/handler"
"carrotskin/internal/middleware"
"carrotskin/internal/task"
"carrotskin/pkg/auth"
"carrotskin/pkg/config"
"carrotskin/pkg/database"
"carrotskin/pkg/email"
"carrotskin/pkg/logger"
"carrotskin/pkg/redis"
"carrotskin/pkg/storage"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
_ "carrotskin/docs" // Swagger docs
)
func main() {
// 初始化配置
if err := config.Init(); err != nil {
log.Fatalf("配置加载失败: %v", err)
}
cfg := config.MustGetConfig()
// 初始化日志
if err := logger.Init(cfg.Log); err != nil {
log.Fatalf("日志初始化失败: %v", err)
}
loggerInstance := logger.MustGetLogger()
defer loggerInstance.Sync()
// 初始化数据库
if err := database.Init(cfg.Database, loggerInstance); err != nil {
loggerInstance.Fatal("数据库初始化失败", zap.Error(err))
}
defer database.Close()
// 执行数据库迁移
if err := database.AutoMigrate(loggerInstance); err != nil {
loggerInstance.Fatal("数据库迁移失败", zap.Error(err))
}
// 初始化种子数据
if err := database.Seed(loggerInstance); err != nil {
loggerInstance.Fatal("种子数据初始化失败", zap.Error(err))
}
// 初始化JWT服务
if err := auth.Init(cfg.JWT); err != nil {
loggerInstance.Fatal("JWT服务初始化失败", zap.Error(err))
}
// 初始化Redis开发/测试环境失败时会自动回退到miniredis
if err := redis.Init(cfg.Redis, loggerInstance); err != nil {
loggerInstance.Fatal("Redis初始化失败", zap.Error(err))
}
defer redis.Close()
// 记录Redis模式
if redis.IsUsingMiniRedis() {
loggerInstance.Info("使用miniredis进行开发/测试")
} else {
loggerInstance.Info("使用生产Redis")
}
// 初始化对象存储 (RustFS - S3兼容)
var storageClient *storage.StorageClient
if err := storage.Init(cfg.RustFS); err != nil {
loggerInstance.Warn("对象存储连接失败,某些功能可能不可用", zap.Error(err))
} else {
storageClient = storage.MustGetClient()
loggerInstance.Info("对象存储连接成功")
}
// 初始化邮件服务
if err := email.Init(cfg.Email, loggerInstance); err != nil {
loggerInstance.Fatal("邮件服务初始化失败", zap.Error(err))
}
emailServiceInstance := email.MustGetService()
// 初始化Casbin权限服务
casbinService, err := auth.NewCasbinService(database.MustGetDB(), cfg.Casbin.ModelPath, loggerInstance)
if err != nil {
loggerInstance.Fatal("Casbin服务初始化失败", zap.Error(err))
}
// 创建依赖注入容器
c := container.NewContainer(
database.MustGetDB(),
redis.MustGetClient(),
loggerInstance,
auth.MustGetJWTService(),
casbinService,
storageClient,
emailServiceInstance,
)
// 设置Gin模式
if cfg.Server.Mode == "production" {
gin.SetMode(gin.ReleaseMode)
}
// 创建路由
router := gin.New()
// 禁用自动重定向允许API路径带或不带/结尾都能正常访问
router.RedirectTrailingSlash = false
router.RedirectFixedPath = false
// 添加中间件
router.Use(middleware.Logger(loggerInstance))
router.Use(middleware.Recovery(loggerInstance))
router.Use(middleware.CORS())
// 使用依赖注入方式注册路由
handler.RegisterRoutesWithDI(router, c)
// 启动后台任务Token已迁移到Redis不再需要清理任务
// 如需使用数据库Token存储可以恢复TokenCleanupTask
taskRunner := task.NewRunner(loggerInstance)
taskCtx, taskCancel := context.WithCancel(context.Background())
defer taskCancel()
taskRunner.Start(taskCtx)
// 创建HTTP服务器
srv := &http.Server{
Addr: cfg.Server.Port,
Handler: router,
ReadTimeout: cfg.Server.ReadTimeout,
WriteTimeout: cfg.Server.WriteTimeout,
}
// 启动服务器
go func() {
loggerInstance.Info("服务器启动", zap.String("port", cfg.Server.Port))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
loggerInstance.Fatal("服务器启动失败", zap.Error(err))
}
}()
// 等待中断信号优雅关闭
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
loggerInstance.Info("正在关闭服务器...")
// 停止后台任务
taskCancel()
taskRunner.Wait()
// 设置关闭超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
loggerInstance.Fatal("服务器强制关闭", zap.Error(err))
}
loggerInstance.Info("服务器已关闭")
}