package service import ( "context" "go.uber.org/zap" "with_you/internal/cache" "with_you/internal/dto" "with_you/internal/model" "with_you/internal/pkg/auth" "with_you/internal/repository" ) // AdminUserService 管理端用户服务接口 type AdminUserService interface { // GetUserList 获取用户列表(分页、搜索、状态筛选) GetUserList(ctx context.Context, page, pageSize int, keyword, status string) ([]dto.AdminUserListResponse, int64, error) // GetUserDetail 获取用户详情(含统计信息) GetUserDetail(ctx context.Context, userID string) (*dto.AdminUserDetailResponse, error) // UpdateUserStatus 更新用户状态 UpdateUserStatus(ctx context.Context, userID string, status model.UserStatus) (*dto.AdminUserDetailResponse, error) } // adminUserServiceImpl 管理端用户服务实现 // // sessionSvc 与 cache 用于在封禁/停用用户时撤销其全部登录会话并失效 Principal 缓存, // 让 RequireAuth 在下一次请求时重新加载用户状态(避免 30s 缓存窗口内仍可访问)。 type adminUserServiceImpl struct { userRepo repository.UserRepository sessionSvc SessionService cache cache.Cache } // NewAdminUserService 创建管理端用户服务 func NewAdminUserService(userRepo repository.UserRepository, sessionSvc SessionService, cache cache.Cache) AdminUserService { return &adminUserServiceImpl{ userRepo: userRepo, sessionSvc: sessionSvc, cache: cache, } } // GetUserList 获取用户列表 func (s *adminUserServiceImpl) GetUserList(ctx context.Context, page, pageSize int, keyword, status string) ([]dto.AdminUserListResponse, int64, error) { users, total, err := s.userRepo.AdminList(page, pageSize, keyword, status) if err != nil { return nil, 0, err } responses := make([]dto.AdminUserListResponse, len(users)) for i, user := range users { responses[i] = convertUserToAdminListResponse(user) } return responses, total, nil } // GetUserDetail 获取用户详情 func (s *adminUserServiceImpl) GetUserDetail(ctx context.Context, userID string) (*dto.AdminUserDetailResponse, error) { user, err := s.userRepo.GetByID(userID) if err != nil { return nil, err } // 获取帖子数 postsCount, _ := s.userRepo.GetPostsCount(userID) // 获取评论数 commentsCount, _ := s.userRepo.GetCommentsCount(userID) return convertUserToAdminDetailResponse(user, postsCount, commentsCount), nil } // UpdateUserStatus 更新用户状态 func (s *adminUserServiceImpl) UpdateUserStatus(ctx context.Context, userID string, status model.UserStatus) (*dto.AdminUserDetailResponse, error) { // 先检查用户是否存在 user, err := s.userRepo.GetByID(userID) if err != nil { return nil, err } // 更新状态 if err := s.userRepo.UpdateStatus(userID, status); err != nil { return nil, err } // 状态变更后的会话/缓存联动: // - banned/inactive:撤销该用户全部会话(refresh token 立即失效)并失效 Principal 缓存, // access token 在剩余 TTL 内仍可用(无状态 JWT 固有限制),但 refresh 已不可续期。 // - active:从封禁恢复时同样失效 Principal 缓存,让下次请求重新加载新状态。 // 失败仅告警不阻塞主流程:状态变更本身已成功,缓存最终会自然过期。 if status == model.UserStatusBanned || status == model.UserStatusInactive { if s.sessionSvc != nil { if rerr := s.sessionSvc.RevokeAllByUser(ctx, userID); rerr != nil { zap.L().Warn("failed to revoke sessions on user status change", zap.String("user_id", userID), zap.String("status", string(status)), zap.Error(rerr), ) } } } if status != user.Status { auth.InvalidatePrincipalCache(s.cache, userID) } // 重新获取用户信息 user, err = s.userRepo.GetByID(userID) if err != nil { return nil, err } // 获取统计信息 postsCount, _ := s.userRepo.GetPostsCount(userID) commentsCount, _ := s.userRepo.GetCommentsCount(userID) return convertUserToAdminDetailResponse(user, postsCount, commentsCount), nil } // convertUserToAdminListResponse 转换用户为管理端列表响应 func convertUserToAdminListResponse(user *model.User) dto.AdminUserListResponse { var lastLoginAt string if user.LastLoginAt != nil { lastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05") } return dto.AdminUserListResponse{ ID: user.ID, Username: user.Username, Nickname: user.Nickname, Email: user.Email, Phone: user.Phone, EmailVerified: user.EmailVerified, Avatar: user.Avatar, Status: string(user.Status), PostsCount: user.PostsCount, FollowersCount: user.FollowersCount, FollowingCount: user.FollowingCount, LastLoginAt: lastLoginAt, LastLoginIP: user.LastLoginIP, CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), } } // convertUserToAdminDetailResponse 转换用户为管理端详情响应 func convertUserToAdminDetailResponse(user *model.User, postsCount, commentsCount int64) *dto.AdminUserDetailResponse { var lastLoginAt string if user.LastLoginAt != nil { lastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05") } return &dto.AdminUserDetailResponse{ ID: user.ID, Username: user.Username, Nickname: user.Nickname, Email: user.Email, Phone: user.Phone, EmailVerified: user.EmailVerified, Avatar: user.Avatar, CoverURL: user.CoverURL, Bio: user.Bio, Website: user.Website, Location: user.Location, IsVerified: user.IsVerified, Status: string(user.Status), PostsCount: int(postsCount), CommentsCount: commentsCount, FollowersCount: user.FollowersCount, FollowingCount: user.FollowingCount, LastLoginAt: lastLoginAt, LastLoginIP: user.LastLoginIP, CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), } }