refactor: 重构获取登录用户信息方式(线程级存储)

This commit is contained in:
2024-10-10 22:33:20 +08:00
parent 8466105a9b
commit 79ea39dd07
27 changed files with 523 additions and 363 deletions

View File

@@ -18,7 +18,6 @@ package top.continew.admin.auth.service;
import top.continew.admin.auth.model.query.OnlineUserQuery;
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.resp.PageResp;
@@ -48,7 +47,7 @@ public interface OnlineUserService {
* @param query 查询条件
* @return 列表信息
*/
List<LoginUser> list(OnlineUserQuery query);
List<OnlineUserResp> list(OnlineUserQuery query);
/**
* 查询 Token 最后活跃时间

View File

@@ -16,6 +16,7 @@
package top.continew.admin.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
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.RegexConstants;
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.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.MessageTemplateEnum;
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.util.TreeUtils;
import top.continew.starter.messaging.websocket.util.WebSocketUtils;
import top.continew.starter.web.util.SpringWebUtils;
import java.time.Duration;
import java.time.LocalDateTime;
@@ -205,15 +208,19 @@ public class LoginServiceImpl implements LoginService {
Long userId = user.getId();
CompletableFuture<Set<String>> permissionFuture = CompletableFuture.supplyAsync(() -> roleService
.listPermissionByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Set<RoleDTO>> roleFuture = CompletableFuture.supplyAsync(() -> roleService
CompletableFuture<Set<RoleContext>> roleFuture = CompletableFuture.supplyAsync(() -> roleService
.listByUserId(userId), threadPoolTaskExecutor);
CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture);
LoginUser loginUser = new LoginUser(permissionFuture.join(), roleFuture.join(), passwordExpirationDaysFuture
.join());
BeanUtil.copyProperties(user, loginUser);
return LoginHelper.login(loginUser);
UserContext userContext = new UserContext(permissionFuture.join(), roleFuture
.join(), passwordExpirationDaysFuture.join());
BeanUtil.copyProperties(user, userContext);
// 登录并缓存用户信息
StpUtil.login(userContext.getId(), SaLoginConfig.setExtraData(BeanUtil
.beanToMap(new UserExtraContext(SpringWebUtils.getRequest()))));
UserContextHolder.setContext(userContext);
return StpUtil.getTokenValue();
}
/**

View File

@@ -21,6 +21,7 @@ import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
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.resp.OnlineUserResp;
import top.continew.admin.auth.service.OnlineUserService;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.common.context.UserContext;
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.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
* 在线用户业务实现
@@ -53,31 +53,43 @@ public class OnlineUserServiceImpl implements OnlineUserService {
@Override
@AutoOperate(type = OnlineUserResp.class, on = "list")
public PageResp<OnlineUserResp> page(OnlineUserQuery query, PageQuery pageQuery) {
List<LoginUser> loginUserList = this.list(query);
List<OnlineUserResp> list = BeanUtil.copyToList(loginUserList, OnlineUserResp.class);
List<OnlineUserResp> list = this.list(query);
return PageResp.build(pageQuery.getPage(), pageQuery.getSize(), list);
}
@Override
public List<LoginUser> list(OnlineUserQuery query) {
List<LoginUser> loginUserList = new ArrayList<>();
// 查询所有登录用户
public List<OnlineUserResp> list(OnlineUserQuery query) {
List<OnlineUserResp> list = new ArrayList<>();
// 查询所有在线 Token
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);
// 忽略已过期或失效 Token
if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < SaTokenDao.NEVER_EXPIRE) {
return;
return StpUtil.getStpLogic().getTokenActiveTimeoutByToken(token) >= SaTokenDao.NEVER_EXPIRE;
})
.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;
}
// 检查是否符合查询条件
LoginUser loginUser = LoginHelper.getLoginUser(token);
if (this.isMatchQuery(query, loginUser)) {
loginUserList.add(loginUser);
}
});
List<Date> loginTimeList = query.getLoginTime();
entry.getValue().parallelStream().forEach(token -> {
UserExtraContext extraContext = UserContextHolder.getExtraContext(token);
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());
return loginUserList;
CollUtil.sort(list, Comparator.comparing(OnlineUserResp::getLoginTime).reversed());
return list;
}
@Override
@@ -95,35 +107,31 @@ public class OnlineUserServiceImpl implements OnlineUserService {
}
/**
* 是否符合查询条件
* 是否匹配昵称
*
* @param query 查询条件
* @param loginUser 登录用户信息
* @return 是否符合查询条件
* @param nickname 昵称
* @param userContext 用户上下文信息
* @return 是否匹配昵称
*/
private boolean isMatchQuery(OnlineUserQuery query, LoginUser loginUser) {
boolean flag1 = true;
String nickname = query.getNickname();
if (StrUtil.isNotBlank(nickname)) {
flag1 = StrUtil.contains(loginUser.getUsername(), nickname) || StrUtil.contains(LoginHelper
.getNickname(loginUser.getId()), nickname);
private boolean isMatchNickname(String nickname, UserContext userContext) {
if (StrUtil.isBlank(nickname)) {
return true;
}
boolean flag2 = true;
List<Date> loginTime = query.getLoginTime();
if (CollUtil.isNotEmpty(loginTime)) {
flag2 = DateUtil.isIn(DateUtil.date(loginUser.getLoginTime()).toJdkDate(), loginTime.get(0), loginTime
.get(1));
return StrUtil.contains(userContext.getUsername(), nickname) || StrUtil.contains(UserContextHolder
.getNickname(userContext.getId()), nickname);
}
/**
* 是否匹配登录时间
*
* @param loginTimeList 登录时间列表
* @param loginTime 登录时间
* @return 是否匹配登录时间
*/
private boolean isMatchLoginTime(List<Date> loginTimeList, LocalDateTime loginTime) {
if (CollUtil.isEmpty(loginTimeList)) {
return true;
}
boolean flag3 = true;
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;
return DateUtil.isIn(DateUtil.date(loginTime).toJdkDate(), loginTimeList.get(0), loginTimeList.get(1));
}
}

View File

@@ -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.upload.FilePartInfo;
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.mapper.FileMapper;
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));
file.setStorageId(storage.getId());
file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
file.setUpdateUser(LoginHelper.getUserId());
file.setUpdateUser(UserContextHolder.getUserId());
file.setUpdateTime(file.getCreateTime());
fileMapper.insert(file);
return true;

View File

@@ -16,13 +16,9 @@
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.starter.data.mp.base.BaseMapper;
import java.util.List;
/**
* 用户和角色 Mapper
*
@@ -30,13 +26,4 @@ import java.util.List;
* @since 2023/2/13 23:13
*/
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);
}

