refactor(tenant): 优化租户相关代码

This commit is contained in:
2025-07-15 20:09:54 +08:00
parent ed6dd65a51
commit af1079da6d
111 changed files with 2741 additions and 2319 deletions

View File

@@ -123,5 +123,4 @@ public class AuthServiceImpl implements AuthService {
});
return BeanUtil.copyToList(treeList, RouteResp.class);
}
}

View File

@@ -29,7 +29,7 @@ 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.config.properties.TenantProperties;
import top.continew.admin.common.config.TenantProperties;
import top.continew.admin.common.context.UserContext;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.admin.common.context.UserExtraContext;

View File

@@ -27,7 +27,7 @@ import java.util.List;
* 数据导入策略
*
* @author Kils
* @since 2024-06-17 18:33
* @since 2024/6/17 18:33
*/
@Getter
@RequiredArgsConstructor

View File

@@ -25,7 +25,7 @@ import top.continew.starter.data.mapper.BaseMapper;
import java.util.List;
/**
* 角色和部门 Mapper
* 角色和部门关联 Mapper
*
* @author Charles7c
* @since 2023/2/18 21:57

View File

@@ -16,8 +16,6 @@
package top.continew.admin.system.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import top.continew.admin.common.constant.SysConstants;
import org.apache.ibatis.annotations.Mapper;
import top.continew.admin.system.model.entity.StorageDO;
import top.continew.starter.data.mapper.BaseMapper;
@@ -28,7 +26,6 @@ import top.continew.starter.data.mapper.BaseMapper;
* @author Charles7c
* @since 2023/12/26 22:09
*/
@DS(SysConstants.DEFAULT_DATASOURCE)
@Mapper
public interface StorageMapper extends BaseMapper<StorageDO> {
}

View File

@@ -24,7 +24,7 @@ import java.io.Serial;
import java.io.Serializable;
/**
* 角色和部门实体
* 角色和部门关联实体
*
* @author Charles7c
* @since 2023/2/18 21:57

View File

@@ -59,10 +59,9 @@ public class MenuQuery implements Serializable {
}
/**
* 排除的菜单
* 排除的菜单 ID 列表
*/
@Schema(description = "排除的菜单")
@Schema(hidden = true, description = "菜单 ID 列表", example = "[9000]")
@Query(columns = "id", type = QueryType.NOT_IN)
private List<Long> excludeMenuIdList;
}

View File

@@ -30,7 +30,7 @@ import java.io.Serializable;
* 用户导入请求参数
*
* @author Kils
* @since 2024-6-17 16:42
* @since 2024/6/17 16:42
*/
@Data
@Schema(description = "用户导入请求参数")

View File

@@ -33,7 +33,7 @@ import java.io.Serializable;
* 用户导入行数据请求参数
*
* @author Kils
* @since 2024-6-17 16:42
* @since 2024/6/17 16:42
*/
@Data
@Schema(description = "用户导入行数据请求参数")

View File

@@ -66,7 +66,7 @@ public class DeptResp extends BaseDetailResp {
/**
* 排序
*/
@Schema(description = "排序", example = "3")
@Schema(description = "排序", example = "1")
@ExcelProperty(value = "排序", order = 6)
private Integer sort;

View File

@@ -28,7 +28,7 @@ import java.io.Serializable;
* 用户导入结果响应参数
*
* @author kils
* @since 2024-06-18 14:37
* @since 2024/6/18 14:37
*/
@Data
@Schema(description = "用户导入结果响应参数")

View File

@@ -56,12 +56,4 @@ public interface DeptService extends BaseService<DeptResp, DeptResp, DeptQuery,
* @return 部门数量
*/
int countByNames(List<String> deptNames);
/**
* 初始化租户部门
*
* @param deptName
* @return 部门ID
*/
Long initTenantDept(String deptName);
}

View File

