package main import ( "context" "log" "time" "github.com/gin-gonic/gin" "wx_service/config" "wx_service/internal/achievement" adminmodule "wx_service/internal/admin" authhandler "wx_service/internal/common/auth/handler" authservice "wx_service/internal/common/auth/service" rediscache "wx_service/internal/common/redis/cache" redisservice "wx_service/internal/common/redis/service" uploadhandler "wx_service/internal/common/upload/handler" uploadservice "wx_service/internal/common/upload/service" oahandler "wx_service/internal/common/wechat_official/handler" oaservice "wx_service/internal/common/wechat_official/service" "wx_service/internal/database" expiry "wx_service/internal/expiry" lawyerhandler "wx_service/internal/lawyer/handler" lawyerservice "wx_service/internal/lawyer/service" marketinghandler "wx_service/internal/marketing/handler" marketingmodel "wx_service/internal/marketing/model" marketingrepo "wx_service/internal/marketing/repository" marketingservice "wx_service/internal/marketing/service" membershiphandler "wx_service/internal/membership/handler" membershipmodel "wx_service/internal/membership/model" membershipservice "wx_service/internal/membership/service" "wx_service/internal/model" "wx_service/internal/observability" quitcheckinhandler "wx_service/internal/quitcheckin/handler" quitcheckinmodel "wx_service/internal/quitcheckin/model" quitcheckinservice "wx_service/internal/quitcheckin/service" rmhandler "wx_service/internal/remove_watermark/handler" rmmodel "wx_service/internal/remove_watermark/model" rmservice "wx_service/internal/remove_watermark/service" "wx_service/internal/routes" smokehandler "wx_service/internal/smoke/handler" smokemodel "wx_service/internal/smoke/model" smokeservice "wx_service/internal/smoke/service" ) func main() { if loc, err := time.LoadLocation("Asia/Shanghai"); err == nil { time.Local = loc } else { time.Local = time.FixedZone("CST", 8*3600) } // 1) 加载配置(通常来自环境变量 / .env) config.LoadConfig() // 2) 初始化数据库连接 if err := database.InitDB(); err != nil { log.Fatalf("init database failed: %v", err) } // 3) 自动建表/迁移(开发阶段很方便;生产环境可改为手动迁移) if err := database.AutoMigrate( &adminmodule.Admin{}, &adminmodule.SystemConfig{}, &model.MiniProgram{}, &model.User{}, &model.UserMembership{}, &expiry.ExpiryItem{}, &expiry.ExpiryUserSettings{}, &membershipmodel.MembershipRedeemCode{}, &membershipmodel.MembershipRedemption{}, &rmmodel.VideoParseLog{}, &rmmodel.VideoParseUnlock{}, &rmmodel.VideoDownloadFailure{}, &smokemodel.SmokeLog{}, &smokemodel.SmokeUserProfile{}, &smokemodel.SmokeAIAdvice{}, &smokemodel.SmokeAIAdviceUnlock{}, &smokemodel.SmokeAINextSmoke{}, &smokemodel.SmokeMotivationQuote{}, &smokemodel.SmokeShare{}, &smokemodel.SmokeQuitPlan{}, &smokemodel.SmokeQuitPlanDay{}, &marketingmodel.MarketingCategory{}, &marketingmodel.MarketingTemplate{}, &marketingmodel.MarketingDownload{}, &marketingmodel.UserLogo{}, &marketingmodel.AdPlacement{}, &quitcheckinmodel.Profile{}, &quitcheckinmodel.DailyStatus{}, &quitcheckinmodel.RelapseEvent{}, &quitcheckinmodel.HPChangeLog{}, &quitcheckinmodel.SupervisorInvite{}, &quitcheckinmodel.SupervisorBinding{}, &quitcheckinmodel.SupervisorReminderSetting{}, &quitcheckinmodel.SupervisorReminderLog{}, &quitcheckinmodel.RewardGoal{}, &quitcheckinmodel.DreamPreset{}, &achievement.Theme{}, &achievement.Level{}, ); err != nil { log.Fatalf("auto migrate failed: %v", err) } // 4) 初始化 HTTP 框架(Gin) gin.SetMode(config.AppConfig.Server.Mode) router := gin.Default() metricsCollector := observability.NewCollector() router.Use(observability.RequestLogMiddleware(metricsCollector)) router.GET("/metrics/basic", observability.BasicMetricsHandler(metricsCollector)) // 5) 依赖注入:先创建 service,再创建 handler(handler 只关心 HTTP 输入/输出) miniProgramService := authservice.NewMiniProgramService(database.DB) authService := authservice.NewAuthService(database.DB, miniProgramService) authHandler := authhandler.NewAuthHandler(authService) videoService, err := rmservice.NewVideoService(database.DB, config.AppConfig.ShortVideo) if err != nil { log.Fatalf("init video service failed: %v", err) } videoHandler := rmhandler.NewVideoHandler(videoService) smokeLogService := smokeservice.NewSmokeLogService(database.DB) smokeAIAdviceService := smokeservice.NewSmokeAIAdviceService(database.DB, config.AppConfig.AI) smokeProfileService := smokeservice.NewSmokeProfileService(database.DB) smokeNextService := smokeservice.NewSmokeNextService(database.DB) smokeAINextService := smokeservice.NewSmokeAINextSmokeService(database.DB, config.AppConfig.AI) smokeShareService := smokeservice.NewSmokeShareService(database.DB) smokeQuitPlanService := smokeservice.NewSmokeQuitPlanService(database.DB, config.AppConfig.AI) achievementService := achievement.NewService(database.DB) if err := achievementService.SeedDefaults(context.Background()); err != nil { log.Printf("seed achievement defaults: %v", err) } quitCheckinService := quitcheckinservice.NewService(database.DB) smokeHandler := smokehandler.NewSmokeHandler(smokeLogService, smokeAIAdviceService, smokeProfileService, smokeNextService, smokeAINextService, smokeShareService, achievementService, quitCheckinService) quitPlanHandler := smokehandler.NewQuitPlanHandler(smokeQuitPlanService) quitCheckinHandler := quitcheckinhandler.NewHandler(quitCheckinService) redeemCodeService := membershipservice.NewRedeemCodeService(database.DB, config.AppConfig.Admin.Token) redeemCodeHandler := membershiphandler.NewRedeemCodeHandler(redeemCodeService) uploadSvc := uploadservice.NewUploadService(config.AppConfig.OSS) uploadHandler := uploadhandler.NewUploadHandler(uploadSvc) oaService := oaservice.NewWeChatOAService(config.AppConfig.WeChatOA) oaOAuthHandler := oahandler.NewOAuthHandler(oaService) var sessionCache *rediscache.SessionUserCache redisClient, err := redisservice.NewClient(config.AppConfig.Redis) if err != nil { log.Printf("redis disabled: %v", err) } else if redisClient != nil { sessionCache = rediscache.NewSessionUserCache(redisClient.Redis(), redisClient.KeyPrefix(), redisClient.SessionTTL()) } var lawyerHandler *lawyerhandler.LawyerHandler if lawyerDB, ok := database.GetAdditionalDB("lawyer"); ok { lawyerService := lawyerservice.NewService(lawyerDB) lawyerHandler = lawyerhandler.NewLawyerHandler(lawyerService) } else { log.Println("lawyer 数据库未配置,/lawyers 接口已禁用") } expiryRepo := expiry.NewRepository(database.DB) expiryService := expiry.NewService(expiryRepo) if redisClient != nil { expirySummaryCache := expiry.NewSummaryCache(redisClient.Redis(), redisClient.KeyPrefix(), 5*time.Minute) expiryService.BindSummaryCache(expirySummaryCache) } expiryHandler := expiry.NewHandler(expiryService) categoryRepo := marketingrepo.NewCategoryRepository(database.DB) templateRepo := marketingrepo.NewTemplateRepository(database.DB) downloadRepo := marketingrepo.NewDownloadRepository(database.DB) userLogoRepo := marketingrepo.NewUserLogoRepository(database.DB) categorySvc := marketingservice.NewCategoryService(categoryRepo) templateSvc := marketingservice.NewTemplateService(templateRepo) downloadSvc := marketingservice.NewDownloadService(downloadRepo, templateRepo) userLogoSvc := marketingservice.NewUserLogoService(userLogoRepo) marketingCategoryHandler := marketinghandler.NewCategoryHandler(categorySvc) marketingTemplateHandler := marketinghandler.NewTemplateHandler(templateSvc) marketingDownloadHandler := marketinghandler.NewDownloadHandler(downloadSvc) marketingUserLogoHandler := marketinghandler.NewUserLogoHandler(userLogoSvc) adPlacementRepo := marketingrepo.NewAdPlacementRepository(database.DB) marketingAdPlacementHandler := marketinghandler.NewAdPlacementHandler(adPlacementRepo) adminService := adminmodule.NewService( database.DB, config.AppConfig.JWT.Secret, time.Duration(config.AppConfig.JWT.Expire)*time.Second, config.AppConfig.Admin.DefaultUsername, config.AppConfig.Admin.DefaultPassword, ) if err := adminService.EnsureDefaultAdmin(context.Background()); err != nil { log.Fatalf("ensure default admin failed: %v", err) } adminHandler := adminmodule.NewHandler(adminService) // 6) 注册路由:把 URL 映射到 handler routes.Register( router, database.DB, authHandler, videoHandler, smokeHandler, quitPlanHandler, redeemCodeHandler, uploadHandler, oaOAuthHandler, sessionCache, lawyerHandler, expiryHandler, adminHandler, config.AppConfig.Admin.Token, marketingCategoryHandler, marketingTemplateHandler, marketingDownloadHandler, marketingUserLogoHandler, marketingAdPlacementHandler, quitCheckinHandler, ) // 7) 启动监听端口 addr := ":" + config.AppConfig.Server.Port if err := router.Run(addr); err != nil { log.Fatalf("server stopped: %v", err) } }