View File

@@ -27,12 +27,12 @@ import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
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.GenderEnum;
import top.continew.admin.common.util.helper.LoginHelper;
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.file.excel.converter.ExcelBaseEnumConverter;
import top.continew.starter.file.excel.converter.ExcelListConverter;
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
@@ -160,6 +160,6 @@ public class UserDetailResp extends BaseDetailResp {
@Override
public Boolean getDisabled() {
return this.getIsSystem() || Objects.equals(this.getId(), LoginHelper.getUserId());
return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId());
}
}

View File

@@ -21,9 +21,9 @@ import cn.crane4j.core.executor.handler.ManyToManyAssembleOperationHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
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.GenderEnum;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.starter.extension.crud.model.resp.BaseDetailResp;
import top.continew.starter.security.mask.annotation.JsonMask;
import top.continew.starter.security.mask.enums.MaskType;
@@ -129,6 +129,6 @@ public class UserResp extends BaseDetailResp {
@Override
public Boolean getDisabled() {
return this.getIsSystem() || Objects.equals(this.getId(), LoginHelper.getUserId());
return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId());
}
}

View File

@@ -16,7 +16,7 @@
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.query.RoleQuery;
import top.continew.admin.system.model.req.RoleReq;
@@ -66,7 +66,7 @@ public interface RoleService extends BaseService<RoleResp, RoleDetailResp, RoleQ
* @param userId 用户 ID
* @return 角色集合
*/
Set<RoleDTO> listByUserId(Long userId);
Set<RoleContext> listByUserId(Long userId);
/**
* 根据角色编码查询

View File

@@ -59,6 +59,14 @@ public interface UserRoleService {
*/
List<Long> listRoleIdByUserId(Long userId);
/**
* 根据角色 ID 查询
*
* @param roleId 角色 ID
* @return 用户 ID 列表
*/
List<Long> listUserIdByRoleId(Long roleId);
/**
* 根据角色 ID 判断是否已被用户关联
*

View File

@@ -26,15 +26,14 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
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.common.constant.CacheConstants;
import top.continew.admin.common.constant.ContainerConstants;
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.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.model.entity.RoleDO;
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);
// 如果功能权限或数据权限有变更,则更新在线用户权限信息
if (isSaveMenuSuccess || isSaveDeptSuccess || ObjectUtil.notEqual(req.getDataScope(), oldDataScope)) {
OnlineUserQuery query = new OnlineUserQuery();
query.setRoleId(id);
List<LoginUser> loginUserList = onlineUserService.list(query);
loginUserList.forEach(loginUser -> {
loginUser.setRoles(this.listByUserId(loginUser.getId()));
loginUser.setPermissions(this.listPermissionByUserId(loginUser.getId()));
LoginHelper.updateLoginUser(loginUser, loginUser.getToken());
List<Long> userIdList = userRoleService.listUserIdByRoleId(id);
userIdList.parallelStream().forEach(userId -> {
UserContext userContext = UserContextHolder.getContext(userId);
if (null != userContext) {
userContext.setRoles(this.listByUserId(userId));
userContext.setPermissions(this.listPermissionByUserId(userId));
UserContextHolder.setContext(userContext);
}
});
}
}
@@ -171,10 +171,10 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleRes
}
@Override
public Set<RoleDTO> listByUserId(Long userId) {
public Set<RoleContext> listByUserId(Long userId) {
List<Long> roleIdList = userRoleService.listRoleIdByUserId(userId);
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

View File

@@ -75,7 +75,24 @@ public class UserRoleServiceImpl implements UserRoleService {
@Override
@ContainerMethod(namespace = ContainerConstants.USER_ROLE_ID_LIST, type = MappingType.ORDER_OF_KEYS)
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

View File

@@ -52,15 +52,14 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
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.common.constant.CacheConstants;
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.GenderEnum;
import top.continew.admin.common.model.dto.LoginUser;
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.model.entity.DeptDO;
import top.continew.admin.system.model.entity.RoleDO;
@@ -310,7 +309,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
String phone = req.getPhone();
CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, id), errorMsgTemplate, phone);
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()), "不允许禁用当前用户");
UserDO oldUser = super.getById(id);
if (Boolean.TRUE.equals(oldUser.getIsSystem())) {
@@ -333,14 +332,12 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
}
// 如果角色有变更,则更新在线用户权限信息
if (isSaveUserRoleSuccess) {
OnlineUserQuery query = new OnlineUserQuery();
query.setUserId(id);
List<LoginUser> loginUserList = onlineUserService.list(query);
loginUserList.forEach(loginUser -> {
loginUser.setRoles(roleService.listByUserId(loginUser.getId()));
loginUser.setPermissions(roleService.listPermissionByUserId(loginUser.getId()));
LoginHelper.updateLoginUser(loginUser, loginUser.getToken());
});
UserContext userContext = UserContextHolder.getContext(id);
if (null != userContext) {
userContext.setRoles(roleService.listByUserId(id));
userContext.setPermissions(roleService.listPermissionByUserId(id));
UserContextHolder.setContext(userContext);
}
}
}
@@ -348,7 +345,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
@Transactional(rollbackFor = Exception.class)
@CacheInvalidate(key = "#ids", name = CacheConstants.USER_KEY_PREFIX, multi = true)
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()
.select(UserDO::getNickname, UserDO::getIsSystem)
.in(UserDO::getId, ids)