package main import ( "context" "crypto/sha256" "encoding/hex" "net/http" "os" "os/signal" "syscall" "time" "hfb_sys/backend/internal/config" "hfb_sys/backend/internal/database" "hfb_sys/backend/internal/jobs/ordertimeout" "hfb_sys/backend/internal/jobs/refundretry" "hfb_sys/backend/internal/logging" "hfb_sys/backend/internal/modules/adminauth" "hfb_sys/backend/internal/modules/payment" "hfb_sys/backend/internal/modules/paymentconfig" "hfb_sys/backend/internal/router" "go.uber.org/zap" "gorm.io/gorm" ) // @title HFB Sys API // @version 1.0 // @description 哈夫币租号平台 API 文档 // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.email support@hfb.com // @license.name MIT // @license.url https://opensource.org/licenses/MIT // @host localhost:8080 // @BasePath /api // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization // @description Type "Bearer" followed by a space and JWT token. func main() { cfg := config.Load() logger, err := logging.New(cfg.Log) if err != nil { panic(err) } defer func() { _ = logger.Sync() }() if err := cfg.ValidateProductionSecurity(); err != nil { logger.Fatal("生产安全配置校验失败", zap.Error(err)) } logAuthRuntimeIdentity(logger, cfg) var deps router.Dependencies db, err := database.OpenMySQLWithOptions(cfg.MySQLDSN, cfg.Log.Level, logger, database.MySQLOptions{ MaxOpenConns: cfg.Database.MaxOpenConns, MaxIdleConns: cfg.Database.MaxIdleConns, ConnMaxLifetime: cfg.Database.ConnMaxLifetime, ConnMaxIdleTime: cfg.Database.ConnMaxIdleTime, SlowQueryThreshold: cfg.Database.SlowQueryThreshold, }) if err != nil { logger.Warn("MySQL 不可用,数据库接口将返回 503", zap.Error(err)) } else { deps.DB = db if err := adminauth.BootstrapAdmin(context.Background(), db, adminauth.BootstrapConfig{ AppEnv: cfg.AppEnv, Username: cfg.BootstrapAdminUsername, Password: cfg.BootstrapAdminPassword, Nickname: cfg.BootstrapAdminNickname, }); err != nil { logger.Fatal("初始管理员创建失败", zap.Error(err)) } } redisClient, err := database.OpenRedis(context.Background(), database.RedisConfig{ Addr: cfg.RedisAddr, Password: cfg.RedisPassword, DB: cfg.RedisDB, }) if err != nil { logger.Warn("Redis 不可用,相关接口将返回 503", zap.Error(err)) } else { deps.Redis = redisClient defer func() { _ = redisClient.Close() }() } engine := router.New(cfg, deps, logger) jobCtx, stopJobs := context.WithCancel(context.Background()) defer stopJobs() if deps.DB != nil { ordertimeout.New(deps.DB, deps.Redis, logger).Start(jobCtx) if paymentConfigRepo := newPaymentConfigRepositoryForJobs(cfg, deps.DB, logger); paymentConfigRepo != nil { paymentRepo := payment.NewRepository(deps.DB, paymentConfigRepo, nil, payment.WithLogger(logger)) refundretry.New(deps.DB, deps.Redis, logger, paymentRepo).Start(jobCtx) } } server := &http.Server{ Addr: cfg.AppAddr, Handler: engine, ReadHeaderTimeout: 5 * time.Second, } go func() { logger.Info("API 服务启动", zap.String("addr", cfg.AppAddr)) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Fatal("API 服务异常退出", zap.Error(err)) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit stopJobs() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { logger.Fatal("API 服务关闭失败", zap.Error(err)) } logger.Info("API 服务已停止") } // logAuthRuntimeIdentity 记录可用于排查多实例或部署配置漂移的非敏感身份信息。 func logAuthRuntimeIdentity(logger *zap.Logger, cfg config.Config) { instanceID, err := os.Hostname() if err != nil || instanceID == "" { instanceID = "unknown" } sum := sha256.Sum256([]byte(cfg.JWTSecret)) logger.Debug("认证运行实例", zap.String("instance_id", instanceID), zap.String("app_env", cfg.AppEnv), zap.String("jwt_secret_fingerprint", hex.EncodeToString(sum[:])[:12]), ) } func newPaymentConfigRepositoryForJobs(cfg config.Config, db *gorm.DB, logger *zap.Logger) *paymentconfig.Repository { encryptionKey := cfg.PaymentConfigEncryptionKey var encryptor paymentconfig.Encryptor if encryptionKey != "" { if aesEncryptor, err := paymentconfig.NewAESEncryptor(encryptionKey); err == nil { encryptor = aesEncryptor } } if encryptor == nil { if config.IsProductionEnv(cfg.AppEnv) { logger.Fatal("支付配置加密密钥未设置或无效") } encryptor = &paymentconfig.MockEncryptor{} logger.Debug("定时任务使用模拟支付配置加密器") } return paymentconfig.NewRepository(db, encryptor) }