@@ -34,13 +34,6 @@ import java.util.Set;
*/
public interface MenuService extends BaseService<MenuResp, MenuResp, MenuQuery, MenuReq>, IService<MenuDO> {
/**
* 查询全部菜单
*
* @return 菜单列表
*/
List<MenuResp> listAll(Long tenantId);
/**
* 根据用户 ID 查询
*
@@ -50,35 +43,25 @@ public interface MenuService extends BaseService<MenuResp, MenuResp, MenuQuery,
Set<String> listPermissionByUserId(Long userId);
/**
* 根据角色id查询
* 根据角色 ID 查询
*
* @param roleId 角色id
* @param roleId 角色 ID
* @return 菜单列表
*/
List<MenuResp> listByRoleId(Long roleId);
/**
* 递归初始化菜单
*
* @param menuList 需要初始化的菜单ID
* @param oldParentId 原来的父级ID
* @param newParentId 新的父级ID
*/
void menuInit(List<MenuDO> menuList, Long oldParentId, Long newParentId);
/**
* 删除租户菜单
*
* @param menuList
* @param menuS 菜单列表
*/
void deleteTenantMenus(List<MenuDO> menuList);
void deleteTenantMenus(List<MenuDO> menuS);
/**
* 新增租户菜单
*
* @param menu 新增的菜单
* @param pMenu 新增菜单的父级别
* @param menu 新增的菜单
* @param parentMenu 父菜单
*/
void addTenantMenu(MenuDO menu, MenuDO pMenu);
void addTenantMenu(MenuDO menu, MenuDO parentMenu);
}

View File

@@ -19,7 +19,7 @@ package top.continew.admin.system.service;
import java.util.List;
/**
* 角色和部门业务接口
* 角色和部门关联业务接口
*
* @author Charles7c
* @since 2023/2/19 10:40

View File

@@ -16,8 +16,8 @@
package top.continew.admin.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import top.continew.admin.system.model.entity.RoleMenuDO;
import top.continew.starter.data.service.IService;
import java.util.List;

View File

@@ -100,12 +100,4 @@ public interface RoleService extends BaseService<RoleResp, RoleDetailResp, RoleQ
* @return 角色数量
*/
int countByNames(List<String> roleNames);
/**
* 初始化租户角色
*
* @return 角色ID
*/
Long initTenantRole();
}

View File

@@ -1,31 +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.system.service;
/**
* @description: 多租户系统数据接口
* @author: 小熊
* @create: 2024-12-02 20:08
*/
public interface TenantSysDataService {
/**
* 清除所有系统数据
*/
void clear();
}

View File

@@ -155,13 +155,4 @@ public interface UserService extends BaseService<UserResp, UserDetailResp, UserQ
* @return 用户数量
*/
Long countByDeptIds(List<Long> deptIds);
/**
* 初始化租户管理员
*
* @param username
* @param password
* @return 管理员id
*/
Long initTenantUser(String username, String password, Long deptId);
}

View File

@@ -63,7 +63,7 @@ public class ClientServiceImpl extends BaseServiceImpl<ClientMapper, ClientDO, C
for (Long id : ids) {
ClientDO client = this.getById(id);
query.setClientId(client.getClientId());
CheckUtils.throwIfNotEmpty(onlineUserService.list(query), "客户端 [{}] 还存在在线用户,不删除", client.getClientId());
CheckUtils.throwIfNotEmpty(onlineUserService.list(query), "客户端 [{}] 还存在在线用户,不允许删除", client.getClientId());
}
}

View File

@@ -214,25 +214,4 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptRes
}
baseMapper.updateById(list);
}
/**
* 初始化租户部门
*
* @param deptName
* @return 部门ID
*/
@Override
public Long initTenantDept(String deptName) {
//部门添加
DeptDO deptDO = new DeptDO();
deptDO.setName(deptName);
deptDO.setParentId(0l);
deptDO.setAncestors("0");
deptDO.setDescription("系统初始部门");
deptDO.setSort(1);
deptDO.setStatus(DisEnableStatusEnum.ENABLE);
baseMapper.insert(deptDO);
return deptDO.getId();
}
}

