Files
blog-server/internal/service/admin/user.go
2026-08-19 22:05:49 +08:00

362 lines
7.9 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 admin
import (
"context"
"errors"
"server/internal/db"
"server/internal/db/sqlc"
"server/internal/middleware"
"server/internal/model/auth"
"server/internal/model/common"
"server/internal/model/request"
"server/internal/model/response"
"server/internal/pkg/cache"
"server/internal/pkg/cache/cachekey"
"server/internal/pkg/dberr"
"server/internal/pkg/errs"
"golang.org/x/sync/errgroup"
)
type UserService struct {
store *db.Store
jwt *middleware.JWTMiddleware
cache *cache.Caches
}
func NewUserService(store *db.Store, jwt *middleware.JWTMiddleware, cache *cache.Caches) *UserService {
return &UserService{
store: store,
jwt: jwt,
cache: cache,
}
}
// clearUserCache 清理单个用户缓存 权限 info 和 鉴权状态
func (s *UserService) clearUserCache(ctx context.Context, id int32) error {
if err := s.cache.Del(ctx, cachekey.UserApiPermissions(id)); err != nil {
return err
}
if err := s.cache.Del(ctx, cachekey.UserInfo(id)); err != nil {
return err
}
if err := s.cache.Del(ctx, cachekey.UserAuthState(id)); err != nil {
return err
}
return nil
}
// clearRefreshToken
func (s *UserService) clearRefreshToken(ctx context.Context, id int32) error {
keys, err := s.cache.SMembers(ctx, cachekey.AuthRefreshUser(id))
if err != nil {
return err
}
// 循环删除所有的token
for _, key := range keys {
if err = s.cache.Del(ctx, cachekey.AuthRefresh(key)); err != nil {
return err
}
}
// 删除反向索引
err = s.cache.DelSetMembers(ctx, cachekey.AuthRefreshUser(id))
return err
}
// forceLogout 强制下线bump 版本 + 清缓存(含 auth_state+ 清刷新令牌
func (s *UserService) forceLogout(ctx context.Context, id int32) error {
if err := s.store.IncrementUserTokenVersion(ctx, id); err != nil {
return err
}
_ = s.clearUserCache(ctx, id)
_ = s.clearRefreshToken(ctx, id)
return nil
}
func (s *UserService) GetCurrentUser(ctx context.Context, id int32, isAdmin bool) (*response.UserInfo, error) {
return cache.GetOrSetJSON[*response.UserInfo](ctx, s.cache, cachekey.UserInfo(id), 0, func() (*response.UserInfo, error) {
g, ctx := errgroup.WithContext(ctx)
var (
user sqlc.GetUserRow
roles []sqlc.SysRole
menus []sqlc.SysMenu
permissions []*string
)
g.Go(func() error {
u, err := s.store.GetUser(ctx, sqlc.GetUserParams{ID: id})
if err != nil {
return dberr.MapNoRows(err, errs.ErrUserNotFound)
}
user = u
return nil
})
g.Go(func() error {
r, err := s.store.ListUserRoles(ctx, id)
if err != nil {
return err
}
roles = r
return nil
})
g.Go(func() error {
var (
p []*string
err error
)
if isAdmin {
p, err = s.store.ListAdminPermissionCodes(ctx)
} else {
p, err = s.store.ListUserPermissionCodes(ctx, id)
}
if err != nil {
return err
}
permissions = p
return nil
})
g.Go(func() error {
var (
m []sqlc.SysMenu
err error
)
if isAdmin {
m, err = s.store.ListAdminMenus(ctx)
} else {
m, err = s.store.ListUserMenus(ctx, id)
}
if err != nil {
return err
}
menus = m
return nil
})
if err := g.Wait(); err != nil {
return nil, err
}
// 判断用户不为超管 且状态为0表示用户已被禁用
if !auth.IsAdmin(user.ID) && user.Status == 0 {
return nil, errs.ErrUserDisabled
}
// 处理角色
userInfo := response.NewUserInfo(user, roles, menus, permissions)
return userInfo, nil
})
}
func (s *UserService) List(ctx context.Context, p request.SearchUserParams) (*common.PageResult[sqlc.ListUsersRow], error) {
params := sqlc.ListUsersParams{
Limit: p.PageSize,
Offset: (p.Page - 1) * p.PageSize,
Username: p.Username,
}
total, err := s.store.CountUsers(ctx, p.Username)
if err != nil {
return nil, err
}
list, err := s.store.ListUsers(ctx, params)
if err != nil {
return nil, err
}
return &common.PageResult[sqlc.ListUsersRow]{
List: list,
Total: total,
}, nil
}
func (s *UserService) ListRoles(ctx context.Context, id int32) ([]sqlc.SysRole, error) {
// 先查询用户是否存在
_, err := s.store.GetUser(ctx, sqlc.GetUserParams{ID: id})
if err != nil {
return nil, dberr.MapNoRows(err, errs.ErrUserNotFound)
}
return s.store.ListUserRoles(ctx, id)
}
func (s *UserService) Create(ctx context.Context, req request.CreateUserRequest) error {
passwordHash, err := generatePasswordHash(req.Password)
if err != nil {
return err
}
user := sqlc.CreateUserParams{
Account: req.Account,
Username: req.Username,
PasswordHash: passwordHash,
AvatarID: req.AvatarID,
Status: *req.Status,
}
if err = s.store.CreateUser(ctx, user); err != nil {
return dberr.MapUniqueViolation(err, dberr.SysUserAccountKey, errs.ErrAccountAlreadyExists)
}
return nil
}
func (s *UserService) KickUser(ctx context.Context, id int32) error {
return s.forceLogout(ctx, id)
}
func (s *UserService) KickAllUsers(ctx context.Context) error {
if err := s.store.IncrementTokenVersionForAllUsers(ctx); err != nil {
return err
}
// 清理所有的token和状态
_ = s.cache.DelByPrefix(ctx, cachekey.AuthRefreshPattern)
return s.cache.DelByPrefix(ctx, cachekey.UserAuthStatePattern)
}
func (s *UserService) Update(ctx context.Context, id int32, req request.UpdateUserRequest) error {
user := sqlc.UpdateUserParams{
Username: req.Username,
ID: id,
Status: req.Status,
}
if req.AvatarID.Set {
user.UpdateAvatarID = true
if req.AvatarID.Valid {
user.AvatarID = &req.AvatarID.Value
}
}
rows, err := s.store.UpdateUser(ctx, user)
if err = dberr.MapRowsAffected(rows, err, errs.ErrUserNotFound); err != nil {
return err
}
// 清理缓存
_ = s.clearUserCache(ctx, id)
// 如果将用户的状态修改为0则清除用户刷新令牌
if req.Status != nil && *req.Status == 0 {
// 踢下线
_ = s.forceLogout(ctx, id)
}
return nil
}
func (s *UserService) SetRoles(ctx context.Context, userID int32, req request.SetUserRolesRequest) error {
// 先查询用户是否存在
_, err := s.store.GetUser(ctx, sqlc.GetUserParams{ID: userID})
if err != nil {
return dberr.MapNoRows(err, errs.ErrUserNotFound)
}
err = s.store.WithTx(ctx, func(q *sqlc.Queries) error {
// 1. 先删除关联数据
if err = q.ClearUserRoles(ctx, userID); err != nil {
return err
}
var params []sqlc.CreateUserRoleParams
for _, roleID := range req.RoleIDs {
params = append(params, sqlc.CreateUserRoleParams{
UserID: userID,
RoleID: roleID,
})
}
// 2. 关联用户角色数据
_, err = q.CreateUserRole(ctx, params)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
// 3. 清理缓存
_ = s.clearUserCache(ctx, userID)
return nil
}
func (s *UserService) UpdatePassword(ctx context.Context, id int32, req request.UpdateUserPassword) error {
passwordHash, err := generatePasswordHash(req.Password)
if err != nil {
return err
}
params := sqlc.UpdateUserPasswordParams{
ID: id,
PasswordHash: passwordHash,
}
rows, err := s.store.UpdateUserPassword(ctx, params)
err = dberr.MapRowsAffected(rows, err, errs.ErrUserNotFound)
if err != nil {
return err
}
// 下线当前用户
if err = s.forceLogout(ctx, id); err != nil {
return err
}
return nil
}
func (s *UserService) Delete(ctx context.Context, id int32) error {
isAdmin := auth.IsAdmin(id)
if isAdmin {
return errs.ErrCannotDeleteSuperAdmin
}
err := s.store.WithTx(ctx, func(q *sqlc.Queries) error {
// 清理用户角色关联数据
err := q.ClearUserRoles(ctx, id)
if err != nil {
return err
}
// 删除用户
rows, err := q.DeleteUser(ctx, id)
return dberr.MapRowsAffected(rows, err, errs.ErrUserNotFound)
})
if err != nil {
if errors.Is(err, errs.ErrUserNotFound) {
// 用户不存在也执行清理缓存操作
_ = s.clearUserCache(ctx, id)
}
return err
}
// 事务执行成功后清理缓存
_ = s.clearUserCache(ctx, id)
_ = s.clearRefreshToken(ctx, id)
return nil
}