Files
backend/internal/wire/infrastructure.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

328 lines
9.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 模式返回 *Hubcluster 模式返回 *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 提供推送 WorkerRedis 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)
}