Add Tencent Cloud Text Moderation System (TMS) as a fallback moderation service when OpenAI is unavailable or returns an error. The service now checks OpenAI first, and falls back to Tencent TMS if it fails, providing better reliability for content moderation. Features: - New Tencent TMS configuration in config.yaml with environment variable overrides - New tencent package in internal/pkg/tencent/ with TMS client implementation - PostAIService now uses dual moderation with OpenAI primary and Tencent fallback - Strict mode configuration passed to PostAIService for consistent behavior BREAKING CHANGE: NewPostAIService now requires tencentClient and strictMode parameters - update wire configuration accordingly.
236 lines
6.2 KiB
Go
236 lines
6.2 KiB
Go
package wire
|
||
|
||
import (
|
||
"time"
|
||
|
||
"carrot_bbs/internal/cache"
|
||
"carrot_bbs/internal/config"
|
||
"carrot_bbs/internal/model"
|
||
"carrot_bbs/internal/pkg/crypto"
|
||
"carrot_bbs/internal/pkg/email"
|
||
"carrot_bbs/internal/pkg/hook"
|
||
"carrot_bbs/internal/pkg/openai"
|
||
"carrot_bbs/internal/pkg/redis"
|
||
"carrot_bbs/internal/pkg/s3"
|
||
"carrot_bbs/internal/pkg/tencent"
|
||
"carrot_bbs/internal/pkg/ws"
|
||
"carrot_bbs/internal/repository"
|
||
"carrot_bbs/internal/service"
|
||
|
||
"github.com/google/wire"
|
||
"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 Hub
|
||
ProvideWSHub,
|
||
|
||
// 外部服务客户端
|
||
ProvideOpenAIClient,
|
||
ProvideTencentClient,
|
||
ProvideEmailClient,
|
||
ProvideS3Client,
|
||
|
||
// 消息加密器
|
||
ProvideMessageEncryptor,
|
||
)
|
||
|
||
// ProvideConfig 加载配置
|
||
func ProvideConfig() (*config.Config, error) {
|
||
return config.Load("configs/config.yaml")
|
||
}
|
||
|
||
// ProvideDB 提供数据库连接
|
||
func ProvideDB(cfg *config.Config) (*gorm.DB, error) {
|
||
return model.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
|
||
}
|
||
|
||
// ProvideWSHub 提供 WebSocket Hub
|
||
func ProvideWSHub() *ws.Hub {
|
||
return ws.NewHub()
|
||
}
|
||
|
||
// 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)
|
||
}
|
||
|
||
// ProvideTransactionManager 提供事务管理器
|
||
func ProvideTransactionManager(db *gorm.DB) repository.TransactionManager {
|
||
return repository.NewTransactionManager(db)
|
||
}
|
||
|
||
// ProvideMessageEncryptor 提供消息加密器
|
||
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 {
|
||
zap.L().Error("Encryption key must be 32 bytes for AES-256",
|
||
zap.Int("actual_length", len(cfg.Encryption.Key)),
|
||
)
|
||
return nil
|
||
}
|
||
|
||
if err := crypto.InitMessageEncryptor(cfg.Encryption.Key, cfg.Encryption.KeyVersion); err != nil {
|
||
zap.L().Error("Failed to initialize message encryptor", zap.Error(err))
|
||
return 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
|
||
}
|