package di import ( "net/http" "time" "cloudnest/internal/application/auth" "cloudnest/internal/application/captcha" "cloudnest/internal/application/email" "cloudnest/internal/application/file" "cloudnest/internal/config" "cloudnest/internal/domain/auth/repository" fileRepo "cloudnest/internal/domain/file/repository" "cloudnest/internal/infrastructure/database" infrastructureRepo "cloudnest/internal/infrastructure/repository" "cloudnest/internal/infrastructure/storage" "cloudnest/internal/interface/http/handler" "cloudnest/internal/interface/http/routes" "cloudnest/internal/middleware" "cloudnest/internal/pkg/logger" "github.com/gin-gonic/gin" "go.uber.org/dig" ) type Container struct { *dig.Container } func New() *Container { c := dig.New() c.Provide(func() *config.Config { return config.Load() }) c.Provide(func(cfg *config.Config) (*database.MySQLClient, error) { return database.NewMySQLClient(cfg.Database.DSN()) }) c.Provide(func(cfg *config.Config) (*database.RedisClient, error) { return database.NewRedisClient(cfg.Cache.Addr(), cfg.Cache.Password, cfg.Cache.DB) }) c.Provide(func(cfg *config.Config) (*storage.MinioClient, error) { return storage.NewMinioClient( cfg.Storage.Endpoint, cfg.Storage.AccessKeyID, cfg.Storage.SecretAccessKey, cfg.Storage.UseSSL, ) }) c.Provide(func(mysql *database.MySQLClient) repository.UserRepository { return infrastructureRepo.NewUserRepository(mysql) }) c.Provide(func(minio *storage.MinioClient) fileRepo.FileRepository { return infrastructureRepo.NewFileRepository(minio) }) c.Provide(func(mysql *database.MySQLClient) *infrastructureRepo.FileMetaRepository { return infrastructureRepo.NewFileMetaRepository(mysql) }) c.Provide(func(userRepo repository.UserRepository, emailService *email.EmailService, cfg *config.Config) *auth.AuthService { return auth.NewAuthService(userRepo, emailService, cfg.JWT.Secret, cfg.JWT.ExpiresIn) }) c.Provide(func(fRepo fileRepo.FileRepository, fMetaRepo *infrastructureRepo.FileMetaRepository, cfg *config.Config) *file.FileService { return file.NewFileService(fRepo, fMetaRepo, cfg.Storage.BucketName) }) c.Provide(func(redis *database.RedisClient) *captcha.CaptchaService { return captcha.NewCaptchaService(redis) }) c.Provide(func(cfg *config.Config, redis *database.RedisClient) *email.EmailService { return email.NewEmailService(&cfg.Email, redis) }) c.Provide(func(service *auth.AuthService, captchaService *captcha.CaptchaService, emailService *email.EmailService) *handler.AuthHandler { return handler.NewAuthHandler(service, captchaService, emailService) }) c.Provide(func(emailService *email.EmailService, captchaService *captcha.CaptchaService) *handler.EmailHandler { return handler.NewEmailHandler(emailService, captchaService) }) c.Provide(func(service *file.FileService, authService *auth.AuthService) *handler.FileHandler { return handler.NewFileHandler(service, authService) }) c.Provide(func(service *captcha.CaptchaService) *handler.CaptchaHandler { return handler.NewCaptchaHandler(service) }) return &Container{c} } // BuildEngine builds the Gin engine with routes based on service type. // BuildEngine 根据服务类型构建 Gin 引擎并注册路由。 // // Parameters: // - serviceType: The type of service (auth or file). // // Returns: // - *gin.Engine: The configured Gin engine. // - error: Any error that occurred during setup. // // 参数: // - serviceType: 服务类型 (auth 或 file)。 // // 返回值: // - *gin.Engine: 配置好的 Gin 引擎。 // - error: 设置过程中发生的任何错误。 func (c *Container) BuildEngine(serviceType routes.ServiceType) (*gin.Engine, error) { var engine *gin.Engine err := c.Invoke(func(cfg *config.Config, authHandler *handler.AuthHandler, fileHandler *handler.FileHandler, captchaHandler *handler.CaptchaHandler, emailHandler *handler.EmailHandler) { engine = gin.New() engine.RedirectTrailingSlash = false engine.RedirectFixedPath = false engine.Use(gin.Recovery()) engine.Use(middleware.CORS()) engine.Use(middleware.ErrorHandler()) engine.GET("/healthz", func(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"status": "ok"}) }) routes.SetupRoutes(engine, serviceType, authHandler, fileHandler, captchaHandler, emailHandler, cfg.JWT.Secret) logger.Info("engine built successfully") }) return engine, err } // BuildAuthEngine builds the Gin engine for auth service. // BuildAuthEngine 为认证服务构建 Gin 引擎。 // // This method only registers authentication routes. // // 此方法只注册认证路由。 func (c *Container) BuildAuthEngine() (*gin.Engine, error) { return c.BuildEngine(routes.ServiceTypeAuth) } // BuildFileEngine builds the Gin engine for file service. // BuildFileEngine 为文件服务构建 Gin 引擎。 // // This method only registers file management routes. // // 此方法只注册文件管理路由。 func (c *Container) BuildFileEngine() (*gin.Engine, error) { return c.BuildEngine(routes.ServiceTypeFile) } func (c *Container) InitializeServices() error { return c.Invoke(func(mysql *database.MySQLClient, redis *database.RedisClient, minio *storage.MinioClient, cfg *config.Config) error { if err := mysql.Migrate(); err != nil { return err } if err := minio.EnsureBucket(cfg.Storage.BucketName); err != nil { return err } logger.Info("all services initialized") go startRecycleBinCleaner(cfg, mysql, minio) return nil }) } func startRecycleBinCleaner(cfg *config.Config, mysql *database.MySQLClient, minio *storage.MinioClient) { expireDays := cfg.Storage.RecycleBinExpireDays if expireDays <= 0 { expireDays = 7 } fMetaRepo := infrastructureRepo.NewFileMetaRepository(mysql) fRepo := infrastructureRepo.NewFileRepository(minio) ticker := time.NewTicker(24 * time.Hour) defer ticker.Stop() for { logger.Info("starting recycle bin cleanup", "expire_days", expireDays) expiredFiles, err := fMetaRepo.CleanExpiredRecycleBin(expireDays) if err != nil { logger.Error("failed to clean recycle bin", "error", err) continue } for _, df := range expiredFiles { if err := fRepo.Delete(df.UserCode, df.FileKey); err != nil { logger.Error("failed to delete file from storage", "file_key", df.FileKey, "error", err) } } logger.Info("recycle bin cleanup completed", "files_cleaned", len(expiredFiles)) <-ticker.C } }