Przeglądaj źródła

管理员后端登录管理调整

liuchengsen 1 miesiąc temu
rodzic
commit
378cc5a21d

+ 5 - 24
zhijiayun-user/src/main/java/com/xuekairui/user/controller/AdminUserController.java

@@ -3,19 +3,17 @@ package com.xuekairui.user.controller;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.xuekairui.common.Result;
 import com.xuekairui.common.Result;
 import com.xuekairui.user.dto.AdminUserResponse;
 import com.xuekairui.user.dto.AdminUserResponse;
-import com.xuekairui.user.dto.UpdateRoleRequest;
 import com.xuekairui.user.enums.OperationType;
 import com.xuekairui.user.enums.OperationType;
 import com.xuekairui.user.service.AdminUserService;
 import com.xuekairui.user.service.AdminUserService;
 import com.xuekairui.user.service.OperationAuditLogService;
 import com.xuekairui.user.service.OperationAuditLogService;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequest;
-import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 /**
 /**
  * 运营用户管理接口(超级管理员)
  * 运营用户管理接口(超级管理员)
  * <p>
  * <p>
- * 用户角色管理:查询用户列表、修改用户角色
+ * 用户管理:查询用户列表、踢出用户
  * 仅 SUPER_ADMIN 可访问,由 SecurityConfig 控制。
  * 仅 SUPER_ADMIN 可访问,由 SecurityConfig 控制。
  */
  */
 @RestController
 @RestController
@@ -27,8 +25,8 @@ public class AdminUserController {
     private final OperationAuditLogService auditLogService;
     private final OperationAuditLogService auditLogService;
 
 
     /**
     /**
-     * 分页查询用户列表(不支持删除操作)
-     * GET /api/admin/users?page=1&size=20&userId=1&phone=138&nickname=测试&role=ADMIN
+     * 分页查询用户列表
+     * GET /api/admin/users?page=1&size=20&userId=1&phone=138&nickname=测试
      */
      */
     @GetMapping
     @GetMapping
     public Result<Page<AdminUserResponse>> listUsers(
     public Result<Page<AdminUserResponse>> listUsers(
@@ -41,7 +39,7 @@ public class AdminUserController {
     }
     }
 
 
     /**
     /**
-     * 强制踢出用户(使所有Token失效,强制重新登录)
+     * 强制踢出用户(使所有 Token 失效,强制重新登录)
      * POST /api/admin/users/{userId}/kick
      * POST /api/admin/users/{userId}/kick
      */
      */
     @PostMapping("/{userId}/kick")
     @PostMapping("/{userId}/kick")
@@ -54,21 +52,4 @@ public class AdminUserController {
                 "USER_KICK:" + userId, userId);
                 "USER_KICK:" + userId, userId);
         return Result.success("用户已被强制退出,下次请求将返回1304");
         return Result.success("用户已被强制退出,下次请求将返回1304");
     }
     }
-
-    /**
-     * 修改用户角色(仅超级管理员)
-     * PUT /api/admin/users/{userId}/role
-     */
-    @PutMapping("/{userId}/role")
-    public Result<AdminUserResponse> updateUserRole(
-            @PathVariable Long userId,
-            @Valid @RequestBody UpdateRoleRequest request,
-            HttpServletRequest httpRequest) {
-        Long operatorId = (Long) httpRequest.getAttribute("userId");
-        AdminUserResponse response = adminUserService.updateUserRole(userId, request, operatorId);
-        auditLogService.logAdmin(operatorId, OperationType.USER_UPDATE,
-                "USER_ROLE:" + userId + "->" + request.getRole(), userId);
-        return Result.success(response);
-    }
-
-}
+ }

+ 1 - 1
zhijiayun-user/src/main/java/com/xuekairui/user/dto/AdminUserResponse.java

