Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
92ab4ef
feat:多租户插件
xtanyu Nov 29, 2024
a9f8247
默认启用多租户插件
Charles7c Dec 2, 2024
8272986
多租户功能更新 (#99)
xtanyu Dec 2, 2024
a584b7b
多租户目录结构更新,新增租户登录信息修改功能 (#100)
xtanyu Dec 4, 2024
1aa72dc
dev分支合并,修复在线列表数据问题,租户套餐权限修改时同步修改租户权限 (#101)
xtanyu Dec 5, 2024
87c337c
fix: 修复关闭多租户时bean未注入导致的异常。合并dev (#104)
xtanyu Dec 7, 2024
6dbce78
fix: 修复租户权限问题,合并dev (#105)
xtanyu Dec 11, 2024
a8b6fe9
feat: 数据源级租户支持 (#114)
xtanyu Dec 26, 2024
a46f2b3
fix: 修复租户套餐和数据连接删除时的验证问题 (#116)
xtanyu Dec 26, 2024
26cb9ec
fix: 修复在多数据源情况下,租户忽略的表未使用主数据源问题。修复租户信息修改时的异常问题。合并dev (#127)
xtanyu Jan 15, 2025
c5fb8fd
Merge branch 'dev' into tenant-merge
xtanyu Apr 9, 2025
0ff2d80
fix: 修复租户权限问题
xtanyu May 19, 2025
f8590a4
Merge branch 'c_dev' into tenant-merge
xtanyu May 19, 2025
61d42d4
Merge branch 'c_dev' into tenant-merge
xtanyu Jul 9, 2025
542db25
Merge commit 'dcc28bcf34c8d69f53b9924bf93563bcd172e270' into tenant-m…
xtanyu Jul 9, 2025
c1c50aa
fix: 租户权限问题修复
xtanyu Jul 10, 2025
bdf74a1
fix: 租户删除bug修复。设置验证链接的超时时间
xtanyu Jul 10, 2025
9f44af5
Merge commit '72493f8161582a0afe329f8d54de347aad76959b' into tenant-m…
xtanyu Jul 10, 2025
0d7f906
fix: 修复日志记录问题
xtanyu Jul 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions continew-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,18 @@
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
</dependency>

<!-- 多租户组件-->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
</dependency>

<!-- Dynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.common.config.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import top.continew.starter.core.constant.PropertiesConstants;

import java.util.List;

/**
* @description: 多租户配置
* @author: 小熊
* @create: 2024-11-29 12:05
*/
@Component
@ConfigurationProperties(prefix = PropertiesConstants.TENANT)
@Data
public class TenantProperties {

private boolean enabled;

private List<Long> ignoreMenus;

}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ public class CacheConstants {
*/
public static final String DATA_IMPORT_KEY = "SYSTEM" + DELIMITER + "DATA_IMPORT" + DELIMITER;

/**
* 数据连接键前缀
*/
public static final String DB_CONNECT_KEY_PREFIX = "DB_CONNECT" + DELIMITER;

/**
* 租户信息前缀
*/
public static final String TENANT_KEY = "TENANT" + DELIMITER;

private CacheConstants() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ public class SysConstants {
*/
public static final String LOGOUT_URI = "/auth/logout";

/**
* 描述类字段后缀
*/
public static final String DESCRIPTION_FIELD_SUFFIX = "String";

/**
* 租户数据库前缀
*/
public static final String TENANT_DB_PREFIX = "tenant_";

/**
* 默认租户
*/
public static final String DEFAULT_TENANT = "0";

/**
* 默认数据源
*/
public static final String DEFAULT_DATASOURCE = "master";

/**
* 租户管理员角色编码
*/
public static final String TENANT_ADMIN_CODE = "tenant_admin";

private SysConstants() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public class UserContext implements Serializable {
*/
private Set<RoleContext> roles;

/**
/*
* 客户端类型
*/
private String clientType;
Expand All @@ -90,6 +90,11 @@ public class UserContext implements Serializable {
*/
private String clientId;

/**
* 租户 ID
*/
private Long tenantId;

public UserContext(Set<String> permissions, Set<RoleContext> roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.setRoles(roles);
Expand Down Expand Up @@ -129,4 +134,5 @@ public boolean isPasswordExpired() {
}
return this.pwdResetTime.plusDays(this.passwordExpirationDays).isBefore(LocalDateTime.now());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,11 @@ public static boolean isAdmin() {
StpUtil.checkLogin();
return getContext().isAdmin();
}

/**
* 获取租户ID
*/
public static Long getTenantId() {
return ExceptionUtils.exToNull(() -> getContext().getTenantId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ public interface AppService extends BaseService<AppResp, AppDetailResp, AppQuery
* @return 应用信息
*/
AppDO getByAccessKey(String accessKey);

}
21 changes: 21 additions & 0 deletions continew-plugin/continew-plugin-tenant/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.admin</groupId>
<artifactId>continew-plugin</artifactId>
<version>${revision}</version>
</parent>

<artifactId>continew-plugin-tenant</artifactId>
<description>多租户插件</description>
<dependencies>
<dependency>
<groupId>top.continew.admin</groupId>
<artifactId>continew-system</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* 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.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.common.config.properties.TenantProperties;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.system.model.entity.MenuDO;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.model.req.user.UserPasswordResetReq;
import top.continew.admin.system.service.*;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.query.TenantQuery;
import top.continew.admin.tenant.model.req.TenantLoginUserInfoReq;
import top.continew.admin.tenant.model.req.TenantReq;
import top.continew.admin.tenant.model.resp.*;
import top.continew.admin.tenant.service.TenantDbConnectService;
import top.continew.admin.tenant.service.TenantPackageService;
import top.continew.admin.tenant.service.TenantService;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.admin.common.base.model.resp.BaseResp;
import top.continew.starter.extension.crud.model.req.IdsReq;
import top.continew.starter.extension.crud.model.resp.IdResp;
import top.continew.starter.extension.tenant.TenantHandler;

import java.util.List;

/**
* 租户管理 API
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Tag(name = "租户管理 API")
@RestController
@AllArgsConstructor
@CrudRequestMapping(value = "/tenant/user", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
public class TenantController extends BaseController<TenantService, TenantResp, TenantDetailResp, TenantQuery, TenantReq> {

private final TenantProperties tenantProperties;
private final DeptService deptService;
private final MenuService menuService;
private final TenantPackageService packageService;
private final RoleService roleService;
private final UserService userService;
private final TenantSysDataService tenantSysDataService;
private final RoleMenuService roleMenuService;
private final TenantDbConnectService dbConnectService;

@GetMapping("/common")
@SaIgnore
@Operation(summary = "多租户通用信息查询", description = "多租户通用信息查询")
public TenantCommonResp common() {
TenantCommonResp commonResp = new TenantCommonResp();
commonResp.setIsEnabled(tenantProperties.isEnabled());
commonResp.setAvailableList(baseService.getAvailableList());
return commonResp;
}

@Override
@DSTransactional
public IdResp<Long> create(TenantReq req) {
//套餐菜单
TenantPackageDetailResp detailResp = packageService.get(req.getPackageId());
CheckUtils.throwIf(detailResp.getMenuIds().isEmpty(), "该套餐无可用菜单");
List<MenuDO> menuRespList = menuService.listByIds(detailResp.getMenuIds());
//租户添加
IdResp<Long> baseIdResp = super.create(req);
//在租户中执行数据插入
SpringUtil.getBean(TenantHandler.class).execute(baseIdResp.getId(), () -> {
//租户部门初始化
Long deptId = deptService.initTenantDept(req.getName());
//租户菜单初始化
menuService.menuInit(menuRespList, 0L, 0L);
//租户角色初始化
Long roleId = roleService.initTenantRole();
//角色绑定菜单
roleMenuService.add(menuService.listAll(baseIdResp.getId()).stream().map(BaseResp::getId).toList(), roleId);
//管理用户初始化
Long userId = userService.initTenantUser(req.getUsername(), req.getPassword(), deptId);
//用户绑定角色
roleService.assignToUsers(roleId, ListUtil.of(userId));
//租户绑定用户
baseService.bindUser(baseIdResp.getId(), userId);
});
return baseIdResp;
}

@Override
public void delete(Long id) {
SpringUtil.getBean(TenantHandler.class).execute(id, () -> {
//系统数据清除
tenantSysDataService.clear();
});
super.delete(id);
}

@Override
public void batchDelete(@Valid IdsReq ids) {
for (Long id : ids.getIds()) {
//在租户中执行数据清除
SpringUtil.getBean(TenantHandler.class).execute(id, () -> {
//系统数据清除
tenantSysDataService.clear();
});
}
super.batchDelete(ids);
}

/**
* 获取租户管理账号用户名
*/
@GetMapping("/loginUser/{tenantId}")
@Operation(summary = "获取租户管理账号信息", description = "获取租户管理账号信息")
@SaCheckPermission("tenant:user:editLoginUserInfo")
public String loginUserInfo(@PathVariable Long tenantId) {
TenantDO tenantDO = baseService.getTenantById(tenantId);
CheckUtils.throwIfNull(tenantDO, "租户不存在");
StringBuilder username = new StringBuilder();
SpringUtil.getBean(TenantHandler.class).execute(tenantDO.getId(), () -> {
UserDO userDO = userService.getById(tenantDO.getUserId());
CheckUtils.throwIfNull(userDO, "租户管理用户不存在");
username.append(userDO.getUsername());
});
return username.toString();
}

/**
* 租户管理账号信息更新
*/
@PutMapping("/loginUser")
@Operation(summary = "租户管理账号信息更新", description = "租户管理账号信息更新")
@SaCheckPermission("tenant:user:editLoginUserInfo")
@DSTransactional
public void editLoginUserInfo(@Validated @RequestBody TenantLoginUserInfoReq req) {
TenantDO tenantDO = baseService.getTenantById(req.getTenantId());
CheckUtils.throwIfNull(tenantDO, "租户不存在");
SpringUtil.getBean(TenantHandler.class).execute(tenantDO.getId(), () -> {
UserDO userDO = userService.getById(tenantDO.getUserId());
CheckUtils.throwIfNull(userDO, "用户不存在");
//修改用户名
if (!req.getUsername().equals(userDO.getUsername())) {
userService.update(Wrappers.lambdaUpdate(UserDO.class)
.set(UserDO::getUsername, req.getUsername())
.eq(BaseIdDO::getId, userDO.getId()));
}
//修改密码
if (StrUtil.isNotEmpty(req.getPassword())) {
String password = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfNull(password, "新密码解密失败");
UserPasswordResetReq passwordResetReq = new UserPasswordResetReq();
passwordResetReq.setNewPassword(password);
userService.resetPassword(passwordResetReq, userDO.getId());
}
});
}

/**
* 查询所有租户套餐
*/
@GetMapping("/all/package")
@Operation(summary = "查询所有租户套餐", description = "查询所有租户套餐")
@SaCheckPermission(value = {"tenant:user:add", "tenant:user:update"}, mode = SaMode.OR)
public List<TenantPackageResp> packageList() {
return packageService.list(null, null);
}

/**
* 查询所有数据库连接
*/
@GetMapping("/all/dbConnect")
@Operation(summary = "获取租户数据连接列表", description = "获取租户数据连接列表")
@SaCheckPermission(value = {"tenant:user:add", "tenant:user:update"}, mode = SaMode.OR)
public List<TenantDbConnectResp> dbConnectList() {
return dbConnectService.list(null, null);
}

}
Loading
Loading