Files

209 lines
6.3 KiB
Go

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
}
}