Files
backend/internal/wire/service.go
lafay eb931bf1c6
All checks were successful
Build Backend / build (push) Successful in 2m19s
Build Backend / build-docker (push) Successful in 46s
feat(auth): implement session-based token management with revocation support
Add Session model, SessionService, and SessionRepository to track user login
sessions and enable token revocation on auth-critical events.

- Introduce explicit TokenType (access/refresh) in JWT claims to prevent
  refresh token misuse via access token endpoints
- Add SessionID field to JWT claims, enabling stateless JWT validation
  against revoked sessions
- Replace legacy Auth middleware with RequireAuth/OptionalAuth pipeline
  that validates token type, account status, and session validity
- Implement session revocation on password change, reset, user ban/inactive,
  and explicit logout
- Add Principal cache with active invalidation for banned/role-changed users
- Fix IDOR vulnerability: GetMessagesByCursor now validates currentUserID
  is conversation participant via GetParticipantStrict
- Add group member visibility checks: announcements, group info, member list
  now require group membership
- Simplify Casbin policy: remove g grouping, use r.sub == p.sub matcher
  with globMatch; user_roles table is single source of truth for roles
- Add migration logic to clean legacy casbin g rules and migrate old p rules
  from path-style to admin/<domain> resource naming
2026-07-05 18:28:08 +08:00