@@ -26,7 +26,7 @@ public class AdminUserResponse {
     private String phone;
     private String phone;
 
 
     /** 昵称 */
     /** 昵称 */
-    private String nickname;
+    private String userName;
 
 
     /** 角色:USER/ADMIN/SUPER_ADMIN */
     /** 角色:USER/ADMIN/SUPER_ADMIN */
     private String role;
     private String role;

+ 0 - 17
zhijiayun-user/src/main/java/com/xuekairui/user/dto/UpdateRoleRequest.java

@@ -1,17 +0,0 @@
-package com.xuekairui.user.dto;
-
-import jakarta.validation.constraints.NotBlank;
-import lombok.Data;
-
-/**
- * 更新用户角色请求
- */
-@Data
-public class UpdateRoleRequest {
-
-    /**
-     * 目标角色:USER/ADMIN/SUPER_ADMIN
-     */
-    @NotBlank(message = "角色不能为空")
-    private String role;
-}

+ 28 - 17
zhijiayun-user/src/main/java/com/xuekairui/user/security/JwtAuthenticationFilter.java

@@ -75,7 +75,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
         boolean isHeartbeat = "/api/auth/heartbeat".equals(path);
         boolean isHeartbeat = "/api/auth/heartbeat".equals(path);
 
 
         // 尽早从 token 中提取 userId 注入 MDC(宽容模式,过期 token 也能提取)
         // 尽早从 token 中提取 userId 注入 MDC(宽容模式,过期 token 也能提取)
-        extractUidForMdc(token);
+        Claims claims1 = extractUidForMdcAndSetRequestAttr(token, request);
 
 
         try {
         try {
             if (token != null && !token.isEmpty() && SecurityContextHolder.getContext().getAuthentication() == null) {
             if (token != null && !token.isEmpty() && SecurityContextHolder.getContext().getAuthentication() == null) {
@@ -84,7 +84,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
                     Claims claims;
                     Claims claims;
                     if (isLenientPath) {
                     if (isLenientPath) {
                         // 宽容模式:一次解析 JWT,避免重复调用 parseTokenLenient
                         // 宽容模式:一次解析 JWT,避免重复调用 parseTokenLenient
-                        claims = jwtUtil.parseTokenLenient(token);
+                        claims = claims1;
                     } else {
                     } else {
                         // 严格模式:一次解析 JWT,避免重复调用 parseToken
                         // 严格模式:一次解析 JWT,避免重复调用 parseToken
                          claims = jwtUtil.parseToken(token);
                          claims = jwtUtil.parseToken(token);
@@ -96,14 +96,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
                     String loginSource = claims.get("loginSource", String.class);
                     String loginSource = claims.get("loginSource", String.class);
                     boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
                     boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
 
 
-                    if (userId != null) {
-                        MDC.put("uid", String.valueOf(userId));
-                    }
-
+                    // userId 和 loginSource 已经在 extractUidForMdcAndSetRequestAttr 中设置过了
                     if ("access".equals(tokenType)) {
                     if ("access".equals(tokenType)) {
-                        request.setAttribute("userId", userId);
-                        request.setAttribute("loginSource", loginSource);
-
                         if (!isTokenVersionValid(userId, loginSource, tokenVer, role)) {
                         if (!isTokenVersionValid(userId, loginSource, tokenVer, role)) {
                             log.info("{} {} 的Token版本号不匹配(已在其他设备登录),拒绝访问",
                             log.info("{} {} 的Token版本号不匹配(已在其他设备登录),拒绝访问",
                                     isAdmin ? "管理员" : "用户", userId);
                                     isAdmin ? "管理员" : "用户", userId);
@@ -162,7 +156,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
      */
      */
     private boolean isTokenVersionValid(Long id, String loginSource,
     private boolean isTokenVersionValid(Long id, String loginSource,
                                         Long tokenVersion, String role) {
                                         Long tokenVersion, String role) {
-        if (id == null) return false;
+        if (id == null) {
+            return false;
+        }
         long tokenVer = tokenVersion != null ? tokenVersion : 0L;
         long tokenVer = tokenVersion != null ? tokenVersion : 0L;
         boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
         boolean isAdmin = "ADMIN".equals(role) || "SUPER_ADMIN".equals(role);
 
 
@@ -170,17 +166,23 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
         if (currentVersion == null) {
         if (currentVersion == null) {
             if (isAdmin) {
             if (isAdmin) {
                 com.xuekairui.user.entity.Admin admin = adminMapper.selectById(id);
                 com.xuekairui.user.entity.Admin admin = adminMapper.selectById(id);
-                if (admin == null) return false;
+                if (admin == null) {
+                    return false;
+                }
                 currentVersion = admin.getTokenVersion() != null ? admin.getTokenVersion() : 0L;
                 currentVersion = admin.getTokenVersion() != null ? admin.getTokenVersion() : 0L;
             } else {
             } else {
                 User user = userMapper.selectById(id);
                 User user = userMapper.selectById(id);
-                if (user == null) return false;
+                if (user == null) {
+                    return false;
+                }
                 currentVersion = user.getTokenVersion() != null ? user.getTokenVersion() : 0L;
                 currentVersion = user.getTokenVersion() != null ? user.getTokenVersion() : 0L;
             }
             }
             tokenVersionCache.putVersion(id, isAdmin, currentVersion);
             tokenVersionCache.putVersion(id, isAdmin, currentVersion);
         }
         }
 
 
-        if (isAdmin) return tokenVer <= currentVersion && (currentVersion - tokenVer) < 5;
+        if (isAdmin) {
+            return tokenVer <= currentVersion && (currentVersion - tokenVer) < 5;
+        }
 
 
         // 小程序端:范围匹配,最多 2 台,不踢 PC
         // 小程序端:范围匹配,最多 2 台,不踢 PC
         if ("MINIAPP".equals(loginSource)) {
         if ("MINIAPP".equals(loginSource)) {
@@ -234,18 +236,27 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
         }
         }
     }
     }
 
 
-    /** 从 token 中提取 userId 注入 MDC,便于整个请求链路的日志带上 uid */
-    private void extractUidForMdc(String token) {
+    /** 从 token 中提取 userId 注入 MDC,并设置到 request attribute(宽容模式,过期 token 也能提取) */
+    private Claims extractUidForMdcAndSetRequestAttr(String token, HttpServletRequest request) {
+        Claims claims = null;
         if (token == null || token.isEmpty()) {
         if (token == null || token.isEmpty()) {
-            return;
+            return claims;
         }
         }
         try {
         try {
-            Long uid = jwtUtil.parseTokenLenient(token).get("userId", Long.class);
+            claims = jwtUtil.parseTokenLenient(token);
+            Long uid = claims.get("userId", Long.class);
+            String loginSource = claims.get("loginSource", String.class);
             if (uid != null) {
             if (uid != null) {
                 MDC.put("uid", String.valueOf(uid));
                 MDC.put("uid", String.valueOf(uid));
+                // 同时设置到 request attribute,确保 RequestLoggingFilter 能拿到
+                request.setAttribute("userId", uid);
+                if (loginSource != null) {
+                    request.setAttribute("loginSource", loginSource);
+                }
             }
             }
         } catch (Exception ignored) {
         } catch (Exception ignored) {
             // token 完全无法解析,忽略
             // token 完全无法解析,忽略
         }
         }
+        return claims;
     }
     }
 }
 }

+ 2 - 73
zhijiayun-user/src/main/java/com/xuekairui/user/service/AdminUserService.java

@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.xuekairui.common.BusinessException;
 import com.xuekairui.common.BusinessException;
 import com.xuekairui.common.ErrorCode;
 import com.xuekairui.common.ErrorCode;
 import com.xuekairui.user.dto.AdminUserResponse;
 import com.xuekairui.user.dto.AdminUserResponse;
-import com.xuekairui.user.dto.UpdateRoleRequest;
 import com.xuekairui.user.entity.Admin;
 import com.xuekairui.user.entity.Admin;
 import com.xuekairui.user.entity.User;
 import com.xuekairui.user.entity.User;
 import com.xuekairui.user.mapper.AdminMapper;
 import com.xuekairui.user.mapper.AdminMapper;
@@ -17,7 +16,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Optional;
 
 
 /**
 /**
@@ -49,7 +47,6 @@ public class AdminUserService {
         if (nickname != null && !nickname.isBlank()) {
         if (nickname != null && !nickname.isBlank()) {
             wrapper.like(User::getNickname, nickname);
             wrapper.like(User::getNickname, nickname);
         }
         }
-        // 前端用户列表仅查 USER,管理员走 listAdmins
         wrapper.orderByDesc(User::getCreateTime);
         wrapper.orderByDesc(User::getCreateTime);
 
 
         Page<User> userPage = userMapper.selectPage(page, wrapper);
         Page<User> userPage = userMapper.selectPage(page, wrapper);
@@ -129,74 +126,6 @@ public class AdminUserService {
         tokenVersionCache.clear();
         tokenVersionCache.clear();
     }
     }
 
 
-    /**
-     * 修改用户角色(仅超级管理员可调用)
-     * 升级为 ADMIN/SUPER_ADMIN → 插入 t_admin;降级为 USER → 删除 t_admin 记录
-     */
-    @Transactional
-    public AdminUserResponse updateUserRole(Long userId, UpdateRoleRequest request, Long operatorId) {
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            throw new BusinessException(ErrorCode.USER_NOT_FOUND);
-        }
-
-        String targetRole = request.getRole();
-        Admin existingAdmin = adminMapper.selectById(userId);
-
-        // 禁止取消最后一个超级管理员
-        if (existingAdmin != null && "SUPER_ADMIN".equals(existingAdmin.getRole())
-                && !"SUPER_ADMIN".equals(targetRole)) {
-            long otherCount = adminMapper.selectCount(
-                    new LambdaQueryWrapper<Admin>()
-                            .eq(Admin::getRole, "SUPER_ADMIN")
-                            .ne(Admin::getId, userId));
-            if (otherCount == 0) {
-                throw new BusinessException(ErrorCode.BUSINESS_ERROR, "系统中至少保留一个超级管理员");
-            }
-        }
-
-        if (Objects.equals(operatorId, userId)
-                && existingAdmin != null && "SUPER_ADMIN".equals(existingAdmin.getRole())
-                && !"SUPER_ADMIN".equals(targetRole)) {
-            throw new BusinessException(ErrorCode.BUSINESS_ERROR, "不能取消自己的超级管理员权限");
-        }
-
-        if ("ADMIN".equals(targetRole) || "SUPER_ADMIN".equals(targetRole)) {
-            // 升级:插入或更新 t_admin
-            if (existingAdmin != null) {
-                existingAdmin.setRole(targetRole);
-                adminMapper.updateById(existingAdmin);
-            } else {
-                // 如果没有密码,用手机号后6位作为初始密码(BCrypt加密)
-                String pwd = user.getPassword();
-                if (pwd == null || pwd.isEmpty()) {
-                    String phone = user.getPhone();
-                    String rawPwd = phone != null && phone.length() >= 6
-                            ? phone.substring(phone.length() - 6) : "123456";
-                    pwd = passwordUtil.encode(rawPwd);
-                }
-                Admin admin = Admin.builder()
-                        .id(userId)
-                        .username(user.getPhone() != null ? user.getPhone() : "admin_" + userId)
-                        .password(pwd)
-                        .phone(user.getPhone())
-                        .role(targetRole)
-                        .status(1)
-                        .build();
-                adminMapper.insert(admin);
-            }
-        } else {
-            // 降级为 USER:删除 t_admin 记录
-            if (existingAdmin != null) {
-                adminMapper.deleteById(userId);
-                tokenVersionCache.clear();
-            }
-        }
-
-        log.info("操作人{}修改用户{}角色为{}", operatorId, userId, targetRole);
-        return toResponse(user);
-    }
-
     /**
     /**
      * 强制踢出用户(递增 tokenVersion,所有 Token 立即失效)
      * 强制踢出用户(递增 tokenVersion,所有 Token 立即失效)
      */
      */
@@ -233,7 +162,7 @@ public class AdminUserService {
 
 
     private AdminUserResponse buildAdminResponse(Admin a) {
     private AdminUserResponse buildAdminResponse(Admin a) {
         return AdminUserResponse.builder()
         return AdminUserResponse.builder()
-                .id(a.getId()).phone(a.getPhone()).nickname(a.getUsername())
+                .id(a.getId()).phone(a.getPhone()).userName(a.getUsername())
                 .role(a.getRole()).status(a.getStatus()).createTime(a.getCreateTime())
                 .role(a.getRole()).status(a.getStatus()).createTime(a.getCreateTime())
                 .build();
                 .build();
     }
     }
@@ -242,7 +171,7 @@ public class AdminUserService {
         return AdminUserResponse.builder()
         return AdminUserResponse.builder()
                 .id(user.getId())
                 .id(user.getId())
                 .phone(user.getPhone())
                 .phone(user.getPhone())
-                .nickname(user.getNickname())
+                .userName(user.getNickname())
                 .role("USER")
                 .role("USER")
                 .status(user.getStatus())
                 .status(user.getStatus())
                 .createTime(user.getCreateTime())
                 .createTime(user.getCreateTime())