feat: add hook system, QR code login, and layered cache
All checks were successful
Build Backend / build (push) Successful in 5m54s
Build Backend / build-docker (push) Successful in 4m7s

- Implement extensible hook system for content moderation with
  builtin and AI-powered moderation hooks
- Add QR code login feature with SSE for real-time status updates
  and scan/confirm/cancel workflow
- Introduce layered cache with local LRU + Redis backend for
  improved read performance
- Refactor post and comment services to use hook-based moderation
  instead of direct AI service calls
- Add local cache configuration options (size, buckets, ttl)
This commit is contained in:
lafay
2026-03-20 12:23:28 +08:00
parent bdfcbdadea
commit 98f0c9f2b6
23 changed files with 2726 additions and 76 deletions

View File

@@ -28,6 +28,7 @@ var HandlerSet = wire.NewSet(
handler.NewAdminGroupHandler,
handler.NewAdminDashboardHandler,
handler.NewAdminLogHandler,
handler.NewQRCodeHandler,
// 需要特殊处理的 Handler
ProvideUserHandler,

View File

@@ -9,11 +9,13 @@ import (
"carrot_bbs/internal/pkg/crypto"
"carrot_bbs/internal/pkg/email"
"carrot_bbs/internal/pkg/gorse"
"carrot_bbs/internal/pkg/hook"
"carrot_bbs/internal/pkg/openai"
"carrot_bbs/internal/pkg/redis"
"carrot_bbs/internal/pkg/s3"
"carrot_bbs/internal/pkg/sse"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service"
"github.com/google/wire"
"go.uber.org/zap"
@@ -38,6 +40,11 @@ var InfrastructureSet = wire.NewSet(
ProvideRedisClient,
ProvideCache,
// 钩子系统
ProvideHookManager,
ProvideBuiltinHooks,
ProvideModerationHooks,
// SSE Hub
ProvideSSEHub,
@@ -88,7 +95,18 @@ func ProvideCache(cfg *config.Config, redisClient *redis.Client) cache.Cache {
DisableFlushDB: cfg.Cache.DisableFlushDB,
})
// 直接创建 RedisCache 实例,不依赖全局变量
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
@@ -99,6 +117,47 @@ func ProvideCache(cfg *config.Config, redisClient *redis.Client) cache.Cache {
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,
cfg *config.Config,
) *hook.ModerationHooks {
strictMode := cfg.OpenAI.StrictModeration
moderationHooks := hook.NewModerationHooks(
postAIService,
nil,
postRepo,
commentRepo,
strictMode,
"***",
)
moderationHooks.RegisterAll(manager)
zap.L().Info("Registered moderation hooks",
zap.Bool("strict_mode", strictMode),
)
return moderationHooks
}
// ProvideSSEHub 提供 SSE Hub
func ProvideSSEHub() *sse.Hub {
return sse.NewHub()

View File

@@ -8,7 +8,9 @@ import (
"carrot_bbs/internal/grpc/runner"
"carrot_bbs/internal/pkg/email"
"carrot_bbs/internal/pkg/gorse"
"carrot_bbs/internal/pkg/hook"
"carrot_bbs/internal/pkg/openai"
"carrot_bbs/internal/pkg/redis"
"carrot_bbs/internal/pkg/s3"
"carrot_bbs/internal/pkg/sse"
"carrot_bbs/internal/repository"
@@ -50,6 +52,7 @@ var ServiceSet = wire.NewSet(
ProvideAdminCommentService,
ProvideAdminGroupService,
ProvideAdminDashboardService,
ProvideQRCodeLoginService,
// 日志服务
ProvideAsyncLogManager,
@@ -100,7 +103,6 @@ func ProvideSystemMessageService(
return service.NewSystemMessageService(notifyRepo, pushService, userRepo, postRepo, cacheBackend)
}
// ProvidePostService 提供帖子服务
func ProvidePostService(
postRepo *repository.PostRepository,
systemMessageService service.SystemMessageService,
@@ -108,11 +110,11 @@ func ProvidePostService(
postAIService *service.PostAIService,
cacheBackend cache.Cache,
txManager repository.TransactionManager,
hookManager *hook.Manager,
) service.PostService {
return service.NewPostService(postRepo, systemMessageService, gorseClient, postAIService, cacheBackend, txManager)
return service.NewPostService(postRepo, systemMessageService, gorseClient, postAIService, cacheBackend, txManager, hookManager)
}
// ProvideCommentService 提供评论服务
func ProvideCommentService(
commentRepo *repository.CommentRepository,
postRepo *repository.PostRepository,
@@ -120,8 +122,9 @@ func ProvideCommentService(
gorseClient gorse.Client,
postAIService *service.PostAIService,
cacheBackend cache.Cache,
hookManager *hook.Manager,
) *service.CommentService {
return service.NewCommentService(commentRepo, postRepo, systemMessageService, gorseClient, postAIService, cacheBackend)
return service.NewCommentService(commentRepo, postRepo, systemMessageService, gorseClient, postAIService, cacheBackend, hookManager)
}
// ProvideMessageService 提供消息服务
@@ -343,6 +346,18 @@ func ProvideLogService(
return service.NewLogService(operationLogService, loginLogService, dataChangeLogService)
}
// ProvideQRCodeLoginService 提供二维码登录服务
func ProvideQRCodeLoginService(
redisClient *redis.Client,
sseHub *sse.Hub,
jwtService *service.JWTService,
userService service.UserService,
activityService service.UserActivityService,
logService *service.LogService,
) *service.QRCodeLoginService {
return service.NewQRCodeLoginService(redisClient, sseHub, jwtService, userService, activityService, logService)
}
// parseDuration 解析时长字符串
func parseDuration(s string) (time.Duration, error) {
return time.ParseDuration(s)