优化:优化修改角色的代码逻辑

1.变更角色编码、功能权限或数据权限后,关联在线用户会自动下线
2.优化角色和菜单关联、角色和部门关联、用户和角色关联的业务代码(增加返回结果)
3.重构在线用户功能,抽取在线用户业务实现
This commit is contained in:
Charles7c 2023-03-26 00:14:05 +08:00
parent c5b748fe52
commit 267ad9be13
14 changed files with 260 additions and 84 deletions

View File

@ -73,7 +73,7 @@ public class LoginHelper {
/**
* 获取登录用户信息
*
* @return /
* @return 登录用户信息
*/
public static LoginUser getLoginUser() {
LoginUser loginUser = (LoginUser)SaHolder.getStorage().get(CacheConsts.LOGIN_USER_KEY);
@ -85,6 +85,17 @@ public class LoginHelper {
return loginUser;
}
/**
* 根据 Token 获取登录用户信息
*
* @param token
* 用户 Token
* @return 登录用户信息
*/
public static LoginUser getLoginUser(String token) {
return StpUtil.getTokenSessionByToken(token).get(CacheConsts.LOGIN_USER_KEY, new LoginUser());
}
/**
* 更新登录用户信息
*

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.charles7c.cnadmin.monitor.model.query;
package top.charles7c.cnadmin.auth.model.query;
import java.io.Serializable;
import java.util.Date;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.charles7c.cnadmin.monitor.model.vo;
package top.charles7c.cnadmin.auth.model.vo;
import java.io.Serializable;
import java.time.LocalDateTime;

View File

@ -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.charles7c.cnadmin.auth.service;
import java.util.List;
import top.charles7c.cnadmin.auth.model.query.OnlineUserQuery;
import top.charles7c.cnadmin.auth.model.vo.OnlineUserVO;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.model.query.PageQuery;
import top.charles7c.cnadmin.common.model.vo.PageDataVO;
/**
* 在线用户业务接口
*
* @author Charles7c
* @since 2023/3/25 22:48
*/
public interface OnlineUserService {
/**
* 分页查询列表
*
* @param query
* 查询条件
* @param pageQuery
* 分页查询条件
* @return 分页列表信息
*/
PageDataVO<OnlineUserVO> page(OnlineUserQuery query, PageQuery pageQuery);
/**
* 查询列表
*
* @param query
* 查询条件
* @return 列表信息
*/
List<LoginUser> list(OnlineUserQuery query);
/**
* 根据角色 ID 清除
*
* @param roleId
* 角色 ID
*/
void cleanByRoleId(Long roleId);
}

View File

@ -0,0 +1,123 @@
/*
* 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.charles7c.cnadmin.auth.service.impl;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.auth.model.query.OnlineUserQuery;
import top.charles7c.cnadmin.auth.model.vo.OnlineUserVO;
import top.charles7c.cnadmin.auth.service.OnlineUserService;
import top.charles7c.cnadmin.common.constant.StringConsts;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.model.query.PageQuery;
import top.charles7c.cnadmin.common.model.vo.PageDataVO;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
/**
* 在线用户业务实现类
*
* @author Charles7c
* @author Lion LiRuoYi-Vue-Plus
* @since 2023/3/25 22:49
*/
@Service
@RequiredArgsConstructor
public class OnlineUserServiceImpl implements OnlineUserService {
@Override
public PageDataVO<OnlineUserVO> page(OnlineUserQuery query, PageQuery pageQuery) {
List<LoginUser> loginUserList = this.list(query);
List<OnlineUserVO> list = BeanUtil.copyToList(loginUserList, OnlineUserVO.class);
return PageDataVO.build(pageQuery.getPage(), pageQuery.getSize(), list);
}
@Override
public List<LoginUser> list(OnlineUserQuery query) {
List<LoginUser> loginUserList = new ArrayList<>();
// 查询所有登录用户
List<String> tokenKeyList = StpUtil.searchTokenValue(StringConsts.EMPTY, 0, -1, false);
for (String tokenKey : tokenKeyList) {
String token = StrUtil.subAfter(tokenKey, StringConsts.COLON, true);
// 忽略已过期或失效 Token
if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < SaTokenDao.NEVER_EXPIRE) {
continue;
}
// 检查是否符合查询条件
LoginUser loginUser = LoginHelper.getLoginUser(token);
if (this.checkQuery(query, loginUser)) {
loginUserList.add(loginUser);
}
}
// 设置排序
CollUtil.sort(loginUserList, Comparator.comparing(LoginUser::getLoginTime).reversed());
return loginUserList;
}
@Override
public void cleanByRoleId(Long roleId) {
List<LoginUser> loginUserList = this.list(new OnlineUserQuery());
loginUserList.parallelStream().forEach(u -> {
if (u.getRoleSet().stream().anyMatch(r -> r.getId().equals(roleId))) {
try {
StpUtil.logoutByTokenValue(u.getToken());
} catch (NotLoginException ignored) {
}
}
});
}
/**
* 检查是否符合查询条件
*
* @param query
* 查询条件
* @param loginUser
* 登录用户信息
* @return 是否符合查询条件
*/
private boolean checkQuery(OnlineUserQuery query, LoginUser loginUser) {
boolean flag1 = true;
String nickname = query.getNickname();
if (StrUtil.isNotBlank(nickname)) {
flag1 = StrUtil.contains(loginUser.getUsername(), nickname)
|| StrUtil.contains(loginUser.getNickname(), nickname);
}
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 flag1 && flag2;
}
}

View File

@ -33,8 +33,9 @@ public interface RoleDeptService {
* 部门 ID 列表
* @param roleId
* 角色 ID
* @return true成功false无变更/失败
*/
void save(List<Long> deptIds, Long roleId);
boolean save(List<Long> deptIds, Long roleId);
/**
* 根据角色 ID 查询

View File

@ -33,8 +33,9 @@ public interface RoleMenuService {
* 菜单 ID 列表
* @param roleId
* 角色 ID
* @return true成功false无变更/失败
*/
void save(List<Long> menuIds, Long roleId);
boolean save(List<Long> menuIds, Long roleId);
/**
* 根据角色 ID 查询

View File

@ -33,8 +33,9 @@ public interface UserRoleService {
* 角色 ID 列表
* @param userId
* 用户 ID
* @return true成功false无变更/失败
*/
void save(List<Long> roleIds, Long userId);
boolean save(List<Long> roleIds, Long userId);
/**
* 根据角色 ID 列表查询

View File

@ -23,6 +23,8 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import cn.hutool.core.collection.CollUtil;
import top.charles7c.cnadmin.system.mapper.RoleDeptMapper;
import top.charles7c.cnadmin.system.model.entity.RoleDeptDO;
import top.charles7c.cnadmin.system.service.RoleDeptService;
@ -40,13 +42,19 @@ public class RoleDeptServiceImpl implements RoleDeptService {
private final RoleDeptMapper roleDeptMapper;
@Override
public void save(List<Long> deptIds, Long roleId) {
public boolean save(List<Long> deptIds, Long roleId) {
// 检查是否有变更
List<Long> oldDeptIdList = roleDeptMapper.lambdaQuery().select(RoleDeptDO::getDeptId)
.eq(RoleDeptDO::getRoleId, roleId).list().stream().map(RoleDeptDO::getDeptId).collect(Collectors.toList());
if (CollUtil.isEmpty(CollUtil.disjunction(deptIds, oldDeptIdList))) {
return false;
}
// 删除原有关联
roleDeptMapper.lambdaUpdate().eq(RoleDeptDO::getRoleId, roleId).remove();
// 保存最新关联
List<RoleDeptDO> roleDeptList =
deptIds.stream().map(deptId -> new RoleDeptDO(roleId, deptId)).collect(Collectors.toList());
roleDeptMapper.insertBatch(roleDeptList);
return roleDeptMapper.insertBatch(roleDeptList);
}
@Override

View File

@ -43,13 +43,19 @@ public class RoleMenuServiceImpl implements RoleMenuService {
private final RoleMenuMapper roleMenuMapper;
@Override
public void save(List<Long> menuIds, Long roleId) {
public boolean save(List<Long> menuIds, Long roleId) {
// 检查是否有变更
List<Long> oldMenuIdList = roleMenuMapper.lambdaQuery().select(RoleMenuDO::getMenuId)
.eq(RoleMenuDO::getRoleId, roleId).list().stream().map(RoleMenuDO::getMenuId).collect(Collectors.toList());
if (CollUtil.isEmpty(CollUtil.disjunction(menuIds, oldMenuIdList))) {
return false;
}
// 删除原有关联
roleMenuMapper.lambdaUpdate().eq(RoleMenuDO::getRoleId, roleId).remove();
// 保存最新关联
List<RoleMenuDO> roleMenuList =
menuIds.stream().map(menuId -> new RoleMenuDO(roleId, menuId)).collect(Collectors.toList());
roleMenuMapper.insertBatch(roleMenuList);
return roleMenuMapper.insertBatch(roleMenuList);
}
@Override

View File

@ -26,9 +26,12 @@ import org.springframework.transaction.annotation.Transactional;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import top.charles7c.cnadmin.auth.service.OnlineUserService;
import top.charles7c.cnadmin.common.base.BaseServiceImpl;
import top.charles7c.cnadmin.common.constant.SysConsts;
import top.charles7c.cnadmin.common.enums.DataScopeEnum;
import top.charles7c.cnadmin.common.enums.DataTypeEnum;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
import top.charles7c.cnadmin.common.model.dto.RoleDTO;
@ -54,9 +57,10 @@ import top.charles7c.cnadmin.system.service.*;
public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO, RoleDetailVO, RoleQuery, RoleRequest>
implements RoleService {
private final MenuService menuService;
private final OnlineUserService onlineUserService;
private final RoleMenuService roleMenuService;
private final RoleDeptService roleDeptService;
private final MenuService menuService;
private final UserRoleService userRoleService;
@Override
@ -85,22 +89,30 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO,
String code = request.getCode();
CheckUtils.throwIf(this.checkCodeExists(code, id), "修改失败,[{}] 已存在", code);
RoleDO oldRole = super.getById(id);
DataScopeEnum oldDataScope = oldRole.getDataScope();
String oldCode = oldRole.getCode();
if (DataTypeEnum.SYSTEM.equals(oldRole.getType())) {
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, request.getStatus(), "[{}] 是系统内置角色,不允许禁用",
oldRole.getName());
CheckUtils.throwIfNotEqual(request.getCode(), oldRole.getCode(), "[{}] 是系统内置角色,不允许修改角色编码",
oldRole.getName());
CheckUtils.throwIfNotEqual(request.getDataScope(), oldRole.getDataScope(), "[{}] 是系统内置角色,不允许修改角色数据权限",
CheckUtils.throwIfNotEqual(request.getCode(), oldCode, "[{}] 是系统内置角色,不允许修改角色编码", oldRole.getName());
CheckUtils.throwIfNotEqual(request.getDataScope(), oldDataScope, "[{}] 是系统内置角色,不允许修改角色数据权限",
oldRole.getName());
}
// 更新信息
super.update(request, id);
// 更新关联信息
if (!SysConsts.ADMIN_ROLE_CODE.equals(oldRole.getCode())) {
// 保存角色和菜单关联
roleMenuService.save(request.getMenuIds(), id);
boolean isSaveMenuSuccess = roleMenuService.save(request.getMenuIds(), id);
// 保存角色和部门关联
roleDeptService.save(request.getDeptIds(), id);
boolean isSaveDeptSuccess = roleDeptService.save(request.getDeptIds(), id);
// 如果角色编码功能权限或数据权限有变更则清除关联的在线用户重新登录以获取最新角色权限
if (ObjectUtil.notEqual(request.getCode(), oldCode)
|| ObjectUtil.notEqual(request.getDataScope(), oldDataScope) || isSaveMenuSuccess
|| isSaveDeptSuccess) {
onlineUserService.cleanByRoleId(id);
}
}
}

View File

@ -23,6 +23,8 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import cn.hutool.core.collection.CollUtil;
import top.charles7c.cnadmin.system.mapper.UserRoleMapper;
import top.charles7c.cnadmin.system.model.entity.UserRoleDO;
import top.charles7c.cnadmin.system.service.UserRoleService;
@ -40,13 +42,19 @@ public class UserRoleServiceImpl implements UserRoleService {
private final UserRoleMapper userRoleMapper;
@Override
public void save(List<Long> roleIds, Long userId) {
public boolean save(List<Long> roleIds, Long userId) {
// 检查是否有变更
List<Long> oldRoleIdList = userRoleMapper.lambdaQuery().select(UserRoleDO::getRoleId)
.eq(UserRoleDO::getUserId, userId).list().stream().map(UserRoleDO::getRoleId).collect(Collectors.toList());
if (CollUtil.isEmpty(CollUtil.disjunction(roleIds, oldRoleIdList))) {
return false;
}
// 删除原有关联
userRoleMapper.lambdaUpdate().eq(UserRoleDO::getUserId, userId).remove();
// 保存最新关联
List<UserRoleDO> userRoleList =
roleIds.stream().map(roleId -> new UserRoleDO(userId, roleId)).collect(Collectors.toList());
userRoleMapper.insertBatch(userRoleList);
return userRoleMapper.insertBatch(userRoleList);
}
@Override

View File

@ -204,6 +204,9 @@
@cancel="handleCancel"
>
<a-form ref="formRef" :model="form" :rules="rules" size="large">
<a-alert v-if="!form.disabled" type="warning" style="margin-bottom: 15px;">
变更角色编码功能权限或数据权限后关联在线用户会自动下线
</a-alert>
<fieldset>
<legend>基础信息</legend>
<a-form-item label="角色名称" field="name">

View File

@ -16,11 +16,6 @@
package top.charles7c.cnadmin.webapi.controller.monitor;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import lombok.RequiredArgsConstructor;
import io.swagger.v3.oas.annotations.Operation;
@ -30,28 +25,19 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.constant.CacheConsts;
import top.charles7c.cnadmin.common.constant.StringConsts;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.auth.model.query.OnlineUserQuery;
import top.charles7c.cnadmin.auth.model.vo.OnlineUserVO;
import top.charles7c.cnadmin.auth.service.OnlineUserService;
import top.charles7c.cnadmin.common.model.query.PageQuery;
import top.charles7c.cnadmin.common.model.vo.PageDataVO;
import top.charles7c.cnadmin.common.model.vo.R;
import top.charles7c.cnadmin.common.util.validate.CheckUtils;
import top.charles7c.cnadmin.monitor.model.query.OnlineUserQuery;
import top.charles7c.cnadmin.monitor.model.vo.*;
/**
* 在线用户 API
*
* @author Lion LiRuoYi-Vue-Plus
* @author Charles7c
* @since 2023/1/20 21:51
*/
@ -61,59 +47,13 @@ import top.charles7c.cnadmin.monitor.model.vo.*;
@RequestMapping("/monitor/online/user")
public class OnlineUserController {
private final OnlineUserService onlineUserService;
@Operation(summary = "分页查询列表")
@SaCheckPermission("monitor:online:user:list")
@GetMapping
public R<PageDataVO<OnlineUserVO>> page(@Validated OnlineUserQuery query, @Validated PageQuery pageQuery) {
List<LoginUser> loginUserList = new ArrayList<>();
List<String> tokenKeyList = StpUtil.searchTokenValue(StringConsts.EMPTY, 0, -1, false);
for (String tokenKey : tokenKeyList) {
String token = StrUtil.subAfter(tokenKey, StringConsts.COLON, true);
// 忽略已过期或失效 Token
if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < SaTokenDao.NEVER_EXPIRE) {
continue;
}
// 获取 Token Session
SaSession saSession = StpUtil.getTokenSessionByToken(token);
LoginUser loginUser = saSession.get(CacheConsts.LOGIN_USER_KEY, new LoginUser());
// 检查是否符合查询条件
if (Boolean.TRUE.equals(checkQuery(query, loginUser))) {
loginUserList.add(loginUser);
}
}
// 构建分页数据
List<OnlineUserVO> list = BeanUtil.copyToList(loginUserList, OnlineUserVO.class);
CollUtil.sort(list, Comparator.comparing(OnlineUserVO::getLoginTime).reversed());
PageDataVO<OnlineUserVO> pageDataVO = PageDataVO.build(pageQuery.getPage(), pageQuery.getSize(), list);
return R.ok(pageDataVO);
}
/**
* 检查是否符合查询条件
*
* @param query
* 查询条件
* @param loginUser
* 登录用户信息
* @return 是否符合查询条件
*/
private boolean checkQuery(OnlineUserQuery query, LoginUser loginUser) {
boolean flag1 = true;
String nickname = query.getNickname();
if (StrUtil.isNotBlank(nickname)) {
flag1 = loginUser.getUsername().contains(nickname) || loginUser.getNickname().contains(nickname);
}
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 flag1 && flag2;
return R.ok(onlineUserService.page(query, pageQuery));
}
@Operation(summary = "强退在线用户")