620 lines
20 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 (
"time"
"with_you/internal/cache"
"with_you/internal/config"
"with_you/internal/grpc/runner"
"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"
)
// ServiceSet Service 层 Provider Set
var ServiceSet = wire.NewSet(
// JWT 服务(需要配置参数)
ProvideJWTService,
// 会话服务(令牌撤销支持)
ProvideSessionService,
// 基础服务
ProvideEmailService,
ProvidePostAIService,
// 核心服务(按依赖顺序)
ProvidePushService,
ProvideSystemMessageService,
ProvidePostService,
ProvideCommentService,
ProvideMessageService,
ProvideNotificationService,
ProvideUserService,
ProvideStickerService,
ProvideVoteService,
ProvideChannelService,
ProvideChatService,
ProvideScheduleService,
ProvideScheduleSyncService,
ProvideGradeSyncService,
ProvideExamSyncService,
ProvideEmptyClassroomSyncService,
ProvideGroupService,
ProvideUploadService,
ProvideUserActivityService,
ProvideRoleService,
ProvideAdminUserService,
ProvideAdminPostService,
ProvideAdminCommentService,
ProvideAdminGroupService,
ProvideAdminDashboardService,
ProvideQRCodeLoginService,
ProvideHotRankWorker,
ProvideFileCleanupWorker,
ProvideDeviceCleanupWorker,
ProvideMaterialService,
ProvideCallService,
ProvideLiveKitService,
ProvideReportService,
ProvideAdminReportService,
ProvideVerificationService,
ProvideAdminVerificationService,
ProvideSensitiveService,
ProvideUserProfileAuditService,
ProvideSetupService,
// 帖子内链引用
ProvidePostRefService,
// 二手交易
ProvideTradeService,
// 日志服务
ProvideAsyncLogManager,
ProvideOperationLogService,
ProvideLoginLogService,
ProvideDataChangeLogService,
ProvideLogCleanupService,
ProvideLogService,
)
// ProvideJWTService 提供 JWT 服务
func ProvideJWTService(cfg *config.Config) service.JWTService {
return service.NewJWTService(
cfg.JWT.Secret,
int64(cfg.JWT.AccessTokenExpire.Seconds()),
int64(cfg.JWT.RefreshTokenExpire.Seconds()),
)
}
// ProvideSessionService 提供会话服务(用于令牌撤销)。
func ProvideSessionService(
sessionRepo repository.SessionRepository,
jwtService service.JWTService,
) service.SessionService {
return service.NewSessionService(sessionRepo, jwtService)
}
// ProvideEmailService 提供邮件服务
func ProvideEmailService(client email.Client) service.EmailService {
return service.NewEmailService(client)
}
// ProvidePostAIService 提供 AI 服务
func ProvidePostAIService(openAIClient openai.Client, tencentClient tencent.Client, cfg *config.Config) service.PostAIService {
return service.NewPostAIService(openAIClient, tencentClient, cfg.OpenAI.StrictModeration)
}
// ProvidePushService 提供推送服务
func ProvidePushService(
pushRepo repository.PushRecordRepository,
deviceTokenRepo repository.DeviceTokenRepository,
messageRepo repository.MessageRepository,
publisher ws.MessagePublisher,
jpushClient *jpush.Client,
cfg *config.Config,
) service.PushService {
return service.NewPushService(pushRepo, deviceTokenRepo, messageRepo, publisher, jpushClient, cfg.JPush.Channel)
}
// ProvideSystemMessageService 提供系统消息服务
func ProvideSystemMessageService(
notifyRepo repository.SystemNotificationRepository,
pushService service.PushService,
userRepo repository.UserRepository,
postRepo repository.PostRepository,
cacheBackend cache.Cache,
) service.SystemMessageService {
return service.NewSystemMessageService(notifyRepo, pushService, userRepo, postRepo, cacheBackend)
}
func ProvidePostService(
postRepo repository.PostRepository,
systemMessageService service.SystemMessageService,
postAIService service.PostAIService,
cacheBackend cache.Cache,
txManager repository.TransactionManager,
hookManager *hook.Manager,
_ *hook.ModerationHooks,
_ *hook.BuiltinHooks,
logService *service.LogService,
) service.PostService {
return service.NewPostService(postRepo, systemMessageService, postAIService, cacheBackend, txManager, hookManager, logService)
}
func ProvideCommentService(
commentRepo repository.CommentRepository,
postRepo repository.PostRepository,
systemMessageService service.SystemMessageService,
postAIService service.PostAIService,
cacheBackend cache.Cache,
hookManager *hook.Manager,
logService *service.LogService,
) service.CommentService {
return service.NewCommentService(commentRepo, postRepo, systemMessageService, postAIService, cacheBackend, hookManager, logService)
}
// ProvideMessageService 提供消息服务
func ProvideMessageService(
messageRepo repository.MessageRepository,
cacheBackend cache.Cache,
uploadService service.UploadService,
) service.MessageService {
return service.NewMessageService(messageRepo, cacheBackend, uploadService)
}
// ProvideNotificationService 提供通知服务
func ProvideNotificationService(
notificationRepo repository.NotificationRepository,
cacheBackend cache.Cache,
) service.NotificationService {
return service.NewNotificationService(notificationRepo, cacheBackend)
}
// ProvideUserService 提供用户服务
func ProvideUserService(
userRepo repository.UserRepository,
systemMessageService service.SystemMessageService,
emailService service.EmailService,
cacheBackend cache.Cache,
logService *service.LogService,
sessionService service.SessionService,
) service.UserService {
return service.NewUserService(userRepo, systemMessageService, emailService, cacheBackend, logService, sessionService)
}
// ProvideStickerService 提供表情服务
func ProvideStickerService(
stickerRepo repository.StickerRepository,
) service.StickerService {
return service.NewStickerService(stickerRepo)
}
// ProvideVoteService 提供投票服务
func ProvideVoteService(
voteRepo repository.VoteRepository,
postRepo repository.PostRepository,
cache cache.Cache,
postAIService service.PostAIService,
systemMessageService service.SystemMessageService,
hookManager *hook.Manager,
logService *service.LogService,
) service.VoteService {
return service.NewVoteService(voteRepo, postRepo, cache, postAIService, systemMessageService, hookManager, logService)
}
func ProvideChannelService(channelRepo repository.ChannelRepository, cacheBackend cache.Cache) service.ChannelService {
return service.NewChannelService(channelRepo, cacheBackend)
}
// ProvideChatService 提供聊天服务
// Note: sensitiveService 传 nil与 main.go 保持一致
func ProvideChatService(
messageRepo repository.MessageRepository,
userRepo repository.UserRepository,
publisher ws.MessagePublisher,
cacheBackend cache.Cache,
uploadService service.UploadService,
pushService service.PushService,
seqBufferMgr *cache.SeqBufferManager,
pushWorker *service.PushWorker,
redisClient *redis.Client,
versionLogRepo repository.ConversationVersionLogRepository,
cfg *config.Config,
groupService service.GroupService,
) service.ChatService {
var vlogOpt []repository.ConversationVersionLogRepository
if cfg.VersionLog.Enabled {
vlogOpt = []repository.ConversationVersionLogRepository{versionLogRepo}
}
var rdb *redisPkg.Client
if redisClient != nil {
rdb = redisClient.GetClient()
}
// 注入 GroupAbility群消息发送前校验成员/禁言。
groupAbility := service.NewGroupAbility(groupService)
return service.NewChatService(messageRepo, userRepo, nil, publisher, cacheBackend, uploadService, pushService, seqBufferMgr, pushWorker, rdb, groupAbility, vlogOpt...)
}
// ProvideScheduleService 提供日程服务
func ProvideScheduleService(
scheduleRepo repository.ScheduleRepository,
) service.ScheduleService {
return service.NewScheduleService(scheduleRepo)
}
// ProvideScheduleSyncService 提供课表同步服务
func ProvideScheduleSyncService(
taskManager *runner.TaskManager,
scheduleRepo repository.ScheduleRepository,
logger *zap.Logger,
) service.ScheduleSyncService {
return service.NewScheduleSyncService(taskManager, scheduleRepo, logger)
}
// ProvideGradeSyncService 提供成绩同步服务
func ProvideGradeSyncService(
taskManager *runner.TaskManager,
gradeRepo repository.GradeRepository,
logger *zap.Logger,
) service.GradeSyncService {
return service.NewGradeSyncService(taskManager, gradeRepo, logger)
}
// ProvideExamSyncService 提供考试安排同步服务
func ProvideExamSyncService(
taskManager *runner.TaskManager,
examRepo repository.ExamRepository,
logger *zap.Logger,
) service.ExamSyncService {
return service.NewExamSyncService(taskManager, examRepo, logger)
}
// ProvideEmptyClassroomSyncService 提供空教室同步服务
func ProvideEmptyClassroomSyncService(
taskManager *runner.TaskManager,
classroomRepo repository.EmptyClassroomRepository,
logger *zap.Logger,
) service.EmptyClassroomSyncService {
return service.NewEmptyClassroomSyncService(taskManager, classroomRepo, logger)
}
// ProvideGroupService 提供群组服务
func ProvideGroupService(
groupRepo repository.GroupRepository,
userRepo repository.UserRepository,
messageRepo repository.MessageRepository,
requestRepo repository.GroupJoinRequestRepository,
notifyRepo repository.SystemNotificationRepository,
publisher ws.MessagePublisher,
cacheBackend cache.Cache,
) service.GroupService {
return service.NewGroupService(groupRepo, userRepo, messageRepo, requestRepo, notifyRepo, publisher, cacheBackend)
}
// ProvideUploadService 提供上传服务
func ProvideUploadService(
s3Client *s3.Client,
userService service.UserService,
uploadedRepo repository.UploadedFileRepository,
) service.UploadService {
return service.NewUploadService(s3Client, userService, uploadedRepo)
}
// ProvideFileCleanupWorker 提供聊天文件过期清理 worker
func ProvideFileCleanupWorker(
cfg *config.Config,
uploadedRepo repository.UploadedFileRepository,
s3Client *s3.Client,
redisClient *redis.Client,
) *service.FileCleanupWorker {
var rdb *redisPkg.Client
if redisClient != nil {
rdb = redisClient.GetClient()
}
return service.NewFileCleanupWorker(cfg, uploadedRepo, s3Client, rdb)
}
// ProvideDeviceCleanupWorker 提供设备 registration_id 清理 worker
func ProvideDeviceCleanupWorker(
cfg *config.Config,
deviceRepo repository.DeviceTokenRepository,
redisClient *redis.Client,
) *service.DeviceCleanupWorker {
var rdb *redisPkg.Client
if redisClient != nil {
rdb = redisClient.GetClient()
}
return service.NewDeviceCleanupWorker(cfg, deviceRepo, rdb)
}
// ProvideUserActivityService 提供用户活跃统计服务
func ProvideUserActivityService(repo repository.UserActivityRepository) service.UserActivityService {
return service.NewUserActivityService(repo)
}
// ProvideRoleService 提供角色管理服务
func ProvideRoleService(
roleRepo repository.RoleRepository,
casbinSvc service.CasbinService,
) service.RoleService {
return service.NewRoleService(roleRepo, casbinSvc)
}
// ProvideAdminUserService 提供管理端用户服务
//
// 注入 SessionService 与 cache封禁/停用用户时撤销其全部登录会话并失效 Principal 缓存。
func ProvideAdminUserService(
userRepo repository.UserRepository,
sessionService service.SessionService,
cacheBackend cache.Cache,
) service.AdminUserService {
return service.NewAdminUserService(userRepo, sessionService, cacheBackend)
}
// ProvideAdminPostService 提供管理端帖子服务
func ProvideAdminPostService(postRepo repository.PostRepository, cacheBackend cache.Cache, logService *service.LogService) service.AdminPostService {
return service.NewAdminPostService(postRepo, cacheBackend, logService)
}
// ProvideAdminCommentService 提供管理端评论服务
func ProvideAdminCommentService(commentRepo repository.CommentRepository, postRepo repository.PostRepository, cacheBackend cache.Cache) service.AdminCommentService {
return service.NewAdminCommentService(commentRepo, postRepo, cacheBackend)
}
// ProvideAdminGroupService 提供管理端群组服务
func ProvideAdminGroupService(
groupRepo repository.GroupRepository,
userRepo repository.UserRepository,
) service.AdminGroupService {
return service.NewAdminGroupService(groupRepo, userRepo)
}
// ProvideAdminDashboardService 提供管理端仪表盘服务
func ProvideAdminDashboardService(
db *gorm.DB,
userRepo repository.UserRepository,
postRepo repository.PostRepository,
commentRepo repository.CommentRepository,
groupRepo repository.GroupRepository,
activityRepo repository.UserActivityRepository,
) service.AdminDashboardService {
return service.NewAdminDashboardService(db, userRepo, postRepo, commentRepo, groupRepo, activityRepo)
}
// ProvideAsyncLogManager 提供异步日志管理器
func ProvideAsyncLogManager(
operationRepo repository.OperationLogRepository,
loginRepo repository.LoginLogRepository,
dataChangeRepo repository.DataChangeLogRepository,
logger *zap.Logger,
cfg *config.Config,
) *service.AsyncLogManager {
asyncCfg := &service.AsyncLogConfig{
BufferSize: cfg.Log.Async.BufferSize,
BatchSize: cfg.Log.Async.BatchSize,
WorkerCount: cfg.Log.Async.WorkerCount,
EnableFallback: cfg.Log.Async.EnableFallback,
FallbackPath: cfg.Log.Async.FallbackPath,
}
// 解析刷新间隔
if duration, err := parseDuration(cfg.Log.Async.FlushInterval); err == nil {
asyncCfg.FlushInterval = duration
} else {
asyncCfg.FlushInterval = 3 * time.Second
}
return service.NewAsyncLogManager(operationRepo, loginRepo, dataChangeRepo, logger, asyncCfg)
}
// ProvideOperationLogService 提供操作日志服务
func ProvideOperationLogService(
asyncManager *service.AsyncLogManager,
operationRepo repository.OperationLogRepository,
) service.OperationLogService {
return service.NewOperationLogService(asyncManager, operationRepo)
}
// ProvideLoginLogService 提供登录日志服务
func ProvideLoginLogService(
asyncManager *service.AsyncLogManager,
loginRepo repository.LoginLogRepository,
) service.LoginLogService {
return service.NewLoginLogService(asyncManager, loginRepo)
}
// ProvideDataChangeLogService 提供数据变更日志服务
func ProvideDataChangeLogService(
asyncManager *service.AsyncLogManager,
dataChangeRepo repository.DataChangeLogRepository,
) service.DataChangeLogService {
return service.NewDataChangeLogService(asyncManager, dataChangeRepo)
}
// ProvideLogCleanupService 提供日志清理服务
func ProvideLogCleanupService(
operationRepo repository.OperationLogRepository,
loginRepo repository.LoginLogRepository,
dataChangeRepo repository.DataChangeLogRepository,
logger *zap.Logger,
cfg *config.Config,
) service.LogCleanupService {
cleanupCfg := &service.LogCleanupConfig{
Enabled: true,
OperationLog: cfg.Log.Retention.OperationLog,
LoginLog: cfg.Log.Retention.LoginLog,
DataChangeLog: cfg.Log.Retention.DataChange,
CleanupTime: "02:00",
}
return service.NewLogCleanupService(operationRepo, loginRepo, dataChangeRepo, cleanupCfg, logger)
}
// ProvideLogService 提供日志服务组合
func ProvideLogService(
operationLogService service.OperationLogService,
loginLogService service.LoginLogService,
dataChangeLogService service.DataChangeLogService,
) *service.LogService {
return service.NewLogService(operationLogService, loginLogService, dataChangeLogService)
}
// ProvideQRCodeLoginService 提供二维码登录服务
func ProvideHotRankWorker(cfg *config.Config, postRepo repository.PostRepository, channelRepo repository.ChannelRepository, cacheBackend cache.Cache, redisClient *redis.Client) *service.HotRankWorker {
var rdb *redisPkg.Client
if redisClient != nil {
rdb = redisClient.GetClient()
}
return service.NewHotRankWorker(cfg, postRepo, channelRepo, cacheBackend, rdb)
}
func ProvideQRCodeLoginService(
redisClient *redis.Client,
publisher ws.MessagePublisher,
jwtService service.JWTService,
userService service.UserService,
activityService service.UserActivityService,
logService *service.LogService,
) service.QRCodeLoginService {
return service.NewQRCodeLoginService(redisClient, publisher, jwtService, userService, activityService, logService)
}
// ProvideMaterialService 提供学习资料服务
func ProvideMaterialService(
subjectRepo repository.MaterialSubjectRepository,
fileRepo repository.MaterialFileRepository,
) service.MaterialService {
return service.NewMaterialService(subjectRepo, fileRepo)
}
// parseDuration 解析时长字符串
func parseDuration(s string) (time.Duration, error) {
return time.ParseDuration(s)
}
// ProvideCallService 提供通话服务
func ProvideCallService(
callRepo repository.CallRepository,
publisher ws.MessagePublisher,
cfg *config.Config,
db *gorm.DB,
redisClient *redis.Client,
groupService service.GroupService,
) service.CallService {
return service.NewCallService(callRepo, publisher, cfg, db, redisClient, groupService)
}
// ProvideLiveKitService 提供 LiveKit 服务
func ProvideLiveKitService(
cfg *config.Config,
logger *zap.Logger,
) service.LiveKitService {
return service.NewLiveKitService(cfg, logger)
}
// ProvideReportService 提供举报服务
func ProvideReportService(
reportRepo repository.ReportRepository,
postRepo repository.PostRepository,
commentRepo repository.CommentRepository,
messageRepo repository.MessageRepository,
userRepo repository.UserRepository,
notifyRepo repository.SystemNotificationRepository,
pushService service.PushService,
txManager repository.TransactionManager,
logService service.OperationLogService,
cfg *config.Config,
) service.ReportService {
return service.NewReportService(reportRepo, postRepo, commentRepo, messageRepo, userRepo, notifyRepo, pushService, txManager, logService, cfg)
}
// ProvideAdminReportService 提供管理端举报服务
func ProvideAdminReportService(
reportRepo repository.ReportRepository,
postRepo repository.PostRepository,
commentRepo repository.CommentRepository,
messageRepo repository.MessageRepository,
userRepo repository.UserRepository,
notifyRepo repository.SystemNotificationRepository,
pushService service.PushService,
txManager repository.TransactionManager,
logService service.OperationLogService,
) service.AdminReportService {
return service.NewAdminReportService(reportRepo, postRepo, commentRepo, messageRepo, userRepo, notifyRepo, pushService, txManager, logService)
}
// ProvideVerificationService 提供身份认证服务
func ProvideVerificationService(
verificationRepo repository.VerificationRepository,
userRepo repository.UserRepository,
) service.VerificationService {
return service.NewVerificationService(verificationRepo, userRepo)
}
// ProvideAdminVerificationService 提供管理端身份认证服务
func ProvideAdminVerificationService(
verificationRepo repository.VerificationRepository,
userRepo repository.UserRepository,
) service.AdminVerificationService {
return service.NewAdminVerificationService(verificationRepo, userRepo)
}
// ProvideSensitiveService 提供敏感词服务
func ProvideSensitiveService(
sensitiveWordRepo repository.SensitiveWordRepository,
redisClient *redis.Client,
cfg *config.Config,
) service.SensitiveService {
sensitiveCfg := &service.SensitiveConfig{
Enabled: cfg.Sensitive.Enabled,
ReplaceStr: cfg.Sensitive.ReplaceStr,
MinMatchLen: cfg.Sensitive.MinMatchLen,
LoadFromDB: cfg.Sensitive.LoadFromDB,
LoadFromRedis: cfg.Sensitive.LoadFromRedis,
RedisKeyPrefix: cfg.Sensitive.RedisKeyPrefix,
}
return service.NewSensitiveService(sensitiveWordRepo, redisClient, sensitiveCfg)
}
func ProvideUserProfileAuditService(
auditRepo repository.UserProfileAuditRepository,
userRepo repository.UserRepository,
hookManager *hook.Manager,
moderationHooks *hook.ModerationHooks,
) service.UserProfileAuditService {
return service.NewUserProfileAuditService(auditRepo, userRepo, hookManager, moderationHooks)
}
func ProvideSetupService(
userRepo repository.UserRepository,
roleRepo repository.RoleRepository,
casbinSvc service.CasbinService,
cfg *config.Config,
) service.SetupService {
return service.NewSetupService(userRepo, roleRepo, casbinSvc, cfg.SetupSecret)
}
func ProvidePostRefService(
refRepo repository.PostRefRepository,
postRepo repository.PostRepository,
) service.PostRefService {
return service.NewPostRefService(refRepo, postRepo)
}
func ProvideTradeService(tradeRepo repository.TradeRepository) service.TradeService {
return service.NewTradeService(tradeRepo)
}