package wire import ( "fmt" "time" "with_you/internal/cache" "with_you/internal/config" "with_you/internal/database" "with_you/internal/pkg/crypto" "with_you/internal/pkg/email" "with_you/internal/pkg/hook" "with_you/internal/pkg/jpush" "with_you/internal/pkg/openai" "with_you/internal/pkg/redis" "with_you/internal/pkg/s3" "with_you/internal/pkg/tencent" "with_you/internal/pkg/ws" "with_you/internal/repository" "with_you/internal/service" "github.com/google/wire" redisPkg "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" ) // InfrastructureSet 基础设施 Provider Set var InfrastructureSet = wire.NewSet( // 配置 ProvideConfig, // 日志 ProvideLogger, // 数据库 ProvideDB, // 事务管理器 ProvideTransactionManager, // Redis 和缓存 ProvideRedisClient, ProvideCache, // 钩子系统 ProvideHookManager, ProvideBuiltinHooks, ProvideModerationHooks, // WebSocket MessagePublisher ProvideWSMessagePublisher, // Seq 预分配管理器 ProvideSeqBufferManager, // 推送 Worker ProvidePushWorker, // 外部服务客户端 ProvideOpenAIClient, ProvideTencentClient, ProvideEmailClient, ProvideS3Client, ProvideJPushClient, // 消息加密器 ProvideMessageEncryptor, ) // ProvideConfig 加载配置 func ProvideConfig() (*config.Config, error) { return config.Load("configs/config.yaml") } // ProvideDB 提供数据库连接 func ProvideDB(cfg *config.Config) (*gorm.DB, error) { return database.NewDB(&cfg.Database) } // ProvideRedisClient 提供 Redis 客户端 func ProvideRedisClient(cfg *config.Config) *redis.Client { client, err := redis.New(&cfg.Redis) if err != nil { zap.L().Warn("Failed to connect to Redis, falling back to memory cache", zap.Error(err), ) return nil } return client } // ProvideCache 提供缓存实例 func ProvideCache(cfg *config.Config, redisClient *redis.Client) cache.Cache { cache.Configure(cache.Settings{ Enabled: cfg.Cache.Enabled, KeyPrefix: cfg.Cache.KeyPrefix, DefaultTTL: time.Duration(cfg.Cache.DefaultTTL) * time.Second, NullTTL: time.Duration(cfg.Cache.NullTTL) * time.Second, JitterRatio: cfg.Cache.JitterRatio, PostListTTL: time.Duration(cfg.Cache.Modules.PostList) * time.Second, ConversationTTL: time.Duration(cfg.Cache.Modules.Conversation) * time.Second, UnreadCountTTL: time.Duration(cfg.Cache.Modules.UnreadCount) * time.Second, GroupMembersTTL: time.Duration(cfg.Cache.Modules.GroupMembers) * time.Second, DisableFlushDB: cfg.Cache.DisableFlushDB, }) if cfg.Cache.Local.Enabled { layeredCache := cache.NewLayeredCache(redisClient, &cache.LayeredCacheOptions{ LocalSize: cfg.Cache.Local.Size, LocalBuckets: cfg.Cache.Local.Buckets, LocalDefaultTTL: time.Duration(cfg.Cache.Local.DefaultTTL) * time.Second, KeyPrefix: cfg.Cache.KeyPrefix, Enabled: cfg.Cache.Enabled, }) zap.L().Info("Initialized layered cache (local LRU + Redis)") return layeredCache } if redisClient == nil { zap.L().Warn("Redis client is nil, cache will not be available") return nil } cacheInstance := cache.NewRedisCache(redisClient) zap.L().Info("Initialized Redis cache via dependency injection") return cacheInstance } // ProvideHookManager 提供钩子管理器 func ProvideHookManager() *hook.Manager { return hook.NewManager(&hook.ManagerOptions{ MaxConcurrentAsync: 100, Enabled: true, }) } // ProvideBuiltinHooks 注册内置钩子 func ProvideBuiltinHooks(manager *hook.Manager) *hook.BuiltinHooks { builtin := hook.NewBuiltinHooks(manager) builtin.RegisterAll() zap.L().Info("Registered builtin hooks") return builtin } // ProvideModerationHooks 注册审核钩子 func ProvideModerationHooks( manager *hook.Manager, postAIService service.PostAIService, postRepo repository.PostRepository, commentRepo repository.CommentRepository, userRepo repository.UserRepository, sensitiveService service.SensitiveService, cfg *config.Config, ) *hook.ModerationHooks { strictMode := cfg.OpenAI.StrictModeration moderationHooks := hook.NewModerationHooks( postAIService, sensitiveService, postRepo, commentRepo, userRepo, strictMode, "***", ) moderationHooks.RegisterAll(manager) zap.L().Info("Registered moderation hooks", zap.Bool("strict_mode", strictMode), ) return moderationHooks } // ProvideWSMessagePublisher 提供 WebSocket 消息发布器 // standalone 模式返回 *Hub,cluster 模式返回 *Bus(组合 Hub + Redis Pub/Sub) func ProvideWSMessagePublisher(cfg *config.Config, redisClient *redis.Client) ws.MessagePublisher { hub := ws.NewHub() if cfg.WebSocket.Mode == "cluster" && redisClient != nil { bus := ws.NewBus(hub, redisClient.GetClient(), &ws.WSClusterConfig{ InstanceID: cfg.WebSocket.Cluster.InstanceID, MsgChannel: cfg.WebSocket.Cluster.MsgChannel, OnlineTTL: cfg.WebSocket.Cluster.OnlineTTL, HeartbeatInterval: cfg.WebSocket.Cluster.HeartbeatInterval, }) if err := bus.Start(); err != nil { zap.L().Error("Failed to start WebSocket Bus, falling back to standalone mode", zap.Error(err), ) return hub } zap.L().Info("WebSocket running in cluster mode", zap.String("instance_id", bus.InstanceID()), ) return bus } zap.L().Info("WebSocket running in standalone mode") return hub } // ProvideOpenAIClient 提供 OpenAI 客户端 func ProvideOpenAIClient(cfg *config.Config) openai.Client { return openai.NewClient(openai.ConfigFromAppConfig(&cfg.OpenAI)) } // ProvideTencentClient 提供腾讯云TMS客户端 func ProvideTencentClient(cfg *config.Config) tencent.Client { return tencent.NewClient(tencent.Config{ Enabled: cfg.TencentTMS.Enabled, SecretID: cfg.TencentTMS.SecretID, SecretKey: cfg.TencentTMS.SecretKey, Region: cfg.TencentTMS.Region, BizType: cfg.TencentTMS.BizType, Timeout: cfg.TencentTMS.Timeout, }) } // ProvideEmailClient 提供邮件客户端 func ProvideEmailClient(cfg *config.Config) email.Client { return email.NewClient(email.ConfigFromAppConfig(&cfg.Email)) } // ProvideS3Client 提供 S3 客户端 func ProvideS3Client(cfg *config.Config) (*s3.Client, error) { return s3.New(&cfg.S3) } // ProvideJPushClient 提供极光推送客户端 func ProvideJPushClient(cfg *config.Config, logger *zap.Logger) *jpush.Client { if !cfg.JPush.Enabled { logger.Info("JPush is disabled") return nil } if cfg.JPush.AppKey == "" || cfg.JPush.MasterSecret == "" { logger.Warn("JPush is enabled but app_key or master_secret is not configured") return nil } client := jpush.NewClient(cfg.JPush.AppKey, cfg.JPush.MasterSecret, cfg.JPush.Production, logger) logger.Info("JPush client initialized", zap.Bool("production", cfg.JPush.Production), ) return client } // ProvideTransactionManager 提供事务管理器 func ProvideTransactionManager(db *gorm.DB) repository.TransactionManager { return repository.NewTransactionManager(db) } // ProvideMessageEncryptor 提供消息加密器 // 加密启用时密钥必须是 32 字节(AES-256),否则视为致命配置错误,启动期 fail-fast。 func ProvideMessageEncryptor(cfg *config.Config) error { if !cfg.Encryption.Enabled { zap.L().Info("Message encryption is disabled") return nil } if len(cfg.Encryption.Key) != 32 { // 致命安全配置:不允许降级为「加密已启用但实际不加密」 return fmt.Errorf("encryption is enabled but key must be 32 bytes for AES-256, got %d", len(cfg.Encryption.Key)) } if err := crypto.InitMessageEncryptor(cfg.Encryption.Key, cfg.Encryption.KeyVersion); err != nil { return fmt.Errorf("failed to initialize message encryptor: %w", err) } zap.L().Info("Message encryption initialized successfully", zap.Int("key_version", cfg.Encryption.KeyVersion), ) return nil } // ProvideLogger 提供 Zap Logger func ProvideLogger() *zap.Logger { logger, err := zap.NewProduction() if err != nil { panic("Failed to create logger: " + err.Error()) } // 设置为全局 logger,以便在其他地方使用 zap.L() zap.ReplaceGlobals(logger) return logger } // ProvideSeqBufferManager 提供 Seq 预分配管理器 func ProvideSeqBufferManager(cfg *config.Config, redisClient *redis.Client, cacheBackend cache.Cache) *cache.SeqBufferManager { seqCfg := cache.SeqBufferConfig{ Enabled: cfg.SeqBuffer.Enabled, PrivateBufferSize: cfg.SeqBuffer.PrivateBufferSize, GroupBufferSize: cfg.SeqBuffer.GroupBufferSize, LockTimeoutMs: cfg.SeqBuffer.LockTimeoutMs, MaxRetries: cfg.SeqBuffer.MaxRetries, } if !seqCfg.Enabled { return cache.NewSeqBufferManager(nil, seqCfg, nil, nil) } return cache.NewSeqBufferManagerFromRedisPkg(redisClient, seqCfg, nil, nil) } // ProvidePushWorker 提供推送 Worker(Redis Stream 异步推送) func ProvidePushWorker( cfg *config.Config, redisClient *redis.Client, publisher ws.MessagePublisher, pushSvc service.PushService, msgRepo repository.MessageRepository, userRepo repository.UserRepository, ) *service.PushWorker { pushCfg := service.PushWorkerConfig{ Enabled: cfg.PushWorker.Enabled, Stream: cfg.PushWorker.Stream, Group: cfg.PushWorker.Group, BatchSize: cfg.PushWorker.BatchSize, PollTimeoutMs: cfg.PushWorker.PollTimeoutMs, MaxRetries: cfg.PushWorker.MaxRetries, MaxStreamLen: cfg.PushWorker.MaxStreamLen, IdleTimeoutMs: cfg.PushWorker.IdleTimeoutMs, } if !pushCfg.Enabled { return service.NewPushWorker(nil, nil, nil, nil, nil, pushCfg) } var rdb *redisPkg.Client if redisClient != nil { rdb = redisClient.GetClient() } return service.NewPushWorker(rdb, publisher, pushSvc, msgRepo, userRepo, pushCfg) }