mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	refactor: 重构获取登录用户信息方式(线程级存储)
This commit is contained in:
		| @@ -17,8 +17,7 @@ | |||||||
| package top.continew.admin.common.config.mybatis; | package top.continew.admin.common.config.mybatis; | ||||||
|  |  | ||||||
| import cn.hutool.core.convert.Convert; | import cn.hutool.core.convert.Convert; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.starter.extension.datapermission.enums.DataScope; | import top.continew.starter.extension.datapermission.enums.DataScope; | ||||||
| import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider; | import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider; | ||||||
| import top.continew.starter.extension.datapermission.model.RoleContext; | import top.continew.starter.extension.datapermission.model.RoleContext; | ||||||
| @@ -36,17 +35,16 @@ public class DefaultDataPermissionUserContextProvider implements DataPermissionU | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public boolean isFilter() { |     public boolean isFilter() { | ||||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); |         return !UserContextHolder.isAdmin(); | ||||||
|         return !loginUser.isAdmin(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public UserContext getUserContext() { |     public UserContext getUserContext() { | ||||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); |         top.continew.admin.common.context.UserContext context = UserContextHolder.getContext(); | ||||||
|         UserContext userContext = new UserContext(); |         UserContext userContext = new UserContext(); | ||||||
|         userContext.setUserId(Convert.toStr(loginUser.getId())); |         userContext.setUserId(Convert.toStr(context.getId())); | ||||||
|         userContext.setDeptId(Convert.toStr(loginUser.getDeptId())); |         userContext.setDeptId(Convert.toStr(context.getDeptId())); | ||||||
|         userContext.setRoles(loginUser.getRoles() |         userContext.setRoles(context.getRoles() | ||||||
|             .stream() |             .stream() | ||||||
|             .map(r -> new RoleContext(Convert.toStr(r.getId()), DataScope.valueOf(r.getDataScope().name()))) |             .map(r -> new RoleContext(Convert.toStr(r.getId()), DataScope.valueOf(r.getDataScope().name()))) | ||||||
|             .collect(Collectors.toSet())); |             .collect(Collectors.toSet())); | ||||||
|   | |||||||
| @@ -19,9 +19,8 @@ package top.continew.admin.common.config.mybatis; | |||||||
| import cn.hutool.core.util.ObjectUtil; | import cn.hutool.core.util.ObjectUtil; | ||||||
| import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | ||||||
| import org.apache.ibatis.reflection.MetaObject; | import org.apache.ibatis.reflection.MetaObject; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.starter.core.exception.BusinessException; | import top.continew.starter.core.exception.BusinessException; | ||||||
| import top.continew.starter.core.util.ExceptionUtils; |  | ||||||
| import top.continew.starter.extension.crud.model.entity.BaseDO; | import top.continew.starter.extension.crud.model.entity.BaseDO; | ||||||
|  |  | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
| @@ -62,7 +61,7 @@ public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler { | |||||||
|             if (null == metaObject) { |             if (null == metaObject) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             Long createUser = ExceptionUtils.exToNull(LoginHelper::getUserId); |             Long createUser = UserContextHolder.getUserId(); | ||||||
|             LocalDateTime createTime = LocalDateTime.now(); |             LocalDateTime createTime = LocalDateTime.now(); | ||||||
|             if (metaObject.getOriginalObject() instanceof BaseDO baseDO) { |             if (metaObject.getOriginalObject() instanceof BaseDO baseDO) { | ||||||
|                 // 继承了 BaseDO 的类,填充创建信息字段 |                 // 继承了 BaseDO 的类,填充创建信息字段 | ||||||
| @@ -89,8 +88,7 @@ public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler { | |||||||
|             if (null == metaObject) { |             if (null == metaObject) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |             Long updateUser = UserContextHolder.getUserId(); | ||||||
|             Long updateUser = LoginHelper.getUserId(); |  | ||||||
|             LocalDateTime updateTime = LocalDateTime.now(); |             LocalDateTime updateTime = LocalDateTime.now(); | ||||||
|             if (metaObject.getOriginalObject() instanceof BaseDO baseDO) { |             if (metaObject.getOriginalObject() instanceof BaseDO baseDO) { | ||||||
|                 // 继承了 BaseDO 的类,填充修改信息 |                 // 继承了 BaseDO 的类,填充修改信息 | ||||||
|   | |||||||
| @@ -16,11 +16,11 @@ | |||||||
|  |  | ||||||
| package top.continew.admin.common.config.websocket; | package top.continew.admin.common.config.websocket; | ||||||
|  |  | ||||||
|  | import cn.dev33.satoken.stp.StpUtil; | ||||||
| import jakarta.servlet.http.HttpServletRequest; | import jakarta.servlet.http.HttpServletRequest; | ||||||
| import org.springframework.http.server.ServletServerHttpRequest; | import org.springframework.http.server.ServletServerHttpRequest; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; | import top.continew.starter.core.exception.BusinessException; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.starter.messaging.websocket.core.WebSocketClientService; | import top.continew.starter.messaging.websocket.core.WebSocketClientService; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -36,7 +36,9 @@ public class WebSocketClientServiceImpl implements WebSocketClientService { | |||||||
|     public String getClientId(ServletServerHttpRequest request) { |     public String getClientId(ServletServerHttpRequest request) { | ||||||
|         HttpServletRequest servletRequest = request.getServletRequest(); |         HttpServletRequest servletRequest = request.getServletRequest(); | ||||||
|         String token = servletRequest.getParameter("token"); |         String token = servletRequest.getParameter("token"); | ||||||
|         LoginUser loginUser = LoginHelper.getLoginUser(token); |         if (null == StpUtil.getLoginIdByToken(token)) { | ||||||
|         return loginUser.getToken(); |             throw new BusinessException("登录已过期,请重新登录"); | ||||||
|  |         } | ||||||
|  |         return token; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -31,11 +31,6 @@ public class CacheConstants { | |||||||
|      */ |      */ | ||||||
|     public static final String DELIMITER = StringConstants.COLON; |     public static final String DELIMITER = StringConstants.COLON; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 登录用户键 |  | ||||||
|      */ |  | ||||||
|     public static final String LOGIN_USER_KEY = "LOGIN_USER"; |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 验证码键前缀 |      * 验证码键前缀 | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package top.continew.admin.common.model.dto; | package top.continew.admin.common.context; | ||||||
| 
 | 
 | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| import top.continew.admin.common.enums.DataScopeEnum; | import top.continew.admin.common.enums.DataScopeEnum; | ||||||
| @@ -23,13 +23,13 @@ import java.io.Serial; | |||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * 角色信息 |  * 角色上下文 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2023/3/7 22:08 |  * @since 2023/3/7 22:08 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| public class RoleDTO implements Serializable { | public class RoleContext implements Serializable { | ||||||
| 
 | 
 | ||||||
|     @Serial |     @Serial | ||||||
|     private static final long serialVersionUID = 1L; |     private static final long serialVersionUID = 1L; | ||||||
| @@ -14,7 +14,7 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package top.continew.admin.common.model.dto; | package top.continew.admin.common.context; | ||||||
| 
 | 
 | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| @@ -28,14 +28,14 @@ import java.util.Set; | |||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * 登录用户信息 |  * 用户上下文 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/24 13:01 |  * @since 2024/10/9 20:29 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| @NoArgsConstructor | @NoArgsConstructor | ||||||
| public class LoginUser implements Serializable { | public class UserContext implements Serializable { | ||||||
| 
 | 
 | ||||||
|     @Serial |     @Serial | ||||||
|     private static final long serialVersionUID = 1L; |     private static final long serialVersionUID = 1L; | ||||||
| @@ -55,6 +55,16 @@ public class LoginUser implements Serializable { | |||||||
|      */ |      */ | ||||||
|     private Long deptId; |     private Long deptId; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 最后一次修改密码时间 | ||||||
|  |      */ | ||||||
|  |     private LocalDateTime pwdResetTime; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 登录时系统设置的密码过期天数 | ||||||
|  |      */ | ||||||
|  |     private Integer passwordExpirationDays; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * 权限码集合 |      * 权限码集合 | ||||||
|      */ |      */ | ||||||
| @@ -68,57 +78,17 @@ public class LoginUser implements Serializable { | |||||||
|     /** |     /** | ||||||
|      * 角色集合 |      * 角色集合 | ||||||
|      */ |      */ | ||||||
|     private Set<RoleDTO> roles; |     private Set<RoleContext> roles; | ||||||
| 
 | 
 | ||||||
|     /** |     public UserContext(Set<String> permissions, Set<RoleContext> roles, Integer passwordExpirationDays) { | ||||||
|      * 令牌 |  | ||||||
|      */ |  | ||||||
|     private String token; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * IP |  | ||||||
|      */ |  | ||||||
|     private String ip; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * IP 归属地 |  | ||||||
|      */ |  | ||||||
|     private String address; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 浏览器 |  | ||||||
|      */ |  | ||||||
|     private String browser; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 操作系统 |  | ||||||
|      */ |  | ||||||
|     private String os; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 登录时间 |  | ||||||
|      */ |  | ||||||
|     private LocalDateTime loginTime; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 最后一次修改密码时间 |  | ||||||
|      */ |  | ||||||
|     private LocalDateTime pwdResetTime; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 登录时系统设置的密码过期天数 |  | ||||||
|      */ |  | ||||||
|     private Integer passwordExpirationDays; |  | ||||||
| 
 |  | ||||||
|     public LoginUser(Set<String> permissions, Set<RoleDTO> roles, Integer passwordExpirationDays) { |  | ||||||
|         this.permissions = permissions; |         this.permissions = permissions; | ||||||
|         this.setRoles(roles); |         this.setRoles(roles); | ||||||
|         this.passwordExpirationDays = passwordExpirationDays; |         this.passwordExpirationDays = passwordExpirationDays; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setRoles(Set<RoleDTO> roles) { |     public void setRoles(Set<RoleContext> roles) { | ||||||
|         this.roles = roles; |         this.roles = roles; | ||||||
|         this.roleCodes = roles.stream().map(RoleDTO::getCode).collect(Collectors.toSet()); |         this.roleCodes = roles.stream().map(RoleContext::getCode).collect(Collectors.toSet()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @@ -0,0 +1,185 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.continew.admin.common.context; | ||||||
|  |  | ||||||
|  | import cn.dev33.satoken.session.SaSession; | ||||||
|  | import cn.dev33.satoken.stp.StpUtil; | ||||||
|  | import cn.hutool.core.convert.Convert; | ||||||
|  | import cn.hutool.extra.spring.SpringUtil; | ||||||
|  | import top.continew.starter.core.util.ExceptionUtils; | ||||||
|  | import top.continew.starter.extension.crud.service.CommonUserService; | ||||||
|  |  | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 用户上下文 Holder | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2022/12/24 12:58 | ||||||
|  |  */ | ||||||
|  | public class UserContextHolder { | ||||||
|  |  | ||||||
|  |     private static final ThreadLocal<UserContext> CONTEXT_HOLDER = new ThreadLocal<>(); | ||||||
|  |     private static final ThreadLocal<UserExtraContext> EXTRA_CONTEXT_HOLDER = new ThreadLocal<>(); | ||||||
|  |  | ||||||
|  |     private UserContextHolder() { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置上下文 | ||||||
|  |      * | ||||||
|  |      * @param context 上下文 | ||||||
|  |      */ | ||||||
|  |     public static void setContext(UserContext context) { | ||||||
|  |         setContext(context, true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置上下文 | ||||||
|  |      * | ||||||
|  |      * @param context  上下文 | ||||||
|  |      * @param isUpdate 是否更新 | ||||||
|  |      */ | ||||||
|  |     public static void setContext(UserContext context, boolean isUpdate) { | ||||||
|  |         CONTEXT_HOLDER.set(context); | ||||||
|  |         if (isUpdate) { | ||||||
|  |             StpUtil.getSessionByLoginId(context.getId()).set(SaSession.USER, context); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取上下文 | ||||||
|  |      * | ||||||
|  |      * @return 上下文 | ||||||
|  |      */ | ||||||
|  |     public static UserContext getContext() { | ||||||
|  |         UserContext context = CONTEXT_HOLDER.get(); | ||||||
|  |         if (null == context) { | ||||||
|  |             context = StpUtil.getSession().getModel(SaSession.USER, UserContext.class); | ||||||
|  |             CONTEXT_HOLDER.set(context); | ||||||
|  |         } | ||||||
|  |         return context; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取指定用户的上下文 | ||||||
|  |      * | ||||||
|  |      * @param userId 用户 ID | ||||||
|  |      * @return 上下文 | ||||||
|  |      */ | ||||||
|  |     public static UserContext getContext(Long userId) { | ||||||
|  |         SaSession session = StpUtil.getSessionByLoginId(userId, false); | ||||||
|  |         if (null == session) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return session.getModel(SaSession.USER, UserContext.class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 设置额外上下文 | ||||||
|  |      * | ||||||
|  |      * @param context 额外上下文 | ||||||
|  |      */ | ||||||
|  |     public static void setExtraContext(UserExtraContext context) { | ||||||
|  |         EXTRA_CONTEXT_HOLDER.set(context); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取额外上下文 | ||||||
|  |      * | ||||||
|  |      * @return 额外上下文 | ||||||
|  |      */ | ||||||
|  |     public static UserExtraContext getExtraContext() { | ||||||
|  |         UserExtraContext context = EXTRA_CONTEXT_HOLDER.get(); | ||||||
|  |         if (null == context) { | ||||||
|  |             context = getExtraContext(StpUtil.getTokenValue()); | ||||||
|  |             EXTRA_CONTEXT_HOLDER.set(context); | ||||||
|  |         } | ||||||
|  |         return context; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取额外上下文 | ||||||
|  |      * | ||||||
|  |      * @param token 令牌 | ||||||
|  |      * @return 额外上下文 | ||||||
|  |      */ | ||||||
|  |     public static UserExtraContext getExtraContext(String token) { | ||||||
|  |         UserExtraContext context = new UserExtraContext(); | ||||||
|  |         context.setIp(Convert.toStr(StpUtil.getExtra(token, "ip"))); | ||||||
|  |         context.setAddress(Convert.toStr(StpUtil.getExtra(token, "address"))); | ||||||
|  |         context.setBrowser(Convert.toStr(StpUtil.getExtra(token, "browser"))); | ||||||
|  |         context.setOs(Convert.toStr(StpUtil.getExtra(token, "os"))); | ||||||
|  |         context.setLoginTime(Convert.toLocalDateTime(StpUtil.getExtra(token, "loginTime"))); | ||||||
|  |         return context; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 清除上下文 | ||||||
|  |      */ | ||||||
|  |     public static void clearContext() { | ||||||
|  |         CONTEXT_HOLDER.remove(); | ||||||
|  |         EXTRA_CONTEXT_HOLDER.remove(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取用户 ID | ||||||
|  |      * | ||||||
|  |      * @return 用户 ID | ||||||
|  |      */ | ||||||
|  |     public static Long getUserId() { | ||||||
|  |         return Optional.ofNullable(getContext()).map(UserContext::getId).orElse(null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取用户名 | ||||||
|  |      * | ||||||
|  |      * @return 用户名 | ||||||
|  |      */ | ||||||
|  |     public static String getUsername() { | ||||||
|  |         return Optional.ofNullable(getContext()).map(UserContext::getUsername).orElse(null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取用户昵称 | ||||||
|  |      * | ||||||
|  |      * @return 用户昵称 | ||||||
|  |      */ | ||||||
|  |     public static String getNickname() { | ||||||
|  |         return getNickname(getUserId()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取用户昵称 | ||||||
|  |      * | ||||||
|  |      * @param userId 登录用户 ID | ||||||
|  |      * @return 用户昵称 | ||||||
|  |      */ | ||||||
|  |     public static String getNickname(Long userId) { | ||||||
|  |         return ExceptionUtils.exToNull(() -> SpringUtil.getBean(CommonUserService.class).getNicknameById(userId)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 是否为管理员 | ||||||
|  |      * | ||||||
|  |      * @return 是否为管理员 | ||||||
|  |      */ | ||||||
|  |     public static Boolean isAdmin() { | ||||||
|  |         StpUtil.checkLogin(); | ||||||
|  |         return getContext().isAdmin(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,77 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.continew.admin.common.context; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.hutool.extra.servlet.JakartaServletUtil; | ||||||
|  | import jakarta.servlet.http.HttpServletRequest; | ||||||
|  | import lombok.Data; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  | import top.continew.starter.core.util.ExceptionUtils; | ||||||
|  | import top.continew.starter.core.util.IpUtils; | ||||||
|  | import top.continew.starter.web.util.ServletUtils; | ||||||
|  |  | ||||||
|  | import java.io.Serial; | ||||||
|  | import java.io.Serializable; | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 用户额外上下文 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2024/10/9 20:29 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @NoArgsConstructor | ||||||
|  | public class UserExtraContext implements Serializable { | ||||||
|  |  | ||||||
|  |     @Serial | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * IP | ||||||
|  |      */ | ||||||
|  |     private String ip; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * IP 归属地 | ||||||
|  |      */ | ||||||
|  |     private String address; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 浏览器 | ||||||
|  |      */ | ||||||
|  |     private String browser; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作系统 | ||||||
|  |      */ | ||||||
|  |     private String os; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 登录时间 | ||||||
|  |      */ | ||||||
|  |     private LocalDateTime loginTime; | ||||||
|  |  | ||||||
|  |     public UserExtraContext(HttpServletRequest request) { | ||||||
|  |         this.ip = JakartaServletUtil.getClientIP(request); | ||||||
|  |         this.address = ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip)); | ||||||
|  |         this.setBrowser(ServletUtils.getBrowser(request)); | ||||||
|  |         this.setLoginTime(LocalDateTime.now()); | ||||||
|  |         this.setOs(StrUtil.subBefore(ServletUtils.getOs(request), " or", false)); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,152 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package top.continew.admin.common.util.helper; |  | ||||||
|  |  | ||||||
| import cn.dev33.satoken.context.SaHolder; |  | ||||||
| import cn.dev33.satoken.exception.NotLoginException; |  | ||||||
| import cn.dev33.satoken.session.SaSession; |  | ||||||
| import cn.dev33.satoken.stp.StpUtil; |  | ||||||
| import cn.hutool.core.util.StrUtil; |  | ||||||
| import cn.hutool.extra.servlet.JakartaServletUtil; |  | ||||||
| import cn.hutool.extra.spring.SpringUtil; |  | ||||||
| import jakarta.servlet.http.HttpServletRequest; |  | ||||||
| import top.continew.admin.common.constant.CacheConstants; |  | ||||||
| import top.continew.admin.common.model.dto.LoginUser; |  | ||||||
| import top.continew.starter.core.util.ExceptionUtils; |  | ||||||
| import top.continew.starter.core.util.IpUtils; |  | ||||||
| import top.continew.starter.extension.crud.service.CommonUserService; |  | ||||||
| import top.continew.starter.web.util.ServletUtils; |  | ||||||
| import top.continew.starter.web.util.SpringWebUtils; |  | ||||||
|  |  | ||||||
| import java.time.LocalDateTime; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 登录助手 |  | ||||||
|  * |  | ||||||
|  * @author Charles7c |  | ||||||
|  * @author Lion Li(<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>) |  | ||||||
|  * @since 2022/12/24 12:58 |  | ||||||
|  */ |  | ||||||
| public class LoginHelper { |  | ||||||
|  |  | ||||||
|     private LoginHelper() { |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 用户登录并缓存用户信息 |  | ||||||
|      * |  | ||||||
|      * @param loginUser 登录用户信息 |  | ||||||
|      * @return 令牌 |  | ||||||
|      */ |  | ||||||
|     public static String login(LoginUser loginUser) { |  | ||||||
|         // 记录登录信息 |  | ||||||
|         HttpServletRequest request = SpringWebUtils.getRequest(); |  | ||||||
|         loginUser.setIp(JakartaServletUtil.getClientIP(request)); |  | ||||||
|         loginUser.setAddress(ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(loginUser.getIp()))); |  | ||||||
|         loginUser.setBrowser(ServletUtils.getBrowser(request)); |  | ||||||
|         loginUser.setLoginTime(LocalDateTime.now()); |  | ||||||
|         loginUser.setOs(StrUtil.subBefore(ServletUtils.getOs(request), " or", false)); |  | ||||||
|         // 登录并缓存用户信息 |  | ||||||
|         StpUtil.login(loginUser.getId()); |  | ||||||
|         SaHolder.getStorage().set(CacheConstants.LOGIN_USER_KEY, loginUser); |  | ||||||
|         String tokenValue = StpUtil.getTokenValue(); |  | ||||||
|         loginUser.setToken(tokenValue); |  | ||||||
|         StpUtil.getTokenSession().set(CacheConstants.LOGIN_USER_KEY, loginUser); |  | ||||||
|         return tokenValue; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 更新登录用户信息 |  | ||||||
|      * |  | ||||||
|      * @param loginUser |  | ||||||
|      *                  登录用户信息 |  | ||||||
|      * @param token     令牌 |  | ||||||
|      */ |  | ||||||
|     public static void updateLoginUser(LoginUser loginUser, String token) { |  | ||||||
|         SaHolder.getStorage().delete(CacheConstants.LOGIN_USER_KEY); |  | ||||||
|         StpUtil.getTokenSessionByToken(token).set(CacheConstants.LOGIN_USER_KEY, loginUser); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获取登录用户信息 |  | ||||||
|      * |  | ||||||
|      * @return 登录用户信息 |  | ||||||
|      * @throws NotLoginException 未登录异常 |  | ||||||
|      */ |  | ||||||
|     public static LoginUser getLoginUser() throws NotLoginException { |  | ||||||
|         StpUtil.checkLogin(); |  | ||||||
|         LoginUser loginUser = (LoginUser)SaHolder.getStorage().get(CacheConstants.LOGIN_USER_KEY); |  | ||||||
|         if (null != loginUser) { |  | ||||||
|             return loginUser; |  | ||||||
|         } |  | ||||||
|         SaSession tokenSession = StpUtil.getTokenSession(); |  | ||||||
|         loginUser = (LoginUser)tokenSession.get(CacheConstants.LOGIN_USER_KEY); |  | ||||||
|         SaHolder.getStorage().set(CacheConstants.LOGIN_USER_KEY, loginUser); |  | ||||||
|         return loginUser; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 根据 Token 获取登录用户信息 |  | ||||||
|      * |  | ||||||
|      * @param token 用户 Token |  | ||||||
|      * @return 登录用户信息 |  | ||||||
|      */ |  | ||||||
|     public static LoginUser getLoginUser(String token) { |  | ||||||
|         SaSession tokenSession = StpUtil.getStpLogic().getTokenSessionByToken(token, false); |  | ||||||
|         if (null == tokenSession) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return (LoginUser)tokenSession.get(CacheConstants.LOGIN_USER_KEY); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获取登录用户 ID |  | ||||||
|      * |  | ||||||
|      * @return 登录用户 ID |  | ||||||
|      */ |  | ||||||
|     public static Long getUserId() { |  | ||||||
|         return getLoginUser().getId(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获取登录用户名 |  | ||||||
|      * |  | ||||||
|      * @return 登录用户名 |  | ||||||
|      */ |  | ||||||
|     public static String getUsername() { |  | ||||||
|         return getLoginUser().getUsername(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获取登录用户昵称 |  | ||||||
|      * |  | ||||||
|      * @return 登录用户昵称 |  | ||||||
|      */ |  | ||||||
|     public static String getNickname() { |  | ||||||
|         return getNickname(getUserId()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获取登录用户昵称 |  | ||||||
|      * |  | ||||||
|      * @param userId 登录用户 ID |  | ||||||
|      * @return 登录用户昵称 |  | ||||||
|      */ |  | ||||||
|     public static String getNickname(Long userId) { |  | ||||||
|         return ExceptionUtils.exToNull(() -> SpringUtil.getBean(CommonUserService.class).getNicknameById(userId)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -18,7 +18,6 @@ package top.continew.admin.auth.service; | |||||||
|  |  | ||||||
| import top.continew.admin.auth.model.query.OnlineUserQuery; | import top.continew.admin.auth.model.query.OnlineUserQuery; | ||||||
| import top.continew.admin.auth.model.resp.OnlineUserResp; | import top.continew.admin.auth.model.resp.OnlineUserResp; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; |  | ||||||
| import top.continew.starter.extension.crud.model.query.PageQuery; | import top.continew.starter.extension.crud.model.query.PageQuery; | ||||||
| import top.continew.starter.extension.crud.model.resp.PageResp; | import top.continew.starter.extension.crud.model.resp.PageResp; | ||||||
|  |  | ||||||
| @@ -48,7 +47,7 @@ public interface OnlineUserService { | |||||||
|      * @param query 查询条件 |      * @param query 查询条件 | ||||||
|      * @return 列表信息 |      * @return 列表信息 | ||||||
|      */ |      */ | ||||||
|     List<LoginUser> list(OnlineUserQuery query); |     List<OnlineUserResp> list(OnlineUserQuery query); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 查询 Token 最后活跃时间 |      * 查询 Token 最后活跃时间 | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
|  |  | ||||||
| package top.continew.admin.auth.service.impl; | package top.continew.admin.auth.service.impl; | ||||||
|  |  | ||||||
|  | import cn.dev33.satoken.stp.SaLoginConfig; | ||||||
| import cn.dev33.satoken.stp.StpUtil; | import cn.dev33.satoken.stp.StpUtil; | ||||||
| import cn.hutool.core.bean.BeanUtil; | import cn.hutool.core.bean.BeanUtil; | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
| @@ -39,11 +40,12 @@ import top.continew.admin.auth.service.LoginService; | |||||||
| import top.continew.admin.common.constant.CacheConstants; | import top.continew.admin.common.constant.CacheConstants; | ||||||
| import top.continew.admin.common.constant.RegexConstants; | import top.continew.admin.common.constant.RegexConstants; | ||||||
| import top.continew.admin.common.constant.SysConstants; | import top.continew.admin.common.constant.SysConstants; | ||||||
|  | import top.continew.admin.common.context.RoleContext; | ||||||
|  | import top.continew.admin.common.context.UserContext; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
|  | import top.continew.admin.common.context.UserExtraContext; | ||||||
| import top.continew.admin.common.enums.DisEnableStatusEnum; | import top.continew.admin.common.enums.DisEnableStatusEnum; | ||||||
| import top.continew.admin.common.enums.GenderEnum; | import top.continew.admin.common.enums.GenderEnum; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; |  | ||||||
| import top.continew.admin.common.model.dto.RoleDTO; |  | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.admin.system.enums.MenuTypeEnum; | import top.continew.admin.system.enums.MenuTypeEnum; | ||||||
| import top.continew.admin.system.enums.MessageTemplateEnum; | import top.continew.admin.system.enums.MessageTemplateEnum; | ||||||
| import top.continew.admin.system.enums.MessageTypeEnum; | import top.continew.admin.system.enums.MessageTypeEnum; | ||||||
| @@ -61,6 +63,7 @@ import top.continew.starter.core.util.validate.CheckUtils; | |||||||
| import top.continew.starter.extension.crud.annotation.TreeField; | import top.continew.starter.extension.crud.annotation.TreeField; | ||||||
| import top.continew.starter.extension.crud.util.TreeUtils; | import top.continew.starter.extension.crud.util.TreeUtils; | ||||||
| import top.continew.starter.messaging.websocket.util.WebSocketUtils; | import top.continew.starter.messaging.websocket.util.WebSocketUtils; | ||||||
|  | import top.continew.starter.web.util.SpringWebUtils; | ||||||
|  |  | ||||||
| import java.time.Duration; | import java.time.Duration; | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
| @@ -205,15 +208,19 @@ public class LoginServiceImpl implements LoginService { | |||||||
|         Long userId = user.getId(); |         Long userId = user.getId(); | ||||||
|         CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> roleService |         CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> roleService | ||||||
|             .listPermissionByUserId(userId), threadPoolTaskExecutor); |             .listPermissionByUserId(userId), threadPoolTaskExecutor); | ||||||
|         CompletableFuture<Set<RoleDTO>> roleFuture = CompletableFuture.supplyAsync(() -> roleService |         CompletableFuture<Set<RoleContext>> roleFuture = CompletableFuture.supplyAsync(() -> roleService | ||||||
|             .listByUserId(userId), threadPoolTaskExecutor); |             .listByUserId(userId), threadPoolTaskExecutor); | ||||||
|         CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService |         CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService | ||||||
|             .getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name())); |             .getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name())); | ||||||
|         CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture); |         CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture); | ||||||
|         LoginUser loginUser = new LoginUser(permissionFuture.join(), roleFuture.join(), passwordExpirationDaysFuture |         UserContext userContext = new UserContext(permissionFuture.join(), roleFuture | ||||||
|             .join()); |             .join(), passwordExpirationDaysFuture.join()); | ||||||
|         BeanUtil.copyProperties(user, loginUser); |         BeanUtil.copyProperties(user, userContext); | ||||||
|         return LoginHelper.login(loginUser); |         // 登录并缓存用户信息 | ||||||
|  |         StpUtil.login(userContext.getId(), SaLoginConfig.setExtraData(BeanUtil | ||||||
|  |             .beanToMap(new UserExtraContext(SpringWebUtils.getRequest())))); | ||||||
|  |         UserContextHolder.setContext(userContext); | ||||||
|  |         return StpUtil.getTokenValue(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import cn.dev33.satoken.dao.SaTokenDao; | |||||||
| import cn.dev33.satoken.stp.StpUtil; | import cn.dev33.satoken.stp.StpUtil; | ||||||
| import cn.hutool.core.bean.BeanUtil; | import cn.hutool.core.bean.BeanUtil; | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.core.convert.Convert; | ||||||
| import cn.hutool.core.date.DateUtil; | import cn.hutool.core.date.DateUtil; | ||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| @@ -28,17 +29,16 @@ import org.springframework.stereotype.Service; | |||||||
| import top.continew.admin.auth.model.query.OnlineUserQuery; | import top.continew.admin.auth.model.query.OnlineUserQuery; | ||||||
| import top.continew.admin.auth.model.resp.OnlineUserResp; | import top.continew.admin.auth.model.resp.OnlineUserResp; | ||||||
| import top.continew.admin.auth.service.OnlineUserService; | import top.continew.admin.auth.service.OnlineUserService; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; | import top.continew.admin.common.context.UserContext; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; | import top.continew.admin.common.context.UserContextHolder; | ||||||
|  | import top.continew.admin.common.context.UserExtraContext; | ||||||
| import top.continew.starter.core.constant.StringConstants; | import top.continew.starter.core.constant.StringConstants; | ||||||
| import top.continew.starter.extension.crud.model.query.PageQuery; | import top.continew.starter.extension.crud.model.query.PageQuery; | ||||||
| import top.continew.starter.extension.crud.model.resp.PageResp; | import top.continew.starter.extension.crud.model.resp.PageResp; | ||||||
|  |  | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
| import java.util.ArrayList; | import java.util.*; | ||||||
| import java.util.Comparator; | import java.util.stream.Collectors; | ||||||
| import java.util.Date; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 在线用户业务实现 |  * 在线用户业务实现 | ||||||
| @@ -53,31 +53,43 @@ public class OnlineUserServiceImpl implements OnlineUserService { | |||||||
|     @Override |     @Override | ||||||
|     @AutoOperate(type = OnlineUserResp.class, on = "list") |     @AutoOperate(type = OnlineUserResp.class, on = "list") | ||||||
|     public PageResp<OnlineUserResp> page(OnlineUserQuery query, PageQuery pageQuery) { |     public PageResp<OnlineUserResp> page(OnlineUserQuery query, PageQuery pageQuery) { | ||||||
|         List<LoginUser> loginUserList = this.list(query); |         List<OnlineUserResp> list = this.list(query); | ||||||
|         List<OnlineUserResp> list = BeanUtil.copyToList(loginUserList, OnlineUserResp.class); |  | ||||||
|         return PageResp.build(pageQuery.getPage(), pageQuery.getSize(), list); |         return PageResp.build(pageQuery.getPage(), pageQuery.getSize(), list); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public List<LoginUser> list(OnlineUserQuery query) { |     public List<OnlineUserResp> list(OnlineUserQuery query) { | ||||||
|         List<LoginUser> loginUserList = new ArrayList<>(); |         List<OnlineUserResp> list = new ArrayList<>(); | ||||||
|         // 查询所有登录用户 |         // 查询所有在线 Token | ||||||
|         List<String> tokenKeyList = StpUtil.searchTokenValue(StringConstants.EMPTY, 0, -1, false); |         List<String> tokenKeyList = StpUtil.searchTokenValue(StringConstants.EMPTY, 0, -1, false); | ||||||
|         tokenKeyList.parallelStream().forEach(tokenKey -> { |         Map<Long, List<String>> tokenMap = tokenKeyList.stream().filter(tokenKey -> { | ||||||
|             String token = StrUtil.subAfter(tokenKey, StringConstants.COLON, true); |             String token = StrUtil.subAfter(tokenKey, StringConstants.COLON, true); | ||||||
|             // 忽略已过期或失效 Token |             // 忽略已过期或失效 Token | ||||||
|             if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < SaTokenDao.NEVER_EXPIRE) { |             return StpUtil.getStpLogic().getTokenActiveTimeoutByToken(token) >= SaTokenDao.NEVER_EXPIRE; | ||||||
|                 return; |         }) | ||||||
|  |             .map(tokenKey -> StrUtil.subAfter(tokenKey, StringConstants.COLON, true)) | ||||||
|  |             .collect(Collectors.groupingBy(token -> Convert.toLong(StpUtil.getLoginIdByToken(token)))); | ||||||
|  |         // 过滤 Token | ||||||
|  |         for (Map.Entry<Long, List<String>> entry : tokenMap.entrySet()) { | ||||||
|  |             Long userId = entry.getKey(); | ||||||
|  |             UserContext userContext = UserContextHolder.getContext(userId); | ||||||
|  |             if (null == userContext || !this.isMatchNickname(query.getNickname(), userContext)) { | ||||||
|  |                 continue; | ||||||
|             } |             } | ||||||
|             // 检查是否符合查询条件 |             List<Date> loginTimeList = query.getLoginTime(); | ||||||
|             LoginUser loginUser = LoginHelper.getLoginUser(token); |             entry.getValue().parallelStream().forEach(token -> { | ||||||
|             if (this.isMatchQuery(query, loginUser)) { |                 UserExtraContext extraContext = UserContextHolder.getExtraContext(token); | ||||||
|                 loginUserList.add(loginUser); |                 if (this.isMatchLoginTime(loginTimeList, extraContext.getLoginTime())) { | ||||||
|             } |                     OnlineUserResp resp = BeanUtil.copyProperties(userContext, OnlineUserResp.class); | ||||||
|         }); |                     BeanUtil.copyProperties(extraContext, resp); | ||||||
|  |                     resp.setToken(token); | ||||||
|  |                     list.add(resp); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|         // 设置排序 |         // 设置排序 | ||||||
|         CollUtil.sort(loginUserList, Comparator.comparing(LoginUser::getLoginTime).reversed()); |         CollUtil.sort(list, Comparator.comparing(OnlineUserResp::getLoginTime).reversed()); | ||||||
|         return loginUserList; |         return list; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -95,35 +107,31 @@ public class OnlineUserServiceImpl implements OnlineUserService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 是否符合查询条件 |      * 是否匹配昵称 | ||||||
|      * |      * | ||||||
|      * @param query     查询条件 |      * @param nickname    昵称 | ||||||
|      * @param loginUser 登录用户信息 |      * @param userContext 用户上下文信息 | ||||||
|      * @return 是否符合查询条件 |      * @return 是否匹配昵称 | ||||||
|      */ |      */ | ||||||
|     private boolean isMatchQuery(OnlineUserQuery query, LoginUser loginUser) { |     private boolean isMatchNickname(String nickname, UserContext userContext) { | ||||||
|         boolean flag1 = true; |         if (StrUtil.isBlank(nickname)) { | ||||||
|         String nickname = query.getNickname(); |             return true; | ||||||
|         if (StrUtil.isNotBlank(nickname)) { |  | ||||||
|             flag1 = StrUtil.contains(loginUser.getUsername(), nickname) || StrUtil.contains(LoginHelper |  | ||||||
|                 .getNickname(loginUser.getId()), nickname); |  | ||||||
|         } |         } | ||||||
|         boolean flag2 = true; |         return StrUtil.contains(userContext.getUsername(), nickname) || StrUtil.contains(UserContextHolder | ||||||
|         List<Date> loginTime = query.getLoginTime(); |             .getNickname(userContext.getId()), nickname); | ||||||
|         if (CollUtil.isNotEmpty(loginTime)) { |     } | ||||||
|             flag2 = DateUtil.isIn(DateUtil.date(loginUser.getLoginTime()).toJdkDate(), loginTime.get(0), loginTime |  | ||||||
|                 .get(1)); |     /** | ||||||
|  |      * 是否匹配登录时间 | ||||||
|  |      * | ||||||
|  |      * @param loginTimeList 登录时间列表 | ||||||
|  |      * @param loginTime     登录时间 | ||||||
|  |      * @return 是否匹配登录时间 | ||||||
|  |      */ | ||||||
|  |     private boolean isMatchLoginTime(List<Date> loginTimeList, LocalDateTime loginTime) { | ||||||
|  |         if (CollUtil.isEmpty(loginTimeList)) { | ||||||
|  |             return true; | ||||||
|         } |         } | ||||||
|         boolean flag3 = true; |         return DateUtil.isIn(DateUtil.date(loginTime).toJdkDate(), loginTimeList.get(0), loginTimeList.get(1)); | ||||||
|         Long userId = query.getUserId(); |  | ||||||
|         if (null != userId) { |  | ||||||
|             flag3 = userId.equals(loginUser.getId()); |  | ||||||
|         } |  | ||||||
|         boolean flag4 = true; |  | ||||||
|         Long roleId = query.getRoleId(); |  | ||||||
|         if (null != roleId) { |  | ||||||
|             flag4 = loginUser.getRoles().stream().anyMatch(r -> r.getId().equals(roleId)); |  | ||||||
|         } |  | ||||||
|         return flag1 && flag2 && flag3 && flag4; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ import org.dromara.x.file.storage.core.FileInfo; | |||||||
| import org.dromara.x.file.storage.core.recorder.FileRecorder; | import org.dromara.x.file.storage.core.recorder.FileRecorder; | ||||||
| import org.dromara.x.file.storage.core.upload.FilePartInfo; | import org.dromara.x.file.storage.core.upload.FilePartInfo; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.system.enums.FileTypeEnum; | import top.continew.admin.system.enums.FileTypeEnum; | ||||||
| import top.continew.admin.system.mapper.FileMapper; | import top.continew.admin.system.mapper.FileMapper; | ||||||
| import top.continew.admin.system.mapper.StorageMapper; | import top.continew.admin.system.mapper.StorageMapper; | ||||||
| @@ -66,7 +66,7 @@ public class FileRecorderImpl implements FileRecorder { | |||||||
|         StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false)); |         StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false)); | ||||||
|         file.setStorageId(storage.getId()); |         file.setStorageId(storage.getId()); | ||||||
|         file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime())); |         file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime())); | ||||||
|         file.setUpdateUser(LoginHelper.getUserId()); |         file.setUpdateUser(UserContextHolder.getUserId()); | ||||||
|         file.setUpdateTime(file.getCreateTime()); |         file.setUpdateTime(file.getCreateTime()); | ||||||
|         fileMapper.insert(file); |         fileMapper.insert(file); | ||||||
|         return true; |         return true; | ||||||
|   | |||||||
| @@ -16,13 +16,9 @@ | |||||||
|  |  | ||||||
| package top.continew.admin.system.mapper; | package top.continew.admin.system.mapper; | ||||||
|  |  | ||||||
| import org.apache.ibatis.annotations.Param; |  | ||||||
| import org.apache.ibatis.annotations.Select; |  | ||||||
| import top.continew.admin.system.model.entity.UserRoleDO; | import top.continew.admin.system.model.entity.UserRoleDO; | ||||||
| import top.continew.starter.data.mp.base.BaseMapper; | import top.continew.starter.data.mp.base.BaseMapper; | ||||||
|  |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 用户和角色 Mapper |  * 用户和角色 Mapper | ||||||
|  * |  * | ||||||
| @@ -30,13 +26,4 @@ import java.util.List; | |||||||
|  * @since 2023/2/13 23:13 |  * @since 2023/2/13 23:13 | ||||||
|  */ |  */ | ||||||
| public interface UserRoleMapper extends BaseMapper<UserRoleDO> { | public interface UserRoleMapper extends BaseMapper<UserRoleDO> { | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 根据用户 ID 查询 |  | ||||||
|      * |  | ||||||
|      * @param userId 用户 ID |  | ||||||
|      * @return 角色 ID 列表 |  | ||||||
|      */ |  | ||||||
|     @Select("SELECT role_id FROM sys_user_role WHERE user_id = #{userId}") |  | ||||||
|     List<Long> selectRoleIdByUserId(@Param("userId") Long userId); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -27,12 +27,12 @@ import com.alibaba.excel.annotation.ExcelProperty; | |||||||
| import io.swagger.v3.oas.annotations.media.Schema; | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| import top.continew.admin.common.constant.ContainerConstants; | import top.continew.admin.common.constant.ContainerConstants; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.common.enums.DisEnableStatusEnum; | import top.continew.admin.common.enums.DisEnableStatusEnum; | ||||||
| import top.continew.admin.common.enums.GenderEnum; | import top.continew.admin.common.enums.GenderEnum; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.admin.system.service.DeptService; | import top.continew.admin.system.service.DeptService; | ||||||
| import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; |  | ||||||
| import top.continew.starter.extension.crud.model.resp.BaseDetailResp; | import top.continew.starter.extension.crud.model.resp.BaseDetailResp; | ||||||
|  | import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter; | ||||||
| import top.continew.starter.file.excel.converter.ExcelListConverter; | import top.continew.starter.file.excel.converter.ExcelListConverter; | ||||||
| import top.continew.starter.security.crypto.annotation.FieldEncrypt; | import top.continew.starter.security.crypto.annotation.FieldEncrypt; | ||||||
|  |  | ||||||
| @@ -160,6 +160,6 @@ public class UserDetailResp extends BaseDetailResp { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Boolean getDisabled() { |     public Boolean getDisabled() { | ||||||
|         return this.getIsSystem() || Objects.equals(this.getId(), LoginHelper.getUserId()); |         return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,9 +21,9 @@ import cn.crane4j.core.executor.handler.ManyToManyAssembleOperationHandler; | |||||||
| import io.swagger.v3.oas.annotations.media.Schema; | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| import top.continew.admin.common.constant.ContainerConstants; | import top.continew.admin.common.constant.ContainerConstants; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.common.enums.DisEnableStatusEnum; | import top.continew.admin.common.enums.DisEnableStatusEnum; | ||||||
| import top.continew.admin.common.enums.GenderEnum; | import top.continew.admin.common.enums.GenderEnum; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.starter.extension.crud.model.resp.BaseDetailResp; | import top.continew.starter.extension.crud.model.resp.BaseDetailResp; | ||||||
| import top.continew.starter.security.mask.annotation.JsonMask; | import top.continew.starter.security.mask.annotation.JsonMask; | ||||||
| import top.continew.starter.security.mask.enums.MaskType; | import top.continew.starter.security.mask.enums.MaskType; | ||||||
| @@ -129,6 +129,6 @@ public class UserResp extends BaseDetailResp { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Boolean getDisabled() { |     public Boolean getDisabled() { | ||||||
|         return this.getIsSystem() || Objects.equals(this.getId(), LoginHelper.getUserId()); |         return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|  |  | ||||||
| package top.continew.admin.system.service; | package top.continew.admin.system.service; | ||||||
|  |  | ||||||
| import top.continew.admin.common.model.dto.RoleDTO; | import top.continew.admin.common.context.RoleContext; | ||||||
| import top.continew.admin.system.model.entity.RoleDO; | import top.continew.admin.system.model.entity.RoleDO; | ||||||
| import top.continew.admin.system.model.query.RoleQuery; | import top.continew.admin.system.model.query.RoleQuery; | ||||||
| import top.continew.admin.system.model.req.RoleReq; | import top.continew.admin.system.model.req.RoleReq; | ||||||
| @@ -66,7 +66,7 @@ public interface RoleService extends BaseService<RoleResp, RoleDetailResp, RoleQ | |||||||
|      * @param userId 用户 ID |      * @param userId 用户 ID | ||||||
|      * @return 角色集合 |      * @return 角色集合 | ||||||
|      */ |      */ | ||||||
|     Set<RoleDTO> listByUserId(Long userId); |     Set<RoleContext> listByUserId(Long userId); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 根据角色编码查询 |      * 根据角色编码查询 | ||||||
|   | |||||||
| @@ -59,6 +59,14 @@ public interface UserRoleService { | |||||||
|      */ |      */ | ||||||
|     List<Long> listRoleIdByUserId(Long userId); |     List<Long> listRoleIdByUserId(Long userId); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 根据角色 ID 查询 | ||||||
|  |      * | ||||||
|  |      * @param roleId 角色 ID | ||||||
|  |      * @return 用户 ID 列表 | ||||||
|  |      */ | ||||||
|  |     List<Long> listUserIdByRoleId(Long roleId); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 根据角色 ID 判断是否已被用户关联 |      * 根据角色 ID 判断是否已被用户关联 | ||||||
|      * |      * | ||||||
|   | |||||||
| @@ -26,15 +26,14 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; | |||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| import top.continew.admin.auth.model.query.OnlineUserQuery; |  | ||||||
| import top.continew.admin.auth.service.OnlineUserService; | import top.continew.admin.auth.service.OnlineUserService; | ||||||
| import top.continew.admin.common.constant.CacheConstants; | import top.continew.admin.common.constant.CacheConstants; | ||||||
| import top.continew.admin.common.constant.ContainerConstants; | import top.continew.admin.common.constant.ContainerConstants; | ||||||
| import top.continew.admin.common.constant.SysConstants; | import top.continew.admin.common.constant.SysConstants; | ||||||
|  | import top.continew.admin.common.context.RoleContext; | ||||||
|  | import top.continew.admin.common.context.UserContext; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.common.enums.DataScopeEnum; | import top.continew.admin.common.enums.DataScopeEnum; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; |  | ||||||
| import top.continew.admin.common.model.dto.RoleDTO; |  | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.admin.system.mapper.RoleMapper; | import top.continew.admin.system.mapper.RoleMapper; | ||||||
| import top.continew.admin.system.model.entity.RoleDO; | import top.continew.admin.system.model.entity.RoleDO; | ||||||
| import top.continew.admin.system.model.query.RoleQuery; | import top.continew.admin.system.model.query.RoleQuery; | ||||||
| @@ -104,13 +103,14 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleRes | |||||||
|         boolean isSaveDeptSuccess = roleDeptService.add(req.getDeptIds(), id); |         boolean isSaveDeptSuccess = roleDeptService.add(req.getDeptIds(), id); | ||||||
|         // 如果功能权限或数据权限有变更,则更新在线用户权限信息 |         // 如果功能权限或数据权限有变更,则更新在线用户权限信息 | ||||||
|         if (isSaveMenuSuccess || isSaveDeptSuccess || ObjectUtil.notEqual(req.getDataScope(), oldDataScope)) { |         if (isSaveMenuSuccess || isSaveDeptSuccess || ObjectUtil.notEqual(req.getDataScope(), oldDataScope)) { | ||||||
|             OnlineUserQuery query = new OnlineUserQuery(); |             List<Long> userIdList = userRoleService.listUserIdByRoleId(id); | ||||||
|             query.setRoleId(id); |             userIdList.parallelStream().forEach(userId -> { | ||||||
|             List<LoginUser> loginUserList = onlineUserService.list(query); |                 UserContext userContext = UserContextHolder.getContext(userId); | ||||||
|             loginUserList.forEach(loginUser -> { |                 if (null != userContext) { | ||||||
|                 loginUser.setRoles(this.listByUserId(loginUser.getId())); |                     userContext.setRoles(this.listByUserId(userId)); | ||||||
|                 loginUser.setPermissions(this.listPermissionByUserId(loginUser.getId())); |                     userContext.setPermissions(this.listPermissionByUserId(userId)); | ||||||
|                 LoginHelper.updateLoginUser(loginUser, loginUser.getToken()); |                     UserContextHolder.setContext(userContext); | ||||||
|  |                 } | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -171,10 +171,10 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleRes | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Set<RoleDTO> listByUserId(Long userId) { |     public Set<RoleContext> listByUserId(Long userId) { | ||||||
|         List<Long> roleIdList = userRoleService.listRoleIdByUserId(userId); |         List<Long> roleIdList = userRoleService.listRoleIdByUserId(userId); | ||||||
|         List<RoleDO> roleList = baseMapper.lambdaQuery().in(RoleDO::getId, roleIdList).list(); |         List<RoleDO> roleList = baseMapper.lambdaQuery().in(RoleDO::getId, roleIdList).list(); | ||||||
|         return new HashSet<>(BeanUtil.copyToList(roleList, RoleDTO.class)); |         return new HashSet<>(BeanUtil.copyToList(roleList, RoleContext.class)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -75,7 +75,24 @@ public class UserRoleServiceImpl implements UserRoleService { | |||||||
|     @Override |     @Override | ||||||
|     @ContainerMethod(namespace = ContainerConstants.USER_ROLE_ID_LIST, type = MappingType.ORDER_OF_KEYS) |     @ContainerMethod(namespace = ContainerConstants.USER_ROLE_ID_LIST, type = MappingType.ORDER_OF_KEYS) | ||||||
|     public List<Long> listRoleIdByUserId(Long userId) { |     public List<Long> listRoleIdByUserId(Long userId) { | ||||||
|         return baseMapper.selectRoleIdByUserId(userId); |         return baseMapper.lambdaQuery() | ||||||
|  |             .select(UserRoleDO::getRoleId) | ||||||
|  |             .eq(UserRoleDO::getUserId, userId) | ||||||
|  |             .list() | ||||||
|  |             .stream() | ||||||
|  |             .map(UserRoleDO::getRoleId) | ||||||
|  |             .toList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<Long> listUserIdByRoleId(Long roleId) { | ||||||
|  |         return baseMapper.lambdaQuery() | ||||||
|  |             .select(UserRoleDO::getUserId) | ||||||
|  |             .eq(UserRoleDO::getRoleId, roleId) | ||||||
|  |             .list() | ||||||
|  |             .stream() | ||||||
|  |             .map(UserRoleDO::getUserId) | ||||||
|  |             .toList(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -52,15 +52,14 @@ import org.springframework.security.crypto.password.PasswordEncoder; | |||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| import org.springframework.web.multipart.MultipartFile; | import org.springframework.web.multipart.MultipartFile; | ||||||
| import top.continew.admin.auth.model.query.OnlineUserQuery; |  | ||||||
| import top.continew.admin.auth.service.OnlineUserService; | import top.continew.admin.auth.service.OnlineUserService; | ||||||
| import top.continew.admin.common.constant.CacheConstants; | import top.continew.admin.common.constant.CacheConstants; | ||||||
| import top.continew.admin.common.constant.SysConstants; | import top.continew.admin.common.constant.SysConstants; | ||||||
|  | import top.continew.admin.common.context.UserContext; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.common.enums.DisEnableStatusEnum; | import top.continew.admin.common.enums.DisEnableStatusEnum; | ||||||
| import top.continew.admin.common.enums.GenderEnum; | import top.continew.admin.common.enums.GenderEnum; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; |  | ||||||
| import top.continew.admin.common.util.SecureUtils; | import top.continew.admin.common.util.SecureUtils; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.admin.system.mapper.UserMapper; | import top.continew.admin.system.mapper.UserMapper; | ||||||
| import top.continew.admin.system.model.entity.DeptDO; | import top.continew.admin.system.model.entity.DeptDO; | ||||||
| import top.continew.admin.system.model.entity.RoleDO; | import top.continew.admin.system.model.entity.RoleDO; | ||||||
| @@ -310,7 +309,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes | |||||||
|         String phone = req.getPhone(); |         String phone = req.getPhone(); | ||||||
|         CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, id), errorMsgTemplate, phone); |         CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, id), errorMsgTemplate, phone); | ||||||
|         DisEnableStatusEnum newStatus = req.getStatus(); |         DisEnableStatusEnum newStatus = req.getStatus(); | ||||||
|         CheckUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(newStatus) && ObjectUtil.equal(id, LoginHelper |         CheckUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(newStatus) && ObjectUtil.equal(id, UserContextHolder | ||||||
|             .getUserId()), "不允许禁用当前用户"); |             .getUserId()), "不允许禁用当前用户"); | ||||||
|         UserDO oldUser = super.getById(id); |         UserDO oldUser = super.getById(id); | ||||||
|         if (Boolean.TRUE.equals(oldUser.getIsSystem())) { |         if (Boolean.TRUE.equals(oldUser.getIsSystem())) { | ||||||
| @@ -333,14 +332,12 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes | |||||||
|         } |         } | ||||||
|         // 如果角色有变更,则更新在线用户权限信息 |         // 如果角色有变更,则更新在线用户权限信息 | ||||||
|         if (isSaveUserRoleSuccess) { |         if (isSaveUserRoleSuccess) { | ||||||
|             OnlineUserQuery query = new OnlineUserQuery(); |             UserContext userContext = UserContextHolder.getContext(id); | ||||||
|             query.setUserId(id); |             if (null != userContext) { | ||||||
|             List<LoginUser> loginUserList = onlineUserService.list(query); |                 userContext.setRoles(roleService.listByUserId(id)); | ||||||
|             loginUserList.forEach(loginUser -> { |                 userContext.setPermissions(roleService.listPermissionByUserId(id)); | ||||||
|                 loginUser.setRoles(roleService.listByUserId(loginUser.getId())); |                 UserContextHolder.setContext(userContext); | ||||||
|                 loginUser.setPermissions(roleService.listPermissionByUserId(loginUser.getId())); |             } | ||||||
|                 LoginHelper.updateLoginUser(loginUser, loginUser.getToken()); |  | ||||||
|             }); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -348,7 +345,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes | |||||||
|     @Transactional(rollbackFor = Exception.class) |     @Transactional(rollbackFor = Exception.class) | ||||||
|     @CacheInvalidate(key = "#ids", name = CacheConstants.USER_KEY_PREFIX, multi = true) |     @CacheInvalidate(key = "#ids", name = CacheConstants.USER_KEY_PREFIX, multi = true) | ||||||
|     public void delete(List<Long> ids) { |     public void delete(List<Long> ids) { | ||||||
|         CheckUtils.throwIf(CollUtil.contains(ids, LoginHelper.getUserId()), "不允许删除当前用户"); |         CheckUtils.throwIf(CollUtil.contains(ids, UserContextHolder.getUserId()), "不允许删除当前用户"); | ||||||
|         List<UserDO> list = baseMapper.lambdaQuery() |         List<UserDO> list = baseMapper.lambdaQuery() | ||||||
|             .select(UserDO::getNickname, UserDO::getIsSystem) |             .select(UserDO::getNickname, UserDO::getIsSystem) | ||||||
|             .in(UserDO::getId, ids) |             .in(UserDO::getId, ids) | ||||||
|   | |||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.continew.admin.config.satoken; | ||||||
|  |  | ||||||
|  | import cn.dev33.satoken.fun.SaParamFunction; | ||||||
|  | import cn.dev33.satoken.interceptor.SaInterceptor; | ||||||
|  | import cn.dev33.satoken.stp.StpUtil; | ||||||
|  | import jakarta.servlet.http.HttpServletRequest; | ||||||
|  | import jakarta.servlet.http.HttpServletResponse; | ||||||
|  | import org.springframework.lang.Nullable; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sa-Token 扩展拦截器 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2024/10/10 20:25 | ||||||
|  |  */ | ||||||
|  | public class SaExtensionInterceptor extends SaInterceptor { | ||||||
|  |  | ||||||
|  |     public SaExtensionInterceptor(SaParamFunction<Object> auth) { | ||||||
|  |         super(auth); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean preHandle(HttpServletRequest request, | ||||||
|  |                              HttpServletResponse response, | ||||||
|  |                              Object handler) throws Exception { | ||||||
|  |         boolean flag = super.preHandle(request, response, handler); | ||||||
|  |         if (flag && StpUtil.isLogin()) { | ||||||
|  |             UserContextHolder.getContext(); | ||||||
|  |             UserContextHolder.getExtraContext(); | ||||||
|  |         } | ||||||
|  |         return flag; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void afterCompletion(HttpServletRequest request, | ||||||
|  |                                 HttpServletResponse response, | ||||||
|  |                                 Object handler, | ||||||
|  |                                 @Nullable Exception e) throws Exception { | ||||||
|  |         try { | ||||||
|  |             super.afterCompletion(request, response, handler, e); | ||||||
|  |         } finally { | ||||||
|  |             UserContextHolder.clearContext(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -19,11 +19,12 @@ package top.continew.admin.config.satoken; | |||||||
| import cn.dev33.satoken.interceptor.SaInterceptor; | import cn.dev33.satoken.interceptor.SaInterceptor; | ||||||
| import cn.dev33.satoken.router.SaRouter; | import cn.dev33.satoken.router.SaRouter; | ||||||
| import cn.dev33.satoken.stp.StpInterface; | import cn.dev33.satoken.stp.StpInterface; | ||||||
|  | import cn.dev33.satoken.stp.StpUtil; | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; | import top.continew.admin.common.context.UserContext; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties; | import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties; | ||||||
| import top.continew.starter.core.constant.StringConstants; | import top.continew.starter.core.constant.StringConstants; | ||||||
| import top.continew.starter.core.util.validate.CheckUtils; | import top.continew.starter.core.util.validate.CheckUtils; | ||||||
| @@ -54,14 +55,15 @@ public class SaTokenConfiguration { | |||||||
|      */ |      */ | ||||||
|     @Bean |     @Bean | ||||||
|     public SaInterceptor saInterceptor() { |     public SaInterceptor saInterceptor() { | ||||||
|         return new SaInterceptor(handle -> SaRouter.match(StringConstants.PATH_PATTERN) |         return new SaExtensionInterceptor(handle -> SaRouter.match(StringConstants.PATH_PATTERN) | ||||||
|             .notMatch(properties.getSecurity().getExcludes()) |             .notMatch(properties.getSecurity().getExcludes()) | ||||||
|             .check(r -> { |             .check(r -> { | ||||||
|                 LoginUser loginUser = LoginHelper.getLoginUser(); |                 StpUtil.checkLogin(); | ||||||
|                 if (SaRouter.isMatchCurrURI(loginPasswordProperties.getExcludes())) { |                 if (SaRouter.isMatchCurrURI(loginPasswordProperties.getExcludes())) { | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|                 CheckUtils.throwIf(loginUser.isPasswordExpired(), "密码已过期,请修改密码"); |                 UserContext userContext = UserContextHolder.getContext(); | ||||||
|  |                 CheckUtils.throwIf(userContext.isPasswordExpired(), "密码已过期,请修改密码"); | ||||||
|             })); |             })); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,8 +17,8 @@ | |||||||
| package top.continew.admin.config.satoken; | package top.continew.admin.config.satoken; | ||||||
|  |  | ||||||
| import cn.dev33.satoken.stp.StpInterface; | import cn.dev33.satoken.stp.StpInterface; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; | import top.continew.admin.common.context.UserContext; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; | import top.continew.admin.common.context.UserContextHolder; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -33,13 +33,13 @@ public class SaTokenPermissionImpl implements StpInterface { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public List<String> getPermissionList(Object loginId, String loginType) { |     public List<String> getPermissionList(Object loginId, String loginType) { | ||||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); |         UserContext userContext = UserContextHolder.getContext(); | ||||||
|         return new ArrayList<>(loginUser.getPermissions()); |         return new ArrayList<>(userContext.getPermissions()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public List<String> getRoleList(Object loginId, String loginType) { |     public List<String> getRoleList(Object loginId, String loginType) { | ||||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); |         UserContext userContext = UserContextHolder.getContext(); | ||||||
|         return new ArrayList<>(loginUser.getRoleCodes()); |         return new ArrayList<>(userContext.getRoleCodes()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -35,9 +35,9 @@ import top.continew.admin.auth.model.resp.RouteResp; | |||||||
| import top.continew.admin.auth.model.resp.UserInfoResp; | import top.continew.admin.auth.model.resp.UserInfoResp; | ||||||
| import top.continew.admin.auth.service.LoginService; | import top.continew.admin.auth.service.LoginService; | ||||||
| import top.continew.admin.common.constant.CacheConstants; | import top.continew.admin.common.constant.CacheConstants; | ||||||
| import top.continew.admin.common.model.dto.LoginUser; | import top.continew.admin.common.context.UserContext; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.common.util.SecureUtils; | import top.continew.admin.common.util.SecureUtils; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.admin.system.model.resp.UserDetailResp; | import top.continew.admin.system.model.resp.UserDetailResp; | ||||||
| import top.continew.admin.system.service.UserService; | import top.continew.admin.system.service.UserService; | ||||||
| import top.continew.starter.cache.redisson.util.RedisUtils; | import top.continew.starter.cache.redisson.util.RedisUtils; | ||||||
| @@ -122,12 +122,12 @@ public class AuthController { | |||||||
|     @Operation(summary = "获取用户信息", description = "获取登录用户信息") |     @Operation(summary = "获取用户信息", description = "获取登录用户信息") | ||||||
|     @GetMapping("/user/info") |     @GetMapping("/user/info") | ||||||
|     public UserInfoResp getUserInfo() { |     public UserInfoResp getUserInfo() { | ||||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); |         UserContext userContext = UserContextHolder.getContext(); | ||||||
|         UserDetailResp userDetailResp = userService.get(loginUser.getId()); |         UserDetailResp userDetailResp = userService.get(userContext.getId()); | ||||||
|         UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class); |         UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class); | ||||||
|         userInfoResp.setPermissions(loginUser.getPermissions()); |         userInfoResp.setPermissions(userContext.getPermissions()); | ||||||
|         userInfoResp.setRoles(loginUser.getRoleCodes()); |         userInfoResp.setRoles(userContext.getRoleCodes()); | ||||||
|         userInfoResp.setPwdExpired(loginUser.isPasswordExpired()); |         userInfoResp.setPwdExpired(userContext.isPasswordExpired()); | ||||||
|         return userInfoResp; |         return userInfoResp; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -135,6 +135,6 @@ public class AuthController { | |||||||
|     @Operation(summary = "获取路由信息", description = "获取登录用户的路由信息") |     @Operation(summary = "获取路由信息", description = "获取登录用户的路由信息") | ||||||
|     @GetMapping("/route") |     @GetMapping("/route") | ||||||
|     public List<RouteResp> listRoute() { |     public List<RouteResp> listRoute() { | ||||||
|         return loginService.buildRouteTree(LoginHelper.getUserId()); |         return loginService.buildRouteTree(UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -23,7 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; | |||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| import org.springframework.validation.annotation.Validated; | import org.springframework.validation.annotation.Validated; | ||||||
| import org.springframework.web.bind.annotation.*; | import org.springframework.web.bind.annotation.*; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.system.model.query.MessageQuery; | import top.continew.admin.system.model.query.MessageQuery; | ||||||
| import top.continew.admin.system.model.resp.MessageResp; | import top.continew.admin.system.model.resp.MessageResp; | ||||||
| import top.continew.admin.system.model.resp.MessageUnreadResp; | import top.continew.admin.system.model.resp.MessageUnreadResp; | ||||||
| @@ -53,7 +53,7 @@ public class MessageController { | |||||||
|     @Operation(summary = "分页查询列表", description = "分页查询列表") |     @Operation(summary = "分页查询列表", description = "分页查询列表") | ||||||
|     @GetMapping |     @GetMapping | ||||||
|     public PageResp<MessageResp> page(MessageQuery query, @Validated PageQuery pageQuery) { |     public PageResp<MessageResp> page(MessageQuery query, @Validated PageQuery pageQuery) { | ||||||
|         query.setUserId(LoginHelper.getUserId()); |         query.setUserId(UserContextHolder.getUserId()); | ||||||
|         return baseService.page(query, pageQuery); |         return baseService.page(query, pageQuery); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -76,6 +76,6 @@ public class MessageController { | |||||||
|     @Parameter(name = "isDetail", description = "是否查询详情", example = "true", in = ParameterIn.QUERY) |     @Parameter(name = "isDetail", description = "是否查询详情", example = "true", in = ParameterIn.QUERY) | ||||||
|     @GetMapping("/unread") |     @GetMapping("/unread") | ||||||
|     public MessageUnreadResp countUnreadMessage(@RequestParam(required = false) Boolean detail) { |     public MessageUnreadResp countUnreadMessage(@RequestParam(required = false) Boolean detail) { | ||||||
|         return messageUserService.countUnreadMessageByUserId(LoginHelper.getUserId(), detail); |         return messageUserService.countUnreadMessageByUserId(UserContextHolder.getUserId(), detail); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -31,8 +31,8 @@ import org.springframework.validation.annotation.Validated; | |||||||
| import org.springframework.web.bind.annotation.*; | import org.springframework.web.bind.annotation.*; | ||||||
| import org.springframework.web.multipart.MultipartFile; | import org.springframework.web.multipart.MultipartFile; | ||||||
| import top.continew.admin.common.constant.CacheConstants; | import top.continew.admin.common.constant.CacheConstants; | ||||||
|  | import top.continew.admin.common.context.UserContextHolder; | ||||||
| import top.continew.admin.common.util.SecureUtils; | import top.continew.admin.common.util.SecureUtils; | ||||||
| import top.continew.admin.common.util.helper.LoginHelper; |  | ||||||
| import top.continew.admin.system.enums.SocialSourceEnum; | import top.continew.admin.system.enums.SocialSourceEnum; | ||||||
| import top.continew.admin.system.model.entity.UserSocialDO; | import top.continew.admin.system.model.entity.UserSocialDO; | ||||||
| import top.continew.admin.system.model.req.UserBasicInfoUpdateReq; | import top.continew.admin.system.model.req.UserBasicInfoUpdateReq; | ||||||
| @@ -73,14 +73,14 @@ public class UserCenterController { | |||||||
|     @PostMapping("/avatar") |     @PostMapping("/avatar") | ||||||
|     public AvatarResp updateAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) throws IOException { |     public AvatarResp updateAvatar(@NotNull(message = "头像不能为空") MultipartFile avatarFile) throws IOException { | ||||||
|         ValidationUtils.throwIf(avatarFile::isEmpty, "头像不能为空"); |         ValidationUtils.throwIf(avatarFile::isEmpty, "头像不能为空"); | ||||||
|         String newAvatar = userService.updateAvatar(avatarFile, LoginHelper.getUserId()); |         String newAvatar = userService.updateAvatar(avatarFile, UserContextHolder.getUserId()); | ||||||
|         return AvatarResp.builder().avatar(newAvatar).build(); |         return AvatarResp.builder().avatar(newAvatar).build(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Operation(summary = "修改基础信息", description = "修改用户基础信息") |     @Operation(summary = "修改基础信息", description = "修改用户基础信息") | ||||||
|     @PatchMapping("/basic/info") |     @PatchMapping("/basic/info") | ||||||
|     public void updateBasicInfo(@Validated @RequestBody UserBasicInfoUpdateReq req) { |     public void updateBasicInfo(@Validated @RequestBody UserBasicInfoUpdateReq req) { | ||||||
|         userService.updateBasicInfo(req, LoginHelper.getUserId()); |         userService.updateBasicInfo(req, UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Operation(summary = "修改密码", description = "修改用户登录密码") |     @Operation(summary = "修改密码", description = "修改用户登录密码") | ||||||
| @@ -92,7 +92,7 @@ public class UserCenterController { | |||||||
|         String rawNewPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq |         String rawNewPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq | ||||||
|             .getNewPassword())); |             .getNewPassword())); | ||||||
|         ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败"); |         ValidationUtils.throwIfNull(rawNewPassword, "新密码解密失败"); | ||||||
|         userService.updatePassword(rawOldPassword, rawNewPassword, LoginHelper.getUserId()); |         userService.updatePassword(rawOldPassword, rawNewPassword, UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Operation(summary = "修改手机号", description = "修改手机号") |     @Operation(summary = "修改手机号", description = "修改手机号") | ||||||
| @@ -106,7 +106,7 @@ public class UserCenterController { | |||||||
|         ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); |         ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); | ||||||
|         ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); |         ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); | ||||||
|         RedisUtils.delete(captchaKey); |         RedisUtils.delete(captchaKey); | ||||||
|         userService.updatePhone(updateReq.getPhone(), rawOldPassword, LoginHelper.getUserId()); |         userService.updatePhone(updateReq.getPhone(), rawOldPassword, UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Operation(summary = "修改邮箱", description = "修改用户邮箱") |     @Operation(summary = "修改邮箱", description = "修改用户邮箱") | ||||||
| @@ -120,13 +120,13 @@ public class UserCenterController { | |||||||
|         ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); |         ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); | ||||||
|         ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); |         ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); | ||||||
|         RedisUtils.delete(captchaKey); |         RedisUtils.delete(captchaKey); | ||||||
|         userService.updateEmail(updateReq.getEmail(), rawOldPassword, LoginHelper.getUserId()); |         userService.updateEmail(updateReq.getEmail(), rawOldPassword, UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Operation(summary = "查询绑定的三方账号", description = "查询绑定的三方账号") |     @Operation(summary = "查询绑定的三方账号", description = "查询绑定的三方账号") | ||||||
|     @GetMapping("/social") |     @GetMapping("/social") | ||||||
|     public List<UserSocialBindResp> listSocialBind() { |     public List<UserSocialBindResp> listSocialBind() { | ||||||
|         List<UserSocialDO> userSocialList = userSocialService.listByUserId(LoginHelper.getUserId()); |         List<UserSocialDO> userSocialList = userSocialService.listByUserId(UserContextHolder.getUserId()); | ||||||
|         return userSocialList.stream().map(userSocial -> { |         return userSocialList.stream().map(userSocial -> { | ||||||
|             String source = userSocial.getSource(); |             String source = userSocial.getSource(); | ||||||
|             UserSocialBindResp userSocialBind = new UserSocialBindResp(); |             UserSocialBindResp userSocialBind = new UserSocialBindResp(); | ||||||
| @@ -144,13 +144,13 @@ public class UserCenterController { | |||||||
|         AuthResponse<AuthUser> response = authRequest.login(callback); |         AuthResponse<AuthUser> response = authRequest.login(callback); | ||||||
|         ValidationUtils.throwIf(!response.ok(), response.getMsg()); |         ValidationUtils.throwIf(!response.ok(), response.getMsg()); | ||||||
|         AuthUser authUser = response.getData(); |         AuthUser authUser = response.getData(); | ||||||
|         userSocialService.bind(authUser, LoginHelper.getUserId()); |         userSocialService.bind(authUser, UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Operation(summary = "解绑三方账号", description = "解绑三方账号") |     @Operation(summary = "解绑三方账号", description = "解绑三方账号") | ||||||
|     @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH) |     @Parameter(name = "source", description = "来源", example = "gitee", in = ParameterIn.PATH) | ||||||
|     @DeleteMapping("/social/{source}") |     @DeleteMapping("/social/{source}") | ||||||
|     public void unbindSocial(@PathVariable String source) { |     public void unbindSocial(@PathVariable String source) { | ||||||
|         userSocialService.deleteBySourceAndUserId(source, LoginHelper.getUserId()); |         userSocialService.deleteBySourceAndUserId(source, UserContextHolder.getUserId()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user