refactor: 重构内部 API 依赖模式(降低耦合,公众号投票结论),在 common 模块新增 api 包,在对应 biz 模块增加实现

This commit is contained in:
2025-07-26 10:24:25 +08:00
parent 3af43ef6c7
commit 7f0059984d
30 changed files with 781 additions and 128 deletions

View File

@@ -14,12 +14,4 @@
<name>${project.artifactId}</name>
<description>租户插件</description>
<dependencies>
<!-- 系统管理模块 -->
<dependency>
<groupId>top.continew.admin</groupId>
<artifactId>continew-system</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -14,28 +14,29 @@
* limitations under the License.
*/
package top.continew.admin.tenant.handler;
package top.continew.admin.tenant.api;
import top.continew.admin.tenant.model.req.TenantReq;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.admin.common.api.tenant.PackageMenuApi;
import top.continew.admin.tenant.service.PackageMenuService;
import java.util.List;
/**
* 租户数据处理器
*
* @author 小熊
* 套餐和菜单关联业务 API 实现
*
* @author Charles7c
* @since 2024/12/2 20:08
* @since 2025/7/23 21:13
*/
public interface TenantDataHandler {
@Service
@RequiredArgsConstructor
public class PackageMenuApiImpl implements PackageMenuApi {
/**
* 初始化数据
*
* @param tenant 租户信息
*/
void init(TenantReq tenant);
private final PackageMenuService baseService;
/**
* 清除数据
*/
void clear();
@Override
public List<Long> listMenuIdsByPackageId(Long packageId) {
return baseService.listMenuIdsByPackageId(packageId);
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.tenant.api;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.admin.common.api.tenant.TenantApi;
import top.continew.admin.tenant.constant.TenantCacheConstants;
import top.continew.admin.tenant.mapper.TenantMapper;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
/**
* 租户业务 API 实现
*
* @author Charles7c
* @since 2025/7/23 21:13
*/
@Service
@RequiredArgsConstructor
public class TenantApiImpl implements TenantApi {
private final TenantMapper baseMapper;
@Override
public void bindAdminUser(Long tenantId, Long userId) {
baseMapper.lambdaUpdate().set(TenantDO::getAdminUser, userId).eq(BaseIdDO::getId, tenantId).update();
// 更新租户缓存
TenantDO entity = baseMapper.selectById(tenantId);
RedisUtils.set(TenantCacheConstants.TENANT_KEY_PREFIX + tenantId, entity);
}
}

View File

@@ -23,11 +23,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.common.api.system.MenuApi;
import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.common.config.TenantExtensionProperties;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.model.query.MenuQuery;
import top.continew.admin.system.service.MenuService;
import top.continew.admin.tenant.model.query.PackageQuery;
import top.continew.admin.tenant.model.req.PackageReq;
import top.continew.admin.tenant.model.resp.PackageDetailResp;
@@ -52,16 +50,12 @@ import java.util.List;
public class PackageController extends BaseController<PackageService, PackageResp, PackageDetailResp, PackageQuery, PackageReq> {
private final TenantExtensionProperties tenantExtensionProperties;
private final MenuService menuService;
private final MenuApi menuApi;
@Operation(summary = "查询租户套餐菜单", description = "查询租户套餐菜单树列表")
@SaCheckPermission("tenant:package:list")
@GetMapping("/menu/tree")
public List<Tree<Long>> listMenuTree() {
MenuQuery query = new MenuQuery();
query.setStatus(DisEnableStatusEnum.ENABLE);
// 过滤掉租户不能使用的菜单
query.setExcludeMenuIdList(tenantExtensionProperties.getIgnoreMenus());
return menuService.tree(query, null, true);
return menuApi.listTree(tenantExtensionProperties.getIgnoreMenus());
}
}

View File

@@ -25,11 +25,9 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.common.api.system.UserApi;
import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.model.req.user.UserPasswordResetReq;
import top.continew.admin.system.service.UserService;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.query.TenantQuery;
import top.continew.admin.tenant.model.req.TenantAdminUserPwdUpdateReq;
@@ -56,7 +54,7 @@ import top.continew.starter.extension.tenant.util.TenantUtils;
@CrudRequestMapping(value = "/tenant/management", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
public class TenantController extends BaseController<TenantService, TenantResp, TenantDetailResp, TenantQuery, TenantReq> {
private final UserService userService;
private final UserApi userApi;
@Operation(summary = "修改租户管理员密码", description = "修改租户管理员密码")
@SaCheckPermission("tenant:management:updateAdminUserPwd")
@@ -65,12 +63,9 @@ public class TenantController extends BaseController<TenantService, TenantResp,
TenantDO tenant = baseService.getById(id);
String encryptPassword = req.getPassword();
TenantUtils.execute(id, () -> {
UserDO user = userService.getById(tenant.getAdminUser());
String password = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(encryptPassword));
ValidationUtils.throwIfNull(password, "新密码解密失败");
UserPasswordResetReq passwordResetReq = new UserPasswordResetReq();
passwordResetReq.setNewPassword(password);
userService.resetPassword(passwordResetReq, user.getId());
userApi.resetPassword(password, tenant.getAdminUser());
});
}
}

View File

@@ -1,221 +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.tenant.handler;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ReUtil;
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.common.constant.RegexConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DataScopeEnum;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.enums.GenderEnum;
import top.continew.admin.common.util.SecureUtils;
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.DeptDO;
import top.continew.admin.system.model.entity.FileDO;
import top.continew.admin.system.model.entity.RoleDO;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.service.FileService;
import top.continew.admin.system.service.RoleMenuService;
import top.continew.admin.system.service.RoleService;
import top.continew.admin.tenant.constant.TenantCacheConstants;
import top.continew.admin.tenant.mapper.TenantMapper;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.req.TenantReq;
import top.continew.admin.tenant.service.PackageMenuService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.tenant.util.TenantUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 租户数据处理器
*
* @author 小熊
* @author Charles7c
* @since 2024/12/2 20:12
*/
@Service
@RequiredArgsConstructor
public class TenantDataHandlerForSystem implements TenantDataHandler {
private final PackageMenuService packageMenuService;
private final DeptMapper deptMapper;
private final RoleMapper roleMapper;
private final RoleMenuService roleMenuService;
private final RoleMenuMapper roleMenuMapper;
private final RoleService roleService;
private final TenantMapper tenantMapper;
private final FileService fileService;
private final LogMapper logMapper;
private final MessageMapper messageMapper;
private final MessageMapper messageUserMapper;
private final NoticeMapper noticeMapper;
private final RoleDeptMapper roleDeptMapper;
private final UserMapper userMapper;
private final UserPasswordHistoryMapper userPasswordHistoryMapper;
private final UserRoleMapper userRoleMapper;
private final UserSocialMapper userSocialMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void init(TenantReq tenant) {
Long tenantId = tenant.getId();
TenantUtils.execute(tenantId, () -> {
// 初始化部门
Long deptId = this.initDeptData(tenant);
// 初始化角色
Long roleId = this.initRoleData(tenant);
// 角色绑定菜单
List<Long> menuIds = packageMenuService.listMenuIdsByPackageId(tenant.getPackageId());
roleMenuService.add(menuIds, roleId);
// 初始化管理用户
Long userId = this.initUserData(tenant, deptId);
// 用户绑定角色
roleService.assignToUsers(roleId, ListUtil.of(userId));
// 租户绑定用户
this.bindTenantUser(tenantId, userId);
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public void clear() {
// 退出所有用户
List<UserDO> userList = userMapper.selectList(null);
for (UserDO user : userList) {
StpUtil.logout(user.getId());
}
Wrapper queryWrapper = Wrappers.query().eq("1", 1);
// 部门清除
deptMapper.delete(queryWrapper);
// 文件清除
List<Long> fileIds = CollUtils.mapToList(fileService.list(), FileDO::getId);
if (!fileIds.isEmpty()) {
fileService.delete(fileIds);
}
// 日志清除
logMapper.delete(queryWrapper);
// 消息清除
messageMapper.delete(queryWrapper);
messageUserMapper.delete(queryWrapper);
// 通知清除
noticeMapper.delete(queryWrapper);
// 角色相关数据清除
roleMapper.delete(queryWrapper);
roleDeptMapper.delete(queryWrapper);
roleMenuMapper.delete(queryWrapper);
// 用户数据清除
userMapper.delete(queryWrapper);
userPasswordHistoryMapper.delete(queryWrapper);
userRoleMapper.delete(queryWrapper);
userSocialMapper.delete(queryWrapper);
}
/**
* 初始化部门数据
*
* @param tenant 租户信息
* @return 部门 ID
*/
private Long initDeptData(TenantReq tenant) {
DeptDO dept = new DeptDO();
dept.setName(tenant.getName());
dept.setParentId(SysConstants.SUPER_PARENT_ID);
dept.setAncestors("0");
dept.setDescription("系统初始部门");
dept.setSort(1);
dept.setStatus(DisEnableStatusEnum.ENABLE);
deptMapper.insert(dept);
return dept.getId();
}
/**
* 初始化角色数据
*
* @param tenant 租户信息
* @return 角色 ID
*/
private Long initRoleData(TenantReq tenant) {
RoleDO role = new RoleDO();
role.setName("系统管理员");
role.setCode(SysConstants.TENANT_ADMIN_ROLE_CODE);
role.setDataScope(DataScopeEnum.ALL);
role.setDescription("系统初始角色");
role.setSort(1);
role.setIsSystem(true);
role.setMenuCheckStrictly(true);
role.setDeptCheckStrictly(true);
roleMapper.insert(role);
return role.getId();
}
/**
* 初始化用户数据
*
* @param tenant 租户信息
* @param deptId 部门 ID
* @return 用户 ID
*/
private Long initUserData(TenantReq tenant, Long deptId) {
// 解密密码
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(tenant.getPassword()));
ValidationUtils.throwIfNull(rawPassword, "密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
// 初始化用户
UserDO user = new UserDO();
user.setUsername(tenant.getUsername());
user.setNickname("系统管理员");
user.setPassword(rawPassword);
user.setGender(GenderEnum.UNKNOWN);
user.setDescription("系统初始用户");
user.setStatus(DisEnableStatusEnum.ENABLE);
user.setIsSystem(true);
user.setPwdResetTime(LocalDateTime.now());
user.setDeptId(deptId);
userMapper.insert(user);
return user.getId();
}
/**
* 绑定租户管理员用户
*
* @param tenantId 租户 ID
* @param userId 用户 ID
*/
public void bindTenantUser(Long tenantId, Long userId) {
tenantMapper.lambdaUpdate().set(TenantDO::getAdminUser, userId).eq(BaseIdDO::getId, tenantId).update();
// 更新租户缓存
TenantDO entity = tenantMapper.selectById(tenantId);
RedisUtils.set(TenantCacheConstants.TENANT_KEY_PREFIX + tenantId, entity);
}
}

View File

@@ -16,24 +16,25 @@
package top.continew.admin.tenant.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alicp.jetcache.anno.Cached;
import lombok.RequiredArgsConstructor;
import me.ahoo.cosid.provider.IdGeneratorProvider;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.continew.admin.common.api.system.RoleApi;
import top.continew.admin.common.api.system.RoleMenuApi;
import top.continew.admin.common.api.tenant.TenantDataApi;
import top.continew.admin.common.base.service.BaseServiceImpl;
import top.continew.admin.common.config.TenantExtensionProperties;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.model.entity.RoleDO;
import top.continew.admin.system.model.entity.RoleMenuDO;
import top.continew.admin.system.service.RoleMenuService;
import top.continew.admin.system.service.RoleService;
import top.continew.admin.common.model.dto.TenantDTO;
import top.continew.admin.tenant.constant.TenantCacheConstants;
import top.continew.admin.tenant.constant.TenantConstants;
import top.continew.admin.tenant.handler.TenantDataHandler;
import top.continew.admin.tenant.mapper.TenantMapper;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.query.TenantQuery;
@@ -44,13 +45,13 @@ import top.continew.admin.tenant.service.PackageService;
import top.continew.admin.tenant.service.TenantService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.extension.tenant.util.TenantUtils;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -64,12 +65,12 @@ import java.util.Set;
@RequiredArgsConstructor
public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, TenantResp, TenantDetailResp, TenantQuery, TenantReq> implements TenantService {
private final Map<String, TenantDataApi> tenantDataApiMap = SpringUtil.getBeansOfType(TenantDataApi.class);
private final TenantExtensionProperties tenantExtensionProperties;
private final PackageService packageService;
private final IdGeneratorProvider idGeneratorProvider;
private final TenantDataHandler tenantDataHandler;
private final RoleMenuService roleMenuService;
private final RoleService roleService;
private final RoleMenuApi roleMenuApi;
private final RoleApi roleApi;
@Override
public Long create(TenantReq req) {
@@ -83,7 +84,7 @@ public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, T
Long id = super.create(req);
// 初始化租户数据
req.setId(id);
tenantDataHandler.init(req);
tenantDataApiMap.forEach((key, value) -> value.init(BeanUtil.copyProperties(req, TenantDTO.class)));
return id;
}
@@ -107,7 +108,7 @@ public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, T
public void beforeDelete(List<Long> ids) {
// 在租户中执行数据清除
for (Long id : ids) {
TenantUtils.execute(id, tenantDataHandler::clear);
TenantUtils.execute(id, () -> tenantDataApiMap.forEach((key, value) -> value.clear()));
}
}
@@ -162,27 +163,20 @@ public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, T
// 删除旧菜单
tenantIdList.forEach(tenantId -> TenantUtils.execute(tenantId, () -> {
// 更新在线用户上下文
List<RoleMenuDO> roleMenuList = roleMenuService.lambdaQuery()
.select(RoleMenuDO::getRoleId)
.notIn(RoleMenuDO::getMenuId, newMenuIds)
.list();
Set<Long> roleIdSet = CollUtils.mapToSet(roleMenuList, RoleMenuDO::getRoleId);
roleIdSet.forEach(roleService::updateUserContext);
Set<Long> roleIdSet = roleMenuApi.listRoleIdByNotInMenuIds(newMenuIds);
roleIdSet.forEach(roleApi::updateUserContext);
// 删除旧菜单
roleMenuService.lambdaUpdate().notIn(RoleMenuDO::getMenuId, newMenuIds).remove();
roleMenuApi.deleteByNotInMenuIds(newMenuIds);
}));
// 租户管理员:新增菜单
tenantIdList.forEach(tenantId -> TenantUtils.execute(tenantId, () -> {
RoleDO role = roleService.getByCode(SysConstants.TENANT_ADMIN_ROLE_CODE);
List<Long> oldMenuIdList = roleMenuService.listMenuIdByRoleIds(List.of(role.getId()));
Long roleId = roleApi.getIdByCode(SysConstants.TENANT_ADMIN_ROLE_CODE);
List<Long> oldMenuIdList = roleMenuApi.listMenuIdByRoleIds(List.of(roleId));
Collection<Long> addMenuIdList = CollUtil.disjunction(newMenuIds, oldMenuIdList);
if (CollUtil.isNotEmpty(addMenuIdList)) {
List<RoleMenuDO> roleMenuList = addMenuIdList.stream()
.map(menuId -> new RoleMenuDO(role.getId(), menuId))
.toList();
roleMenuService.saveBatch(roleMenuList, roleMenuList.size());
roleMenuApi.add(addMenuIdList.stream().toList(), roleId);
// 更新在线用户上下文
roleService.updateUserContext(role.getId());
roleApi.updateUserContext(roleId);
}
}));
// 删除缓存