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

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

View File

@@ -0,0 +1,55 @@
/*
* 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.model.query;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 在线用户查询条件
*
* @author Charles7c
* @since 2023/1/20 23:07
*/
@Data
@ParameterObject
@Schema(description = "在线用户查询条件")
public class OnlineUserQuery implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户昵称
*/
@Schema(description = "用户昵称")
private String nickname;
/**
* 登录时间
*/
@Schema(description = "登录时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private List<Date> loginTime;
}

View File

@@ -0,0 +1,79 @@
/*
* 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.model.vo;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 在线用户信息
*
* @author Charles7c
* @since 2023/1/20 21:54
*/
@Data
@Schema(description = "在线用户信息")
public class OnlineUserVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 令牌
*/
@Schema(description = "令牌")
private String token;
/**
* 用户名
*/
@Schema(description = "用户名")
private String username;
/**
* 昵称
*/
@Schema(description = "昵称")
private String nickname;
/**
* 登录 IP
*/
@Schema(description = "登录 IP")
private String clientIp;
/**
* 登录地点
*/
@Schema(description = "登录地点")
private String location;
/**
* 浏览器
*/
@Schema(description = "浏览器")
private String browser;
/**
* 登录时间
*/
@Schema(description = "登录时间")
private LocalDateTime loginTime;
}

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