This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/internal/service/admin_service.go
Victor_Jay daf87f895b refactor: JWT 无状态认证替换为服务端 Session,修复记住我掉线问题
- 新增 internal/session 包:Session 结构体、Store 接口、MemoryStore(内存+后台清理)、RedisStore 预留
- Cookie 从 3 个精简为 1 个 mlb_sid(HttpOnly),移除 JWT access/refresh cookie
- 会话过期采用滑动窗口:每次请求自动续期,记住我 30 天无操作过期
- AuthMiddleware/AuthAdmin/Maintenance 中间件改用 SessionManager.Validate()
- AuthService Login/Register/ConfirmRestore 去除 token 生成,返回 *model.User
- Controller 层在登录/注册后调用 SessionManager.Create 创建会话
- AdminService UpdateUserStatus/ResetUserToken 同步销毁 session 实现即时退登
- 改密/注销时通过 DestroyByUID 删除所有 session(替换 TokenVersion 检查)
- config.yaml 新增 session 配置段(idle_timeout/remember_timeout/cleanup_interval)
- TokenService/auth_parser/auth_token 标记废弃,移除 JWT 到期字段依赖
2026-05-30 23:36:52 +08:00

147 lines
4.4 KiB
Go
Raw 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 service
import (
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"metazone.cc/metalab/internal/session"
)
type AdminService struct {
userRepo userAdminStore
sessionManager *session.Manager
}
// NewAdminService 构造函数
func NewAdminService(userRepo userAdminStore, sm *session.Manager) *AdminService {
return &AdminService{userRepo: userRepo, sessionManager: sm}
}
// ListUsersParams 用户列表查询参数
type ListUsersParams struct {
Keyword string // 搜索关键字(邮箱/用户名)
Role string // 角色筛选
Status string // 状态筛选
Page int // 页码
PageSize int // 每页条数
}
// ListUsersResult 用户列表查询结果
type ListUsersResult struct {
Users []model.User `json:"users"`
Total int64 `json:"total"`
Page int `json:"page"`
}
// ListUsers 分页搜索用户列表
func (s *AdminService) ListUsers(params ListUsersParams) (*ListUsersResult, error) {
p := common.Pagination{Page: params.Page, PageSize: params.PageSize}
p.DefaultPagination()
total, err := s.userRepo.CountSearchUsers(params.Keyword, params.Role, params.Status)
if err != nil {
return nil, err
}
users, err := s.userRepo.SearchUsers(params.Keyword, params.Role, params.Status, p.Offset(), p.PageSize)
if err != nil {
return nil, err
}
return &ListUsersResult{
Users: users,
Total: total,
Page: p.Page,
}, nil
}
// UpdateUserStatus 修改用户状态
// 权限规则:
// - 不可操作自己
// - Admin 只能设置 active/banned且只能操作 RoleUser
// - Owner 可设置 active/banned/locked可操作任何人除自己
func (s *AdminService) UpdateUserStatus(operatorUID, targetUID uint, newStatus string) error {
// 校验状态值合法性
switch newStatus {
case model.StatusActive, model.StatusBanned, model.StatusLocked:
default:
return common.ErrPermissionDenied
}
return s.checkAndOperate(operatorUID, targetUID, func(operator, target *model.User) error {
// locked 相关操作仅 owner
if target.Status == model.StatusLocked || newStatus == model.StatusLocked {
if !model.HasMinRole(operator.Role, model.RoleOwner) {
return common.ErrPermissionDenied
}
}
if err := s.userRepo.UpdateStatus(target.ID, newStatus); err != nil {
return err
}
// 锁定(删除)→ GORM 软删除释放邮箱;解锁 → 先查邮箱冲突再恢复
if newStatus == model.StatusLocked {
if err := s.userRepo.SoftDelete(target.ID); err != nil {
return err
}
} else if target.Status == model.StatusLocked && newStatus == model.StatusActive {
// 检查邮箱是否已被新用户注册(软删期间邮箱释放了)
collision, err := s.userRepo.ExistsByEmailExclude(target.Email, target.ID)
if err != nil {
return err
}
if collision {
return common.ErrEmailExists
}
if err := s.userRepo.Restore(target.ID); err != nil {
return err
}
}
// 修改状态必须立刻让已有 session 失效 + 递增 token_version向后兼容
if err := s.sessionManager.DestroyByUID(target.ID); err != nil {
return err
}
return s.userRepo.IncrementTokenVersion(target.ID)
})
}
// ResetUserToken 强制下线:销毁所有 session + 递增 token_version
func (s *AdminService) ResetUserToken(operatorUID, targetUID uint) error {
return s.checkAndOperate(operatorUID, targetUID, func(_ *model.User, target *model.User) error {
if err := s.sessionManager.DestroyByUID(target.ID); err != nil {
return err
}
return s.userRepo.IncrementTokenVersion(target.ID)
})
}
// CountUsers 统计用户总数(管理首页用)
func (s *AdminService) CountUsers() (int64, error) {
return s.userRepo.CountSearchUsers("", "", "")
}
// checkAndOperate 通用权限检查 + 执行操作
func (s *AdminService) checkAndOperate(operatorUID, targetUID uint, operate func(*model.User, *model.User) error) error {
// 1. 不可操作自己
if operatorUID == targetUID {
return common.ErrPermissionDenied
}
// 2. 查操作者
operator, err := s.userRepo.FindByIDUnscoped(operatorUID)
if err != nil {
return common.ErrUserNotFound
}
// 3. 查目标含软删除用户locked 解锁需要查到)
target, err := s.userRepo.FindByIDUnscoped(targetUID)
if err != nil {
return common.ErrUserNotFound
}
// 4. 权限检查:操作者是否有权限操作目标角色
if !model.CanOperateRole(operator.Role, target.Role) {
return common.ErrPermissionDenied
}
return operate(operator, target)
}