View File

@@ -17,6 +17,7 @@
package top.continew.admin.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -99,17 +100,12 @@ public class MenuServiceImpl extends BaseServiceImpl<MenuMapper, MenuDO, MenuRes
RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK);
}
@Override
@Cached(key = "'ALL' + #tenantId", name = CacheConstants.ROLE_MENU_KEY_PREFIX)
public List<MenuResp> listAll(Long tenantId) {
return super.list(new MenuQuery(DisEnableStatusEnum.ENABLE), null);
}
@Override
public Set<String> listPermissionByUserId(Long userId) {
return baseMapper.selectPermissionByUserId(userId);
}
@Override
@Cached(key = "#roleId", name = CacheConstants.ROLE_MENU_KEY_PREFIX)
public List<MenuResp> listByRoleId(Long roleId) {
if (SysConstants.SUPER_ROLE_ID.equals(roleId)) {
@@ -122,60 +118,53 @@ public class MenuServiceImpl extends BaseServiceImpl<MenuMapper, MenuDO, MenuRes
}
@Override
public void menuInit(List<MenuDO> menuList, Long oldParentId, Long newParentId) {
List<MenuDO> children = menuList.stream().filter(menuDO -> menuDO.getParentId().equals(oldParentId)).toList();
for (MenuDO menuDO : children) {
Long oldId = menuDO.getId();
menuDO.setId(null);
menuDO.setParentId(newParentId);
save(menuDO);
menuInit(menuList, oldId, menuDO.getId());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteTenantMenus(List<MenuDO> menuList) {
if (!menuList.isEmpty()) {
List<Long> delIds = new ArrayList<>();
for (MenuDO menuDO : menuList) {
MenuDO tMenu = getOne(Wrappers.query(MenuDO.class)
.eq(menuDO.getType().equals(MenuTypeEnum.BUTTON.getValue()), "CONCAT(title,permission)", menuDO
.getTitle() + menuDO.getPermission())
.eq(!menuDO.getType().equals(MenuTypeEnum.BUTTON.getValue()), "name", menuDO.getName()));
if (tMenu != null) {
delIds.add(tMenu.getId());
}
}
if (!delIds.isEmpty()) {
//菜单删除
delete(delIds);
//绑定关系删除
roleMenuService.remove(Wrappers.lambdaQuery(RoleMenuDO.class).in(RoleMenuDO::getMenuId, delIds));
if (CollUtil.isEmpty(menuList)) {
return;
}
List<Long> delIds = new ArrayList<>();
for (MenuDO menu : menuList) {
MenuDO parentMenu = baseMapper.query()
.eq(menu.getType().equals(MenuTypeEnum.BUTTON), "CONCAT(title,permission)", menu.getTitle() + menu
.getPermission())
.eq(!menu.getType().equals(MenuTypeEnum.BUTTON), "name", menu.getName())
.one();
if (parentMenu != null) {
delIds.add(parentMenu.getId());
}
}
if (!delIds.isEmpty()) {
// 菜单删除
this.delete(delIds);
// 删除绑定关系
roleMenuService.remove(Wrappers.lambdaQuery(RoleMenuDO.class).in(RoleMenuDO::getMenuId, delIds));
}
// 删除缓存
RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK);
}
@Override
public void addTenantMenu(MenuDO menu, MenuDO pMenu) {
Long pId = 0l;
if (pMenu != null) {
MenuDO tPMenu = getOne(Wrappers.query(MenuDO.class)
.eq(pMenu.getType().equals(MenuTypeEnum.BUTTON.getValue()), "CONCAT(title,permission)", pMenu
.getTitle() + pMenu.getPermission())
.eq(!pMenu.getType().equals(MenuTypeEnum.BUTTON.getValue()), "name", pMenu.getName()));
pId = tPMenu.getId();
public void addTenantMenu(MenuDO menu, MenuDO parentMenu) {
Long parentId = SysConstants.SUPER_PARENT_ID;
if (parentMenu != null) {
MenuDO parent = baseMapper.query()
.eq(parentMenu.getType().equals(MenuTypeEnum.BUTTON), "CONCAT(title,permission)", parentMenu
.getTitle() + parentMenu.getPermission())
.eq(!parentMenu.getType().equals(MenuTypeEnum.BUTTON), "name", parentMenu.getName())
.one();
parentId = parent.getId();
}
menu.setId(null);
menu.setParentId(pId);
//菜单新增
save(menu);
//管理员绑定菜单
RoleDO roleDO = roleMapper.selectOne(Wrappers.lambdaQuery(RoleDO.class)
.eq(RoleDO::getCode, SysConstants.TENANT_ADMIN_CODE));
RoleMenuDO roleMenuDO = new RoleMenuDO();
roleMenuDO.setRoleId(roleDO.getId());
roleMenuDO.setMenuId(menu.getId());
roleMenuService.save(roleMenuDO);
menu.setParentId(parentId);
// 菜单新增
baseMapper.insert(menu);
// 管理员绑定菜单
RoleDO role = roleMapper.selectOne(Wrappers.lambdaQuery(RoleDO.class)
.eq(RoleDO::getCode, SysConstants.TENANT_ADMIN_ROLE_CODE));
roleMenuService.save(new RoleMenuDO(role.getId(), menu.getId()));
// 删除缓存
RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK);
}
/**

View File

@@ -27,7 +27,7 @@ import top.continew.admin.system.service.RoleDeptService;
import java.util.List;
/**
* 角色和部门业务实现
* 角色和部门关联业务实现
*
* @author Charles7c
* @since 2023/2/19 10:47

View File

@@ -70,7 +70,7 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleRes
String code = req.getCode();
CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code);
// 防止租户添加超管
CheckUtils.throwIf(SysConstants.SUPER_ROLE_CODE.equals(code), "新增失败,[{}] 禁止使用", code);
CheckUtils.throwIf(SysConstants.SUPER_ROLE_CODE.equals(code), "新增失败,编码 [{}] 禁止使用", code);
// 新增信息
Long roleId = super.create(req);
// 保存角色和部门关联
@@ -249,24 +249,4 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleRes
}
});
}
/**
* 初始化租户角色
*
* @return 角色ID
*/
@Override
public Long initTenantRole() {
RoleDO roleDO = new RoleDO();
roleDO.setName("系统管理员");
roleDO.setCode(SysConstants.TENANT_ADMIN_CODE);
roleDO.setDataScope(DataScopeEnum.ALL);
roleDO.setDescription("系统初始角色");
roleDO.setSort(1);
roleDO.setIsSystem(true);
roleDO.setMenuCheckStrictly(false);
roleDO.setDeptCheckStrictly(false);
baseMapper.insert(roleDO);
return roleDO.getId();
}
}

View File

@@ -225,19 +225,19 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
* 解密 SecretKey
*
* @param encryptSecretKey 加密的 SecretKey
* @param storage 存储信息
* @param oldStorage 旧存储配置
* @return 解密后的 SecretKey
*/
private String decryptSecretKey(String encryptSecretKey, StorageDO storage) {
// 修改时,如果 SecretKey 不修改,需要手动修正
if (storage != null) {
private String decryptSecretKey(String encryptSecretKey, StorageDO oldStorage) {
// 修改时SecretKey 为空或带 *,将不更改
if (oldStorage != null) {
boolean isSecretKeyNotUpdate = StrUtil.isBlank(encryptSecretKey) || encryptSecretKey
.contains(StringConstants.ASTERISK);
if (isSecretKeyNotUpdate) {
return storage.getSecretKey();
return oldStorage.getSecretKey();
}
}
// 新增场景,直接解密 SecretKey
// 解密
String secretKey = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(encryptSecretKey));
ValidationUtils.throwIfNull(secretKey, "私有密钥解密失败");
ValidationUtils.throwIf(secretKey.length() > 255, "私有密钥长度不能超过 255 个字符");

View File

@@ -1,96 +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.system.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
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.system.mapper.*;
import top.continew.admin.system.mapper.user.UserMapper;
import top.continew.admin.system.mapper.user.UserPasswordHistoryMapper;
import top.continew.admin.system.mapper.user.UserSocialMapper;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.service.FileService;
import top.continew.admin.system.service.TenantSysDataService;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import java.util.List;
/**
* @description: 多租户系统数据接口
* @author: 小熊
* @create: 2024-12-02 20:12
*/
@RequiredArgsConstructor
@Service
public class TenantSysDataServiceImpl implements TenantSysDataService {
private final DeptMapper deptMapper;
private final FileService fileService;
private final LogMapper logMapper;
private final MenuMapper menuMapper;
private final MessageMapper messageMapper;
private final MessageMapper messageUserMapper;
private final NoticeMapper noticeMapper;
private final RoleMapper roleMapper;
private final RoleDeptMapper roleDeptMapper;
private final RoleMenuMapper roleMenuMapper;
private final UserMapper userMapper;
private final UserPasswordHistoryMapper userPasswordHistoryMapper;
private final UserRoleMapper userRoleMapper;
private final UserSocialMapper userSocialMapper;
@Override
@Transactional
public void clear() {
//所有用户退出
List<UserDO> userDOS = userMapper.selectList(null);
for (UserDO userDO : userDOS) {
StpUtil.logout(userDO.getId());
}
Wrapper dw = Wrappers.query().eq("1", 1);
//部门清除
deptMapper.delete(dw);
//文件清除
List<Long> fileIds = fileService.list().stream().map(BaseIdDO::getId).toList();
if (!fileIds.isEmpty()) {
fileService.delete(fileIds);
}
//日志清除
logMapper.delete(dw);
//菜单清除
menuMapper.delete(dw);
//消息清除
messageMapper.delete(dw);
messageUserMapper.delete(dw);
//通知清除
noticeMapper.delete(dw);
//角色相关数据清除
roleMapper.delete(dw);
roleDeptMapper.delete(dw);
roleMenuMapper.delete(dw);
//用户数据清除
userMapper.delete(dw);
userPasswordHistoryMapper.delete(dw);
userRoleMapper.delete(dw);
userSocialMapper.delete(dw);
}
}

View File

@@ -23,7 +23,10 @@ import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
@@ -54,7 +57,6 @@ import org.springframework.web.multipart.MultipartFile;
import top.continew.admin.auth.service.OnlineUserService;
import top.continew.admin.common.base.service.BaseServiceImpl;
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.UserContext;
import top.continew.admin.common.context.UserContextHolder;
@@ -78,10 +80,8 @@ import top.continew.admin.system.service.*;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.FileUploadUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
@@ -734,26 +734,6 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
}
}
@Override
public Long initTenantUser(String username, String password, Long deptId) {
//密码验证
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(password));
ValidationUtils.throwIfNull(rawPassword, "密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
UserDO userDO = new UserDO();
userDO.setUsername(username);
userDO.setNickname("系统管理员");
userDO.setPassword(rawPassword);
userDO.setGender(GenderEnum.UNKNOWN);
userDO.setDescription("系统初始用户");
userDO.setStatus(DisEnableStatusEnum.ENABLE);
userDO.setIsSystem(true);
userDO.setDeptId(deptId);
baseMapper.insert(userDO);
return userDO.getId();
}
/**
* 根据 ID 获取用户信息(数据权限)
*