From ed6dd65a51a1c26af2c9d76407463b7f67c71fd5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B0=8F=E7=86=8A?=
<50282105+xtanyu@users.noreply.github.com>
Date: Thu, 10 Jul 2025 20:38:59 +0800
Subject: [PATCH] =?UTF-8?q?feat(plugin/tenant):=20=E6=96=B0=E5=A2=9E?=
=?UTF-8?q?=E5=A4=9A=E7=A7=9F=E6=88=B7=E6=8F=92=E4=BB=B6=E6=A8=A1=E5=9D=97?=
=?UTF-8?q?=20(#175)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
continew-common/pom.xml | 13 +
.../config/properties/TenantProperties.java | 40 +++
.../admin/common/constant/CacheConstants.java | 10 +
.../admin/common/constant/SysConstants.java | 25 ++
.../admin/common/context/UserContext.java | 8 +-
.../common/context/UserContextHolder.java | 7 +
.../admin/open/service/AppService.java | 1 +
.../continew-plugin-tenant/pom.xml | 21 ++
.../tenant/controller/TenantController.java | 211 +++++++++++++++
.../controller/TenantDbConnectController.java | 39 +++
.../controller/TenantPackageController.java | 116 +++++++++
.../tenant/mapper/TenantDbConnectMapper.java | 28 ++
.../admin/tenant/mapper/TenantMapper.java | 44 ++++
.../tenant/mapper/TenantPackageMapper.java | 29 +++
.../admin/tenant/model/entity/TenantDO.java | 83 ++++++
.../model/entity/TenantDbConnectDO.java | 67 +++++
.../tenant/model/entity/TenantPackageDO.java | 58 +++++
.../model/enums/TenantConnectTypeEnum.java | 38 +++
.../model/query/TenantDbConnectQuery.java | 49 ++++
.../model/query/TenantPackageQuery.java | 55 ++++
.../admin/tenant/model/query/TenantQuery.java | 54 ++++
.../tenant/model/req/TenantDbConnectReq.java | 92 +++++++
.../model/req/TenantLoginUserInfoReq.java | 56 ++++
.../tenant/model/req/TenantPackageReq.java | 76 ++++++
.../admin/tenant/model/req/TenantReq.java | 120 +++++++++
.../model/resp/TenantAvailableResp.java | 30 +++
.../tenant/model/resp/TenantCommonResp.java | 41 +++
.../model/resp/TenantDbConnectDetailResp.java | 82 ++++++
.../model/resp/TenantDbConnectResp.java | 68 +++++
.../tenant/model/resp/TenantDetailResp.java | 99 +++++++
.../model/resp/TenantPackageDetailResp.java | 72 ++++++
.../tenant/model/resp/TenantPackageResp.java | 65 +++++
.../admin/tenant/model/resp/TenantResp.java | 91 +++++++
.../service/TenantDbConnectService.java | 36 +++
.../tenant/service/TenantPackageService.java | 31 +++
.../admin/tenant/service/TenantService.java | 63 +++++
.../impl/TenantDbConnectServiceImpl.java | 120 +++++++++
.../impl/TenantPackageServiceImpl.java | 71 ++++++
.../service/impl/TenantProviderImpl.java | 82 ++++++
.../service/impl/TenantServiceImpl.java | 202 +++++++++++++++
.../admin/tenant/util/DbConnectUtil.java | 106 ++++++++
continew-plugin/pom.xml | 1 +
continew-server/pom.xml | 6 +
.../admin/config/log/LogDaoLocalImpl.java | 8 +-
.../config/tenant/DataSourceSwitchAspect.java | 59 +++++
.../main/resources/config/application-dev.yml | 58 +++--
.../resources/config/application-prod.yml | 53 ++--
.../src/main/resources/config/application.yml | 30 ++-
.../db/changelog/db.changelog-master.yaml | 2 +
.../changelog/mysql/plugin/plugin_tenant.sql | 145 +++++++++++
.../db/changelog/mysql/tenant_table.sql | 241 ++++++++++++++++++
.../admin/auth/AbstractLoginHandler.java | 26 +-
.../auth/service/impl/AuthServiceImpl.java | 1 +
.../service/impl/OnlineUserServiceImpl.java | 10 +
.../admin/system/mapper/StorageMapper.java | 3 +
.../system/model/entity/user/UserDO.java | 6 +
.../admin/system/model/query/MenuQuery.java | 9 +
.../admin/system/service/DeptService.java | 8 +
.../admin/system/service/MenuService.java | 36 ++-
.../admin/system/service/RoleMenuService.java | 5 +-
.../admin/system/service/RoleService.java | 8 +
.../system/service/TenantSysDataService.java | 31 +++
.../admin/system/service/UserService.java | 9 +
.../system/service/impl/DeptServiceImpl.java | 21 ++
.../system/service/impl/MenuServiceImpl.java | 73 +++++-
.../service/impl/RoleMenuServiceImpl.java | 3 +-
.../system/service/impl/RoleServiceImpl.java | 22 ++
.../impl/TenantSysDataServiceImpl.java | 96 +++++++
.../system/service/impl/UserServiceImpl.java | 28 +-
pom.xml | 7 +
70 files changed, 3539 insertions(+), 65 deletions(-)
create mode 100644 continew-common/src/main/java/top/continew/admin/common/config/properties/TenantProperties.java
create mode 100644 continew-plugin/continew-plugin-tenant/pom.xml
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantDbConnectController.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantPackageController.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantDbConnectMapper.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantMapper.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantPackageMapper.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDO.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDbConnectDO.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantPackageDO.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/enums/TenantConnectTypeEnum.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantDbConnectQuery.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantPackageQuery.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantQuery.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantDbConnectReq.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantLoginUserInfoReq.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantPackageReq.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantAvailableResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantCommonResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectDetailResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDetailResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageDetailResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantResp.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantDbConnectService.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantPackageService.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantService.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantDbConnectServiceImpl.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantPackageServiceImpl.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantProviderImpl.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java
create mode 100644 continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/util/DbConnectUtil.java
create mode 100644 continew-server/src/main/java/top/continew/admin/config/tenant/DataSourceSwitchAspect.java
create mode 100644 continew-server/src/main/resources/db/changelog/mysql/plugin/plugin_tenant.sql
create mode 100644 continew-server/src/main/resources/db/changelog/mysql/tenant_table.sql
create mode 100644 continew-system/src/main/java/top/continew/admin/system/service/TenantSysDataService.java
create mode 100644 continew-system/src/main/java/top/continew/admin/system/service/impl/TenantSysDataServiceImpl.java
diff --git a/continew-common/pom.xml b/continew-common/pom.xml
index 2db3f20b..f029b85f 100644
--- a/continew-common/pom.xml
+++ b/continew-common/pom.xml
@@ -167,5 +167,18 @@
top.continew.starter
continew-starter-extension-crud-mp
+
+
+
+ top.continew.starter
+ continew-starter-extension-tenant-mp
+
+
+
+
+ com.baomidou
+ dynamic-datasource-spring-boot3-starter
+
+
\ No newline at end of file
diff --git a/continew-common/src/main/java/top/continew/admin/common/config/properties/TenantProperties.java b/continew-common/src/main/java/top/continew/admin/common/config/properties/TenantProperties.java
new file mode 100644
index 00000000..a3d09e40
--- /dev/null
+++ b/continew-common/src/main/java/top/continew/admin/common/config/properties/TenantProperties.java
@@ -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 ignoreMenus;
+
+}
diff --git a/continew-common/src/main/java/top/continew/admin/common/constant/CacheConstants.java b/continew-common/src/main/java/top/continew/admin/common/constant/CacheConstants.java
index 66cd0533..020d19d0 100644
--- a/continew-common/src/main/java/top/continew/admin/common/constant/CacheConstants.java
+++ b/continew-common/src/main/java/top/continew/admin/common/constant/CacheConstants.java
@@ -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() {
}
}
diff --git a/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java b/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java
index 1e08d9c5..7f9bfdb2 100644
--- a/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java
+++ b/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java
@@ -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() {
}
}
diff --git a/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java b/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java
index 35223f4d..91280b9a 100644
--- a/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java
+++ b/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java
@@ -80,7 +80,7 @@ public class UserContext implements Serializable {
*/
private Set roles;
- /**
+ /*
* 客户端类型
*/
private String clientType;
@@ -90,6 +90,11 @@ public class UserContext implements Serializable {
*/
private String clientId;
+ /**
+ * 租户 ID
+ */
+ private Long tenantId;
+
public UserContext(Set permissions, Set roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.setRoles(roles);
@@ -129,4 +134,5 @@ public class UserContext implements Serializable {
}
return this.pwdResetTime.plusDays(this.passwordExpirationDays).isBefore(LocalDateTime.now());
}
+
}
diff --git a/continew-common/src/main/java/top/continew/admin/common/context/UserContextHolder.java b/continew-common/src/main/java/top/continew/admin/common/context/UserContextHolder.java
index e727865c..019974b2 100644
--- a/continew-common/src/main/java/top/continew/admin/common/context/UserContextHolder.java
+++ b/continew-common/src/main/java/top/continew/admin/common/context/UserContextHolder.java
@@ -180,4 +180,11 @@ public class UserContextHolder {
StpUtil.checkLogin();
return getContext().isAdmin();
}
+
+ /**
+ * 获取租户ID
+ */
+ public static Long getTenantId() {
+ return ExceptionUtils.exToNull(() -> getContext().getTenantId());
+ }
}
diff --git a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/service/AppService.java b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/service/AppService.java
index 469f10ef..59e0dcbc 100644
--- a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/service/AppService.java
+++ b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/service/AppService.java
@@ -55,4 +55,5 @@ public interface AppService extends BaseService
+
+ 4.0.0
+
+ top.continew.admin
+ continew-plugin
+ ${revision}
+
+
+ continew-plugin-tenant
+ 多租户插件
+
+
+ top.continew.admin
+ continew-system
+
+
+
+
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java
new file mode 100644
index 00000000..e116c277
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java
@@ -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 {
+
+ 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 create(TenantReq req) {
+ //套餐菜单
+ TenantPackageDetailResp detailResp = packageService.get(req.getPackageId());
+ CheckUtils.throwIf(detailResp.getMenuIds().isEmpty(), "该套餐无可用菜单");
+ List menuRespList = menuService.listByIds(detailResp.getMenuIds());
+ //租户添加
+ IdResp 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 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 dbConnectList() {
+ return dbConnectService.list(null, null);
+ }
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantDbConnectController.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantDbConnectController.java
new file mode 100644
index 00000000..8caf9359
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantDbConnectController.java
@@ -0,0 +1,39 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.RestController;
+import top.continew.admin.common.base.controller.BaseController;
+import top.continew.admin.tenant.model.query.TenantDbConnectQuery;
+import top.continew.admin.tenant.model.req.TenantDbConnectReq;
+import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
+import top.continew.admin.tenant.model.resp.TenantDbConnectResp;
+import top.continew.admin.tenant.service.TenantDbConnectService;
+import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
+import top.continew.starter.extension.crud.enums.Api;
+
+/**
+ * 租户数据连接管理 API
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+@Tag(name = "租户数据连接管理 API")
+@RestController
+@CrudRequestMapping(value = "/tenant/dbConnect", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
+public class TenantDbConnectController extends BaseController {}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantPackageController.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantPackageController.java
new file mode 100644
index 00000000..2b1cdf0b
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantPackageController.java
@@ -0,0 +1,116 @@
+/*
+ * 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.hutool.core.lang.tree.Tree;
+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 lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import top.continew.admin.common.base.controller.BaseController;
+import top.continew.admin.common.config.properties.TenantProperties;
+import top.continew.admin.common.constant.CacheConstants;
+import top.continew.admin.common.enums.DisEnableStatusEnum;
+import top.continew.admin.system.model.entity.MenuDO;
+import top.continew.admin.system.model.query.MenuQuery;
+import top.continew.admin.system.service.MenuService;
+import top.continew.admin.tenant.model.entity.TenantDO;
+import top.continew.admin.tenant.model.query.TenantPackageQuery;
+import top.continew.admin.tenant.model.req.TenantPackageReq;
+import top.continew.admin.tenant.model.resp.TenantPackageDetailResp;
+import top.continew.admin.tenant.model.resp.TenantPackageResp;
+import top.continew.admin.tenant.service.TenantPackageService;
+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.extension.crud.annotation.CrudRequestMapping;
+import top.continew.starter.extension.crud.enums.Api;
+import top.continew.starter.extension.tenant.TenantHandler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 租户套餐管理 API
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+@Tag(name = "租户套餐管理 API")
+@RestController
+@AllArgsConstructor
+@CrudRequestMapping(value = "/tenant/package", api = {Api.LIST, Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
+public class TenantPackageController extends BaseController {
+
+ private final MenuService menuService;
+ private final TenantProperties tenantProperties;
+ private final TenantService tenantService;
+
+ @GetMapping("/menuTree")
+ @SaCheckPermission("tenant:package:get")
+ @Operation(summary = "获取租户套餐菜单", description = "获取租户套餐菜单")
+ public List> menuTree() {
+ MenuQuery query = new MenuQuery();
+ //必须是启用状态的菜单
+ query.setStatus(DisEnableStatusEnum.ENABLE);
+ //过滤掉租户不能使用的菜单
+ query.setExcludeMenuIdList(tenantProperties.getIgnoreMenus());
+ return menuService.tree(query, null, true);
+ }
+
+ @Override
+ @DSTransactional
+ public void update(TenantPackageReq req, Long id) {
+ //查询套餐对应的租户
+ List tenantDOList = tenantService.list(Wrappers.lambdaQuery(TenantDO.class)
+ .eq(TenantDO::getPackageId, id));
+ if (!tenantDOList.isEmpty()) {
+ TenantPackageDetailResp detail = baseService.get(id);
+ List oldMenuIds = detail.getMenuIds();
+ List newMenuIds = Arrays.stream(req.getMenuIds()).toList();
+ //删除的菜单
+ List deleteMenuIds = new ArrayList<>(oldMenuIds);
+ deleteMenuIds.removeAll(newMenuIds);
+ //如果有删除的菜单则绑定了套餐的租户对应的菜单也会删除
+ if (!deleteMenuIds.isEmpty()) {
+ List deleteMenus = menuService.listByIds(deleteMenuIds);
+ tenantDOList.forEach(tenantDO -> SpringUtil.getBean(TenantHandler.class)
+ .execute(tenantDO.getId(), () -> menuService.deleteTenantMenus(deleteMenus)));
+ }
+ //新增的菜单
+ List addMenuIds = new ArrayList<>(newMenuIds);
+ addMenuIds.removeAll(oldMenuIds);
+ //如果有新增的菜单则绑定了套餐的租户对应的菜单也会新增
+ if (!addMenuIds.isEmpty()) {
+ List addMenus = menuService.listByIds(addMenuIds);
+ for (MenuDO addMenu : addMenus) {
+ MenuDO pMenu = addMenu.getParentId() != 0 ? menuService.getById(addMenu.getParentId()) : null;
+ tenantDOList.forEach(tenantDO -> SpringUtil.getBean(TenantHandler.class)
+ .execute(tenantDO.getId(), () -> menuService.addTenantMenu(addMenu, pMenu)));
+ }
+ RedisUtils.deleteByPattern(CacheConstants.ROLE_MENU_KEY_PREFIX + StringConstants.ASTERISK);
+ }
+ }
+ super.update(req, id);
+ }
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantDbConnectMapper.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantDbConnectMapper.java
new file mode 100644
index 00000000..d7bd685e
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantDbConnectMapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.mapper;
+
+import top.continew.admin.tenant.model.entity.TenantDbConnectDO;
+import top.continew.starter.data.mapper.BaseMapper;
+
+/**
+ * 租户数据连接 Mapper
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+public interface TenantDbConnectMapper extends BaseMapper {}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantMapper.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantMapper.java
new file mode 100644
index 00000000..cc422f96
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantMapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.mapper;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import top.continew.admin.common.constant.SysConstants;
+import top.continew.admin.tenant.model.entity.TenantDO;
+import top.continew.admin.tenant.model.resp.TenantResp;
+import top.continew.starter.data.mapper.BaseMapper;
+
+/**
+ * 租户 Mapper
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+@DS(SysConstants.DEFAULT_DATASOURCE)
+@Mapper
+public interface TenantMapper extends BaseMapper {
+
+ @Select("SELECT sys_tenant.*,sys_tenant_package.`name` as package_name FROM sys_tenant\n" + "LEFT JOIN sys_tenant_package ON sys_tenant.package_id = sys_tenant_package.id\n" + "${ew.getCustomSqlSegment}")
+ IPage listTenant(IPage page, @Param(Constants.WRAPPER) Wrapper wrapper);
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantPackageMapper.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantPackageMapper.java
new file mode 100644
index 00000000..b42e6f50
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/TenantPackageMapper.java
@@ -0,0 +1,29 @@
+/*
+ * 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.mapper;
+
+import top.continew.starter.data.mapper.BaseMapper;
+import top.continew.admin.tenant.model.entity.TenantPackageDO;
+
+/**
+ * 租户套餐 Mapper
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+public interface TenantPackageMapper extends BaseMapper {
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDO.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDO.java
new file mode 100644
index 00000000..f7576a17
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDO.java
@@ -0,0 +1,83 @@
+/*
+ * 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.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import top.continew.admin.common.base.model.entity.BaseDO;
+
+import java.io.Serial;
+import java.time.LocalDateTime;
+
+/**
+ * 租户实体
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+@Data
+@TableName("sys_tenant")
+public class TenantDO extends BaseDO {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户名称
+ */
+ private String name;
+
+ /**
+ * 绑定的域名
+ */
+ private String domain;
+
+ /**
+ * 租户套餐编号
+ */
+ private Long packageId;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ private Integer status;
+
+ /**
+ * 租户过期时间
+ */
+ private LocalDateTime expireTime;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 租户编号
+ */
+ private String tenantSn;
+
+ /**
+ * 隔离级别
+ */
+ private Integer isolationLevel;
+
+ /**
+ * 数据连接ID
+ */
+ private Long dbConnectId;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDbConnectDO.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDbConnectDO.java
new file mode 100644
index 00000000..d5fbab31
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantDbConnectDO.java
@@ -0,0 +1,67 @@
+/*
+ * 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.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import top.continew.admin.common.base.model.entity.BaseDO;
+
+import java.io.Serial;
+
+/**
+ * 租户数据连接实体
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+@Data
+@TableName("sys_tenant_db_connect")
+public class TenantDbConnectDO extends BaseDO {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 连接名称
+ */
+ private String connectName;
+
+ /**
+ * 连接类型
+ */
+ private Integer type;
+
+ /**
+ * 连接主机地址
+ */
+ private String host;
+
+ /**
+ * 连接端口
+ */
+ private Integer port;
+
+ /**
+ * 连接用户名
+ */
+ private String username;
+
+ /**
+ * 连接密码
+ */
+ private String password;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantPackageDO.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantPackageDO.java
new file mode 100644
index 00000000..aa5bea91
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/entity/TenantPackageDO.java
@@ -0,0 +1,58 @@
+/*
+ * 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.model.entity;
+
+import java.io.Serial;
+
+import lombok.Data;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import top.continew.admin.common.base.model.entity.BaseDO;
+
+/**
+ * 租户套餐实体
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+@Data
+@TableName("sys_tenant_package")
+public class TenantPackageDO extends BaseDO {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 套餐名称
+ */
+ private String name;
+
+ /**
+ * 关联的菜单ids
+ */
+ private String menuIds;
+
+ /**
+ * 菜单选择是否父子节点关联
+ */
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ private Integer status;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/enums/TenantConnectTypeEnum.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/enums/TenantConnectTypeEnum.java
new file mode 100644
index 00000000..6be37cc0
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/enums/TenantConnectTypeEnum.java
@@ -0,0 +1,38 @@
+/*
+ * 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.model.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import top.continew.starter.core.exception.BusinessException;
+
+@Getter
+@AllArgsConstructor
+public enum TenantConnectTypeEnum {
+
+ MYSQL;
+
+ public static TenantConnectTypeEnum getByOrdinal(Integer ordinal) {
+ for (TenantConnectTypeEnum item : TenantConnectTypeEnum.values()) {
+ if (item.ordinal() == ordinal) {
+ return item;
+ }
+ }
+ throw new BusinessException("未知的连接类型");
+ }
+
+}
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantDbConnectQuery.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantDbConnectQuery.java
new file mode 100644
index 00000000..b89d0651
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantDbConnectQuery.java
@@ -0,0 +1,49 @@
+/*
+ * 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.model.query;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.*;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import top.continew.starter.data.annotation.Query;
+import top.continew.starter.data.enums.QueryType;
+
+/**
+ * 租户数据连接查询条件
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+@Data
+@Schema(description = "租户数据连接查询条件")
+public class TenantDbConnectQuery implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 连接名称
+ */
+ @Schema(description = "连接名称")
+ @Query(type = QueryType.EQ)
+ private String connectName;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantPackageQuery.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantPackageQuery.java
new file mode 100644
index 00000000..0c5b5121
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantPackageQuery.java
@@ -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.continew.admin.tenant.model.query;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.*;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import top.continew.starter.data.annotation.Query;
+import top.continew.starter.data.enums.QueryType;
+
+/**
+ * 租户套餐查询条件
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+@Data
+@Schema(description = "租户套餐查询条件")
+public class TenantPackageQuery implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 套餐名称
+ */
+ @Schema(description = "套餐名称")
+ @Query(type = QueryType.EQ)
+ private String name;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ @Schema(description = "状态(1:启用;2:禁用)")
+ @Query(type = QueryType.EQ)
+ private Integer status;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantQuery.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantQuery.java
new file mode 100644
index 00000000..98d5a652
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/query/TenantQuery.java
@@ -0,0 +1,54 @@
+/*
+ * 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.model.query;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import top.continew.starter.data.annotation.Query;
+import top.continew.starter.data.enums.QueryType;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 租户查询条件
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+@Data
+@Schema(description = "租户查询条件")
+public class TenantQuery implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户名称
+ */
+ @Schema(description = "租户名称")
+ @Query(type = QueryType.LIKE)
+ private String name;
+
+ /**
+ * 租户套餐编号
+ */
+ @Schema(description = "租户套餐编号")
+ @Query(type = QueryType.EQ)
+ private Long packageId;
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantDbConnectReq.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantDbConnectReq.java
new file mode 100644
index 00000000..40120d6f
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantDbConnectReq.java
@@ -0,0 +1,92 @@
+/*
+ * 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.model.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 创建或修改租户数据连接参数
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+@Data
+@Schema(description = "创建或修改租户数据连接参数")
+public class TenantDbConnectReq implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 连接名称
+ */
+ @Schema(description = "连接名称")
+ @NotBlank(message = "连接名称不能为空")
+ @Length(max = 128, message = "连接名称长度不能超过 {max} 个字符")
+ private String connectName;
+
+ /**
+ * 连接类型
+ */
+ @Schema(description = "连接类型")
+ @NotNull(message = "连接类型不能为空")
+ private Integer type;
+
+ /**
+ * 连接主机地址
+ */
+ @Schema(description = "连接主机地址")
+ @NotBlank(message = "连接主机地址不能为空")
+ @Length(max = 128, message = "连接主机地址长度不能超过 {max} 个字符")
+ private String host;
+
+ /**
+ * 连接端口
+ */
+ @Schema(description = "连接端口")
+ @NotNull(message = "连接端口不能为空")
+ private Integer port;
+
+ /**
+ * 连接用户名
+ */
+ @Schema(description = "连接用户名")
+ @NotBlank(message = "连接用户名不能为空")
+ @Length(max = 128, message = "连接用户名长度不能超过 {max} 个字符")
+ private String username;
+
+ /**
+ * 连接密码
+ */
+ @Schema(description = "连接密码")
+ @NotBlank(message = "连接密码不能为空")
+ @Length(max = 128, message = "连接密码长度不能超过 {max} 个字符")
+ private String password;
+
+ /**
+ * ID
+ */
+ @Schema(hidden = true)
+ private Long id;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantLoginUserInfoReq.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantLoginUserInfoReq.java
new file mode 100644
index 00000000..f58377f4
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantLoginUserInfoReq.java
@@ -0,0 +1,56 @@
+/*
+ * 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.model.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @description: 租户登录用户信息
+ * @author: 小熊
+ * @create: 2024-12-02 20:41
+ */
+@Data
+public class TenantLoginUserInfoReq implements Serializable {
+
+ /**
+ * 租户id
+ */
+ @NotNull(message = "租户ID不能为空")
+ private Long tenantId;
+
+ /**
+ * 登录用户名
+ */
+ @NotEmpty(message = "登录用户名不能为空")
+ private String username;
+
+ /**
+ * 登录密码
+ */
+ private String password;
+
+ /**
+ * ID
+ */
+ @Schema(hidden = true)
+ private Long id;
+}
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantPackageReq.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantPackageReq.java
new file mode 100644
index 00000000..02713d75
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantPackageReq.java
@@ -0,0 +1,76 @@
+/*
+ * 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.model.req;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.*;
+
+import jakarta.validation.constraints.*;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 创建或修改租户套餐参数
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+@Data
+@Schema(description = "创建或修改租户套餐参数")
+public class TenantPackageReq implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 套餐名称
+ */
+ @Schema(description = "套餐名称")
+ @NotBlank(message = "套餐名称不能为空")
+ @Length(max = 64, message = "套餐名称长度不能超过 {max} 个字符")
+ private String name;
+
+ /**
+ * 关联的菜单ids
+ */
+ @Schema(description = "关联的菜单ids")
+ private Long[] menuIds;
+
+ /**
+ * 菜单选择是否父子节点关联
+ */
+ @Schema(description = "菜单选择是否父子节点关联")
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 状态
+ */
+ @Schema(description = "状态")
+ @NotNull(message = "状态不能为空")
+ private Integer status;
+
+ /**
+ * ID
+ */
+ @Schema(hidden = true)
+ private Long id;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java
new file mode 100644
index 00000000..3159698b
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java
@@ -0,0 +1,120 @@
+/*
+ * 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.model.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+import top.continew.admin.common.constant.RegexConstants;
+import top.continew.starter.extension.crud.validation.CrudValidationGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 创建或修改租户参数
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+@Data
+@Schema(description = "创建或修改租户参数")
+public class TenantReq implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户名称
+ */
+ @Schema(description = "租户名称")
+ @NotBlank(message = "租户名称不能为空")
+ @Length(max = 64, message = "租户名称长度不能超过 {max} 个字符")
+ private String name;
+
+ /**
+ * 绑定的域名
+ */
+ @Schema(description = "绑定的域名")
+ @Length(max = 128, message = "绑定的域名长度不能超过 {max} 个字符")
+ private String domain;
+
+ /**
+ * 租户套餐编号
+ */
+ @Schema(description = "租户套餐编号")
+ @NotNull(message = "租户套餐编号不能为空")
+ private Long packageId;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ @Schema(description = "状态")
+ @NotNull(message = "状态不能为空")
+ private Integer status;
+
+ /**
+ * 租户过期时间
+ */
+ @Schema(description = "租户过期时间")
+ @Future(message = "过期时间必须是未来时间")
+ private LocalDateTime expireTime;
+
+ /**
+ * 用户名
+ */
+ @Schema(description = "用户名", example = "zhangsan")
+ @NotBlank(message = "用户名不能为空", groups = CrudValidationGroup.Create.class)
+ @Pattern(regexp = RegexConstants.USERNAME, message = "用户名长度为 4-64 个字符,支持大小写字母、数字、下划线,以字母开头")
+ private String username;
+
+ /**
+ * 密码(加密)
+ */
+ @Schema(description = "密码(加密)", example = "E7c72TH+LDxKTwavjM99W1MdI9Lljh79aPKiv3XB9MXcplhm7qJ1BJCj28yaflbdVbfc366klMtjLIWQGqb0qw==")
+ @NotBlank(message = "密码不能为空", groups = CrudValidationGroup.Create.class)
+ private String password;
+
+ /**
+ * 租户编号
+ */
+ private String tenantSn;
+
+ /**
+ * 隔离级别
+ */
+ @Schema(description = "隔离级别")
+ @NotNull(message = "隔离级别不能为空", groups = CrudValidationGroup.Create.class)
+ private Integer isolationLevel;
+
+ /**
+ * 数据连接ID
+ */
+ @Schema(description = "数据连接ID")
+ private Long dbConnectId;
+
+ /**
+ * ID
+ */
+ @Schema(hidden = true)
+ private Long id;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantAvailableResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantAvailableResp.java
new file mode 100644
index 00000000..82f9010f
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantAvailableResp.java
@@ -0,0 +1,30 @@
+/*
+ * 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.model.resp;
+
+import lombok.Data;
+
+@Data
+public class TenantAvailableResp {
+
+ private Long id;
+
+ private String name;
+
+ private String domain;
+
+}
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantCommonResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantCommonResp.java
new file mode 100644
index 00000000..083a8d56
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantCommonResp.java
@@ -0,0 +1,41 @@
+/*
+ * 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.model.resp;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @description: 租户通用信息返回
+ * @author: 小熊
+ * @create: 2024-11-28 09:53
+ */
+@Data
+public class TenantCommonResp {
+
+ /**
+ * 是否开启了多租户
+ */
+ private Boolean isEnabled;
+
+ /**
+ * 可用租户列表
+ */
+ private List availableList;
+
+}
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectDetailResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectDetailResp.java
new file mode 100644
index 00000000..219e0aa2
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectDetailResp.java
@@ -0,0 +1,82 @@
+/*
+ * 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.model.resp;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import top.continew.admin.common.base.model.resp.BaseDetailResp;
+
+import java.io.Serial;
+
+/**
+ * 租户数据连接详情信息
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+@Data
+@ExcelIgnoreUnannotated
+@Schema(description = "租户数据连接详情信息")
+public class TenantDbConnectDetailResp extends BaseDetailResp {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 连接名称
+ */
+ @Schema(description = "连接名称")
+ @ExcelProperty(value = "连接名称")
+ private String connectName;
+
+ /**
+ * 连接类型
+ */
+ @Schema(description = "连接类型")
+ @ExcelProperty(value = "连接类型")
+ private Integer type;
+
+ /**
+ * 连接主机地址
+ */
+ @Schema(description = "连接主机地址")
+ @ExcelProperty(value = "连接主机地址")
+ private String host;
+
+ /**
+ * 连接端口
+ */
+ @Schema(description = "连接端口")
+ @ExcelProperty(value = "连接端口")
+ private Integer port;
+
+ /**
+ * 连接用户名
+ */
+ @Schema(description = "连接用户名")
+ @ExcelProperty(value = "连接用户名")
+ private String username;
+
+ /**
+ * 连接密码
+ */
+ @Schema(description = "连接密码")
+ @ExcelProperty(value = "连接密码")
+ private String password;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectResp.java
new file mode 100644
index 00000000..a7efd70f
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDbConnectResp.java
@@ -0,0 +1,68 @@
+/*
+ * 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.model.resp;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import top.continew.admin.common.base.model.resp.BaseResp;
+
+import java.io.Serial;
+
+/**
+ * 租户数据连接信息
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+@Data
+@Schema(description = "租户数据连接信息")
+public class TenantDbConnectResp extends BaseResp {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 连接名称
+ */
+ @Schema(description = "连接名称")
+ private String connectName;
+
+ /**
+ * 连接类型
+ */
+ @Schema(description = "连接类型")
+ private Integer type;
+
+ /**
+ * 连接主机地址
+ */
+ @Schema(description = "连接主机地址")
+ private String host;
+
+ /**
+ * 连接端口
+ */
+ @Schema(description = "连接端口")
+ private Integer port;
+
+ /**
+ * 连接用户名
+ */
+ @Schema(description = "连接用户名")
+ private String username;
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDetailResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDetailResp.java
new file mode 100644
index 00000000..7ce09059
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantDetailResp.java
@@ -0,0 +1,99 @@
+/*
+ * 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.model.resp;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import top.continew.admin.common.base.model.resp.BaseDetailResp;
+
+import java.io.Serial;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 租户详情信息
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+@Data
+@ExcelIgnoreUnannotated
+@Schema(description = "租户详情信息")
+public class TenantDetailResp extends BaseDetailResp {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户名称
+ */
+ @Schema(description = "租户名称")
+ @ExcelProperty(value = "租户名称")
+ private String name;
+
+ /**
+ * 绑定的域名
+ */
+ @Schema(description = "绑定的域名")
+ @ExcelProperty(value = "绑定的域名")
+ private String domain;
+
+ /**
+ * 租户套餐编号
+ */
+ @Schema(description = "租户套餐编号")
+ @ExcelProperty(value = "租户套餐编号")
+ private Long packageId;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ @Schema(description = "状态(1:启用;2:禁用)")
+ @ExcelProperty(value = "状态(1:启用;2:禁用)")
+ private Integer status;
+
+ /**
+ * 租户过期时间
+ */
+ @Schema(description = "租户过期时间")
+ @ExcelProperty(value = "租户过期时间")
+ private LocalDateTime expireTime;
+
+ /**
+ * 绑定的套餐名称
+ */
+ @Schema(description = "绑定的套餐名称")
+ private String packageName;
+
+ /**
+ * 套餐关联的菜单
+ */
+ @Schema(description = "关联的菜单ids")
+ private List menuIds;
+
+ /**
+ * 租户编号
+ */
+ private String tenantSn;
+
+ /**
+ * 租户绑定的管理用户id
+ */
+ private Long userId;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageDetailResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageDetailResp.java
new file mode 100644
index 00000000..d928a390
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageDetailResp.java
@@ -0,0 +1,72 @@
+/*
+ * 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.model.resp;
+
+import java.io.Serial;
+import java.time.*;
+import java.util.List;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import top.continew.admin.common.base.model.resp.BaseDetailResp;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+
+/**
+ * 租户套餐详情信息
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+@Data
+@ExcelIgnoreUnannotated
+@Schema(description = "租户套餐详情信息")
+public class TenantPackageDetailResp extends BaseDetailResp {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 套餐名称
+ */
+ @Schema(description = "套餐名称")
+ @ExcelProperty(value = "套餐名称")
+ private String name;
+
+ /**
+ * 关联的菜单ids
+ */
+ @Schema(description = "关联的菜单ids")
+ @ExcelProperty(value = "关联的菜单ids")
+ private List menuIds;
+
+ /**
+ * 菜单选择是否父子节点关联
+ */
+ @Schema(description = "菜单选择是否父子节点关联")
+ @ExcelProperty(value = "菜单选择是否父子节点关联")
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ @Schema(description = "状态(1:启用;2:禁用)")
+ @ExcelProperty(value = "状态(1:启用;2:禁用)")
+ private Integer status;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageResp.java
new file mode 100644
index 00000000..846cebe8
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantPackageResp.java
@@ -0,0 +1,65 @@
+/*
+ * 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.model.resp;
+
+import java.io.Serial;
+import java.time.*;
+import java.util.List;
+
+import lombok.Data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import top.continew.admin.common.base.model.resp.BaseResp;
+
+/**
+ * 租户套餐信息
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+@Data
+@Schema(description = "租户套餐信息")
+public class TenantPackageResp extends BaseResp {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 套餐名称
+ */
+ @Schema(description = "套餐名称")
+ private String name;
+
+ /**
+ * 关联的菜单ids
+ */
+ @Schema(description = "关联的菜单ids")
+ private List menuIds;
+
+ /**
+ * 菜单选择是否父子节点关联
+ */
+ @Schema(description = "菜单选择是否父子节点关联")
+ private Boolean menuCheckStrictly;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ @Schema(description = "状态(1:启用;2:禁用)")
+ private Integer status;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantResp.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantResp.java
new file mode 100644
index 00000000..e51ca067
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/resp/TenantResp.java
@@ -0,0 +1,91 @@
+/*
+ * 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.model.resp;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import top.continew.admin.common.base.model.resp.BaseResp;
+
+import java.io.Serial;
+import java.time.LocalDateTime;
+
+/**
+ * 租户信息
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+@Data
+@Schema(description = "租户信息")
+public class TenantResp extends BaseResp {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户名称
+ */
+ @Schema(description = "租户名称")
+ private String name;
+
+ /**
+ * 绑定的域名
+ */
+ @Schema(description = "绑定的域名")
+ private String domain;
+
+ /**
+ * 租户套餐编号
+ */
+ @Schema(description = "租户套餐编号")
+ private Long packageId;
+
+ /**
+ * 状态(1:启用;2:禁用)
+ */
+ @Schema(description = "状态(1:启用;2:禁用)")
+ private Integer status;
+
+ /**
+ * 租户过期时间
+ */
+ @Schema(description = "租户过期时间")
+ private LocalDateTime expireTime;
+
+ /**
+ * 绑定的套餐名称
+ */
+ @Schema(description = "绑定的套餐名称")
+ private String packageName;
+
+ /**
+ * 租户编号
+ */
+ private String tenantSn;
+
+ /**
+ * 隔离级别
+ */
+ @Schema(description = "隔离级别")
+ private Integer isolationLevel;
+
+ /**
+ * 数据连接ID
+ */
+ @Schema(description = "数据连接ID")
+ private Long dbConnectId;
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantDbConnectService.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantDbConnectService.java
new file mode 100644
index 00000000..fb121d5a
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantDbConnectService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.service;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import top.continew.admin.common.base.service.BaseService;
+import top.continew.admin.tenant.model.query.TenantDbConnectQuery;
+import top.continew.admin.tenant.model.req.TenantDbConnectReq;
+import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
+import top.continew.admin.tenant.model.resp.TenantDbConnectResp;
+
+/**
+ * 租户数据连接业务接口
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+public interface TenantDbConnectService extends BaseService {
+
+ JdbcTemplate getConnectJdbcTemplateById(Long id);
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantPackageService.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantPackageService.java
new file mode 100644
index 00000000..99e29145
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantPackageService.java
@@ -0,0 +1,31 @@
+/*
+ * 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.service;
+
+import top.continew.admin.common.base.service.BaseService;
+import top.continew.admin.tenant.model.query.TenantPackageQuery;
+import top.continew.admin.tenant.model.req.TenantPackageReq;
+import top.continew.admin.tenant.model.resp.TenantPackageDetailResp;
+import top.continew.admin.tenant.model.resp.TenantPackageResp;
+
+/**
+ * 租户套餐业务接口
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+public interface TenantPackageService extends BaseService {}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantService.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantService.java
new file mode 100644
index 00000000..7eb058cb
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/TenantService.java
@@ -0,0 +1,63 @@
+/*
+ * 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.service;
+
+import top.continew.admin.common.base.service.BaseService;
+import top.continew.admin.tenant.model.entity.TenantDO;
+import top.continew.admin.tenant.model.query.TenantQuery;
+import top.continew.admin.tenant.model.req.TenantReq;
+import top.continew.admin.tenant.model.resp.TenantAvailableResp;
+import top.continew.admin.tenant.model.resp.TenantDetailResp;
+import top.continew.admin.tenant.model.resp.TenantResp;
+import top.continew.starter.data.service.IService;
+
+import java.util.List;
+
+/**
+ * 租户业务接口
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+public interface TenantService extends BaseService, IService {
+
+ /**
+ * 获取所有可用的租户列表
+ */
+ List getAvailableList();
+
+ /**
+ * 租户绑定用户
+ */
+ void bindUser(Long tenantId, Long userId);
+
+ /**
+ * 检查租户状态
+ */
+ void checkStatus();
+
+ /**
+ * 根据id获取租户DO
+ */
+ TenantDO getTenantById(Long id);
+
+ /**
+ * 根据用户id获取租户信息
+ */
+ TenantDO getTenantByUserId(Long userId);
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantDbConnectServiceImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantDbConnectServiceImpl.java
new file mode 100644
index 00000000..2962c156
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantDbConnectServiceImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.service.impl;
+
+import com.alicp.jetcache.anno.Cached;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import top.continew.admin.common.base.service.BaseServiceImpl;
+import top.continew.admin.common.constant.CacheConstants;
+import top.continew.admin.tenant.mapper.TenantDbConnectMapper;
+import top.continew.admin.tenant.mapper.TenantMapper;
+import top.continew.admin.tenant.model.entity.TenantDO;
+import top.continew.admin.tenant.model.entity.TenantDbConnectDO;
+import top.continew.admin.tenant.model.enums.TenantConnectTypeEnum;
+import top.continew.admin.tenant.model.query.TenantDbConnectQuery;
+import top.continew.admin.tenant.model.req.TenantDbConnectReq;
+import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
+import top.continew.admin.tenant.model.resp.TenantDbConnectResp;
+import top.continew.admin.tenant.service.TenantDbConnectService;
+import top.continew.admin.tenant.util.DbConnectUtil;
+import top.continew.starter.cache.redisson.util.RedisUtils;
+import top.continew.starter.core.util.validation.CheckUtils;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+/**
+ * 租户数据连接业务实现
+ *
+ * @author 小熊
+ * @since 2024/12/12 19:13
+ */
+@Service
+@RequiredArgsConstructor
+public class TenantDbConnectServiceImpl extends BaseServiceImpl implements TenantDbConnectService {
+
+ private final TenantMapper tenantMapper;
+
+ @Override
+ @Cached(name = CacheConstants.DB_CONNECT_KEY_PREFIX, key = "#id")
+ public TenantDbConnectDetailResp get(Long id) {
+ return super.get(id);
+ }
+
+ @Override
+ protected void beforeCreate(TenantDbConnectReq req) {
+ TenantConnectTypeEnum connectTypeEnum = TenantConnectTypeEnum.getByOrdinal(req.getType());
+ if (TenantConnectTypeEnum.MYSQL.equals(connectTypeEnum)) {
+ DbConnectUtil.getMysqlDataSource(req.getHost(), req.getPort(), req.getUsername(), req
+ .getPassword(), null, null);
+ checkRepeat(req, null);
+ }
+ }
+
+ /**
+ * 验证重复数据
+ */
+ private void checkRepeat(TenantDbConnectReq req, Long id) {
+ CheckUtils.throwIf(baseMapper.exists(Wrappers.lambdaQuery(TenantDbConnectDO.class)
+ .eq(TenantDbConnectDO::getHost, req.getHost())
+ .eq(TenantDbConnectDO::getPort, req.getPort())
+ .eq(TenantDbConnectDO::getUsername, req.getUsername())
+ .ne(id != null, TenantDbConnectDO::getId, id)), "数据库连接已存在");
+ }
+
+ @Override
+ protected void beforeUpdate(TenantDbConnectReq req, Long id) {
+ TenantConnectTypeEnum connectTypeEnum = TenantConnectTypeEnum.getByOrdinal(req.getType());
+ if (TenantConnectTypeEnum.MYSQL.equals(connectTypeEnum)) {
+ DbConnectUtil.getMysqlDataSource(req.getHost(), req.getPort(), req.getUsername(), req
+ .getPassword(), null, null);
+ checkRepeat(req, id);
+ }
+ }
+
+ @Override
+ protected void beforeDelete(List ids) {
+ CheckUtils.throwIf(tenantMapper.selectCount(Wrappers.lambdaQuery(TenantDO.class)
+ .in(TenantDO::getDbConnectId, ids)) > 0, "存在关联租户无法删除");
+ }
+
+ @Override
+ protected void afterUpdate(TenantDbConnectReq req, TenantDbConnectDO entity) {
+ RedisUtils.delete(CacheConstants.DB_CONNECT_KEY_PREFIX + entity.getId());
+ }
+
+ @Override
+ protected void afterDelete(List ids) {
+ ids.forEach(id -> RedisUtils.delete(CacheConstants.DB_CONNECT_KEY_PREFIX + id));
+ }
+
+ @Override
+ public JdbcTemplate getConnectJdbcTemplateById(Long id) {
+ TenantDbConnectDetailResp dbConnectReq = get(id);
+ TenantConnectTypeEnum connectTypeEnum = TenantConnectTypeEnum.getByOrdinal(dbConnectReq.getType());
+ if (TenantConnectTypeEnum.MYSQL.equals(connectTypeEnum)) {
+ DataSource dataSource = DbConnectUtil.getMysqlDataSource(dbConnectReq.getHost(), dbConnectReq
+ .getPort(), dbConnectReq.getUsername(), dbConnectReq.getPassword(), null, null);
+ return new JdbcTemplate(dataSource);
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantPackageServiceImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantPackageServiceImpl.java
new file mode 100644
index 00000000..2b90155e
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantPackageServiceImpl.java
@@ -0,0 +1,71 @@
+/*
+ * 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.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.json.JSONArray;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import top.continew.admin.common.base.service.BaseServiceImpl;
+import top.continew.admin.tenant.mapper.TenantMapper;
+import top.continew.admin.tenant.mapper.TenantPackageMapper;
+import top.continew.admin.tenant.model.entity.TenantDO;
+import top.continew.admin.tenant.model.entity.TenantPackageDO;
+import top.continew.admin.tenant.model.query.TenantPackageQuery;
+import top.continew.admin.tenant.model.req.TenantPackageReq;
+import top.continew.admin.tenant.model.resp.TenantPackageDetailResp;
+import top.continew.admin.tenant.model.resp.TenantPackageResp;
+import top.continew.admin.tenant.service.TenantPackageService;
+import top.continew.starter.core.util.validation.CheckUtils;
+
+import java.util.List;
+
+/**
+ * 租户套餐业务实现
+ *
+ * @author 小熊
+ * @since 2024/11/26 11:25
+ */
+@Service
+@RequiredArgsConstructor
+public class TenantPackageServiceImpl extends BaseServiceImpl implements TenantPackageService {
+
+ private final TenantMapper tenantMapper;
+
+ @Override
+ public TenantPackageDetailResp get(Long id) {
+ TenantPackageDO tenantPackageDO = getById(id);
+ TenantPackageDetailResp packageDetailResp = BeanUtil
+ .copyProperties(tenantPackageDO, TenantPackageDetailResp.class);
+ packageDetailResp.setMenuIds(new JSONArray(tenantPackageDO.getMenuIds()).toList(Long.class));
+ fill(packageDetailResp);
+ return packageDetailResp;
+ }
+
+ @Override
+ protected void beforeCreate(TenantPackageReq req) {
+ CheckUtils.throwIf(baseMapper.selectCount(Wrappers.lambdaQuery(TenantPackageDO.class)
+ .eq(TenantPackageDO::getName, req.getName())) > 0, "租户套餐名称不能重复");
+ }
+
+ @Override
+ protected void beforeDelete(List ids) {
+ CheckUtils.throwIf(tenantMapper.selectCount(Wrappers.lambdaQuery(TenantDO.class)
+ .in(TenantDO::getPackageId, ids)) > 0, "存在关联租户无法删除");
+ }
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantProviderImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantProviderImpl.java
new file mode 100644
index 00000000..e5bccde9
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantProviderImpl.java
@@ -0,0 +1,82 @@
+/*
+ * 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.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.zaxxer.hikari.HikariConfig;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import top.continew.admin.common.constant.SysConstants;
+import top.continew.admin.common.enums.DisEnableStatusEnum;
+import top.continew.admin.tenant.model.entity.TenantDO;
+import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
+import top.continew.admin.tenant.service.TenantDbConnectService;
+import top.continew.admin.tenant.service.TenantService;
+import top.continew.admin.tenant.util.DbConnectUtil;
+import top.continew.starter.core.util.validation.CheckUtils;
+import top.continew.starter.extension.tenant.config.TenantDataSource;
+import top.continew.starter.extension.tenant.config.TenantProvider;
+import top.continew.starter.extension.tenant.context.TenantContext;
+import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
+
+/**
+ * @description: 租户数据源提供者实现
+ * @author: 小熊
+ * @create: 2024-12-12 15:35
+ */
+@Service
+@RequiredArgsConstructor
+public class TenantProviderImpl implements TenantProvider {
+
+ private final TenantService tenantService;
+ private final TenantDbConnectService tenantDbConnectService;
+
+ @Override
+ public TenantContext getByTenantId(String tenantId, boolean verify) {
+ TenantContext context = new TenantContext();
+ if (StrUtil.isNotEmpty(tenantId) && !SysConstants.DEFAULT_TENANT.equals(tenantId)) {
+ Long longTenantId = Long.valueOf(tenantId);
+ TenantDO tenantDO = tenantService.getTenantById(longTenantId);
+ CheckUtils.throwIfNull(tenantDO, "租户[{}]不存在", tenantId);
+ CheckUtils.throwIf(verify && DisEnableStatusEnum.DISABLE.getValue()
+ .equals(tenantDO.getStatus()), "租户[{}]已被禁用", tenantId);
+ context.setTenantId(longTenantId);
+ TenantIsolationLevel isolationLevel = TenantIsolationLevel.DATASOURCE.ordinal() == tenantDO
+ .getIsolationLevel() ? TenantIsolationLevel.DATASOURCE : TenantIsolationLevel.LINE;
+ context.setIsolationLevel(isolationLevel);
+ if (isolationLevel.equals(TenantIsolationLevel.DATASOURCE)) {
+ TenantDbConnectDetailResp dbConnectReq = tenantDbConnectService.get(tenantDO.getDbConnectId());
+ String dbName = SysConstants.TENANT_DB_PREFIX + tenantDO.getTenantSn();
+ HikariConfig hikariConfig = DbConnectUtil.formatHikariConfig(dbConnectReq.getHost(), dbConnectReq
+ .getPort(), dbConnectReq.getUsername(), dbConnectReq.getPassword(), dbName, DbConnectUtil
+ .getDefaultMysqlConnectParameter());
+ TenantDataSource source = new TenantDataSource();
+ source.setPoolName(tenantId);
+ source.setDriverClassName(hikariConfig.getDriverClassName());
+ source.setUrl(hikariConfig.getJdbcUrl());
+ source.setUsername(hikariConfig.getUsername());
+ source.setPassword(hikariConfig.getPassword());
+ context.setDataSource(source);
+ }
+ } else {
+ context.setTenantId(Long.valueOf(SysConstants.DEFAULT_TENANT));
+ context.setIsolationLevel(TenantIsolationLevel.LINE);
+ }
+ return context;
+ }
+
+}
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java
new file mode 100644
index 00000000..64cb4fa8
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java
@@ -0,0 +1,202 @@
+/*
+ * 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.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.resource.ClassPathResource;
+import cn.hutool.core.io.resource.Resource;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import com.alicp.jetcache.anno.Cached;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import top.continew.admin.common.base.service.BaseServiceImpl;
+import top.continew.admin.common.config.properties.TenantProperties;
+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.tenant.mapper.TenantMapper;
+import top.continew.admin.tenant.mapper.TenantPackageMapper;
+import top.continew.admin.tenant.model.entity.TenantDO;
+import top.continew.admin.tenant.model.entity.TenantPackageDO;
+import top.continew.admin.tenant.model.query.TenantQuery;
+import top.continew.admin.tenant.model.req.TenantReq;
+import top.continew.admin.tenant.model.resp.TenantAvailableResp;
+import top.continew.admin.tenant.model.resp.TenantDetailResp;
+import top.continew.admin.tenant.model.resp.TenantResp;
+import top.continew.admin.tenant.service.TenantDbConnectService;
+import top.continew.admin.tenant.service.TenantService;
+import top.continew.starter.cache.redisson.util.RedisUtils;
+import top.continew.starter.core.util.validation.CheckUtils;
+import top.continew.starter.core.util.validation.ValidationUtils;
+import top.continew.starter.extension.crud.model.entity.BaseIdDO;
+import top.continew.starter.extension.crud.model.query.PageQuery;
+import top.continew.starter.extension.crud.model.resp.PageResp;
+import top.continew.starter.extension.tenant.context.TenantContextHolder;
+import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 租户业务实现
+ *
+ * @author 小熊
+ * @since 2024/11/26 17:20
+ */
+@Service
+@RequiredArgsConstructor
+public class TenantServiceImpl extends BaseServiceImpl implements TenantService {
+
+ private final TenantPackageMapper packageMapper;
+ private final TenantProperties tenantProperties;
+ private final TenantDbConnectService dbConnectService;
+
+ @Override
+ protected void beforeCreate(TenantReq req) {
+ //租户名称不能重复
+ ValidationUtils.throwIf(baseMapper.exists(Wrappers.lambdaQuery(TenantDO.class)
+ .eq(TenantDO::getName, req.getName())), "重复的租户名称");
+ //录入随机的六位租户编号
+ req.setTenantSn(generateTenantSn());
+ }
+
+ /**
+ * 生成六位随机不重复的编号
+ */
+ private String generateTenantSn() {
+ String tenantSn;
+ do {
+ tenantSn = RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER_LOWER, 6);
+ } while (baseMapper.exists(Wrappers.lambdaQuery(TenantDO.class).eq(TenantDO::getTenantSn, tenantSn)));
+ return tenantSn;
+ }
+
+ @Override
+ protected void afterCreate(TenantReq req, TenantDO entity) {
+ //数据源级别的租户需要创建数据库
+ if (entity.getIsolationLevel().equals(TenantIsolationLevel.DATASOURCE.ordinal())) {
+ JdbcTemplate jdbcTemplate = dbConnectService.getConnectJdbcTemplateById(entity.getDbConnectId());
+ String dbName = SysConstants.TENANT_DB_PREFIX + entity.getTenantSn();
+ //建库
+ jdbcTemplate.execute(StrUtil
+ .format("CREATE DATABASE {} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;", dbName));
+ jdbcTemplate.execute(StrUtil.format("USE {};", dbName));
+ //建表
+ Resource resource = new ClassPathResource("db/changelog/mysql/tenant_table.sql");
+ String tableSql = resource.readUtf8Str();
+ Arrays.stream(tableSql.split(";"))
+ .map(String::trim)
+ .filter(sql -> !sql.isEmpty())
+ .forEach(jdbcTemplate::execute);
+ }
+ }
+
+ @Override
+ public List getAvailableList() {
+ List tenantDOS = baseMapper.selectList(Wrappers.lambdaQuery(TenantDO.class)
+ .select(TenantDO::getName, BaseIdDO::getId, TenantDO::getDomain)
+ .eq(TenantDO::getStatus, DisEnableStatusEnum.ENABLE.getValue())
+ .and(t -> t.isNull(TenantDO::getExpireTime).or().ge(TenantDO::getExpireTime, DateUtil.date())));
+ return BeanUtil.copyToList(tenantDOS, TenantAvailableResp.class);
+ }
+
+ @Override
+ public PageResp page(TenantQuery query, PageQuery pageQuery) {
+ QueryWrapper queryWrapper = Wrappers.query(TenantQuery.class)
+ .eq(query.getPackageId() != null, "package_id", query.getPackageId())
+ .like(StrUtil.isNotEmpty(query.getName()), "sys_tenant.name", query.getName());
+ this.sort(queryWrapper, pageQuery);
+ IPage list = baseMapper.listTenant(new Page<>(pageQuery.getPage(), pageQuery
+ .getSize()), queryWrapper);
+ PageResp pageResp = PageResp.build(list, TenantResp.class);
+ return pageResp;
+ }
+
+ @Override
+ public TenantDetailResp get(Long id) {
+ TenantDetailResp detailResp = new TenantDetailResp();
+ TenantDO tenantDO = getById(id);
+ if (tenantDO != null) {
+ BeanUtil.copyProperties(tenantDO, detailResp);
+ TenantPackageDO packageDO = packageMapper.selectById(tenantDO.getPackageId());
+ if (packageDO != null) {
+ detailResp.setPackageName(packageDO.getName());
+ detailResp.setMenuIds(new JSONArray(packageDO.getMenuIds()).toList(Long.class));
+ }
+ }
+ fill(detailResp);
+ return detailResp;
+ }
+
+ @Override
+ public void bindUser(Long tenantId, Long userId) {
+ update(Wrappers.lambdaUpdate(TenantDO.class).set(TenantDO::getUserId, userId).eq(BaseIdDO::getId, tenantId));
+ TenantDO entity = getById(tenantId);
+ RedisUtils.set(CacheConstants.TENANT_KEY + tenantId, entity);
+ }
+
+ @Override
+ public void checkStatus() {
+ if (tenantProperties.isEnabled()) {
+ Long tenantId = TenantContextHolder.getTenantId();
+ CheckUtils.throwIfNull(tenantId, "未选择租户");
+ if (tenantId != 0) {
+ TenantDO tenantDO = baseMapper.selectById(tenantId);
+ CheckUtils.throwIfNull(tenantDO, "租户不存在");
+ CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE.getValue(), tenantDO.getStatus(), "此租户已被禁用");
+ //租户过期
+ CheckUtils.throwIf(tenantDO.getExpireTime() != null && tenantDO.getExpireTime()
+ .isBefore(DateUtil.date().toLocalDateTime()), "租户已过期");
+ //套餐状态
+ TenantPackageDO packageDO = packageMapper.selectById(tenantDO.getPackageId());
+ CheckUtils.throwIfNull(tenantDO, "套餐不存在");
+ CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE.getValue(), packageDO.getStatus(), "此租户套餐已被禁用");
+ }
+ }
+ }
+
+ @Override
+ @Cached(name = CacheConstants.TENANT_KEY, key = "#id")
+ public TenantDO getTenantById(Long id) {
+ return baseMapper.selectById(id);
+ }
+
+ @Override
+ protected void afterUpdate(TenantReq req, TenantDO entity) {
+ RedisUtils.set(CacheConstants.TENANT_KEY + entity.getId(), entity);
+ }
+
+ @Override
+ protected void afterDelete(List ids) {
+ ids.forEach(id -> RedisUtils.delete(CacheConstants.TENANT_KEY + id));
+
+ }
+
+ @Override
+ public TenantDO getTenantByUserId(Long userId) {
+ return baseMapper.selectOne(Wrappers.lambdaQuery(TenantDO.class).eq(TenantDO::getUserId, userId));
+ }
+
+}
\ No newline at end of file
diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/util/DbConnectUtil.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/util/DbConnectUtil.java
new file mode 100644
index 00000000..e4254b7f
--- /dev/null
+++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/util/DbConnectUtil.java
@@ -0,0 +1,106 @@
+/*
+ * 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.util;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import top.continew.starter.core.exception.BusinessException;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @description: 数据连接工具类
+ * @author: 小熊
+ * @create: 2024-12-15 18:54
+ */
+public class DbConnectUtil {
+
+ /**
+ * 格式化HikariConfig
+ */
+ public static HikariConfig formatHikariConfig(String host,
+ Integer port,
+ String username,
+ String password,
+ String dbName,
+ Map parameter) {
+ String activeProfile = SpringUtil.getActiveProfile();
+ String jdbcUrl = StrUtil.format("jdbc:mysql://{}:{}", host, port);
+ String driverClassName = "com.mysql.cj.jdbc.Driver";
+ if ("dev".equals(activeProfile)) {
+ jdbcUrl = StrUtil.format("jdbc:p6spy:mysql://{}:{}", host, port);
+ driverClassName = "com.p6spy.engine.spy.P6SpyDriver";
+ }
+ if (StrUtil.isNotEmpty(dbName)) {
+ jdbcUrl = StrUtil.format("{}/{}", jdbcUrl, dbName);
+ }
+ if (parameter != null) {
+ jdbcUrl = StrUtil.format("{}?{}", jdbcUrl, MapUtil.join(parameter, "&", "="));
+ }
+ HikariConfig configuration = new HikariConfig();
+ configuration.setJdbcUrl(jdbcUrl);
+ configuration.setDriverClassName(driverClassName);
+ configuration.setUsername(username);
+ configuration.setPassword(password);
+ configuration.setConnectionTimeout(3000L);
+ return configuration;
+ }
+
+ /**
+ * 验证mysql连接有效性并返回数据源
+ */
+ public static DataSource getMysqlDataSource(String host,
+ Integer port,
+ String username,
+ String password,
+ String dbName,
+ Map parameter) {
+ try {
+ DataSource dataSource = new HikariDataSource(formatHikariConfig(host, port, username, password, dbName, parameter));
+ Connection connection = dataSource.getConnection();
+ connection.close();
+ return dataSource;
+ } catch (Exception e) {
+ throw new BusinessException("数据库连接失败,请检查基础配置信息");
+ }
+ }
+
+ /**
+ * 默认的mysql连接参数
+ *
+ * @return
+ */
+ public static Map getDefaultMysqlConnectParameter() {
+ Map parameter = new HashMap<>();
+ parameter.put("serverTimezone", "Asia/Shanghai");
+ parameter.put("useUnicode", "true");
+ parameter.put("characterEncoding", "utf8");
+ parameter.put("useSSL", "false");
+ parameter.put("allowMultiQueries", "true");
+ parameter.put("autoReconnect", "true");
+ parameter.put("maxReconnects", "10");
+ parameter.put("failOverReadOnly", "false");
+ return parameter;
+ }
+
+}
diff --git a/continew-plugin/pom.xml b/continew-plugin/pom.xml
index 092cf258..2ecaebe7 100644
--- a/continew-plugin/pom.xml
+++ b/continew-plugin/pom.xml
@@ -19,6 +19,7 @@
continew-plugin-schedule
continew-plugin-open
continew-plugin-generator
+ continew-plugin-tenant
diff --git a/continew-server/pom.xml b/continew-server/pom.xml
index 8ff192d1..32bc2959 100644
--- a/continew-server/pom.xml
+++ b/continew-server/pom.xml
@@ -64,6 +64,12 @@
liquibase-core
+
+
+ top.continew.admin
+ continew-plugin-tenant
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/continew-server/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java b/continew-server/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java
index 37fbdbc2..986344c1 100644
--- a/continew-server/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java
+++ b/continew-server/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java
@@ -23,6 +23,7 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
+import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
@@ -41,6 +42,8 @@ import top.continew.admin.system.service.UserService;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.StrUtils;
+import top.continew.starter.extension.tenant.TenantHandler;
+import top.continew.starter.extension.tenant.context.TenantContextHolder;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.model.LogRecord;
import top.continew.starter.log.model.LogRequest;
@@ -85,7 +88,10 @@ public class LogDaoLocalImpl implements LogDao {
logDO.setCreateTime(LocalDateTime.ofInstant(logRecord.getTimestamp(), ZoneId.systemDefault()));
// 设置操作人
this.setCreateUser(logDO, logRequest, logResponse);
- logMapper.insert(logDO);
+ Long tenantId = TenantContextHolder.getTenantId();
+ SpringUtil.getBean(TenantHandler.class).execute(tenantId, () -> {
+ logMapper.insert(logDO);
+ });
}
/**
diff --git a/continew-server/src/main/java/top/continew/admin/config/tenant/DataSourceSwitchAspect.java b/continew-server/src/main/java/top/continew/admin/config/tenant/DataSourceSwitchAspect.java
new file mode 100644
index 00000000..deecf743
--- /dev/null
+++ b/continew-server/src/main/java/top/continew/admin/config/tenant/DataSourceSwitchAspect.java
@@ -0,0 +1,59 @@
+/*
+ * 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.config.tenant;
+
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import org.aspectj.lang.annotation.After;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+import top.continew.admin.common.constant.SysConstants;
+import top.continew.starter.core.constant.PropertiesConstants;
+import top.continew.starter.extension.tenant.context.TenantContextHolder;
+import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
+
+/**
+ * @description: 主数据源切面
+ * @author: 小熊
+ * @create: 2025-01-15 16:02
+ */
+@Aspect
+@Component
+@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true")
+public class DataSourceSwitchAspect {
+
+ @Pointcut("execution(* top.continew.admin.tenant.mapper..*(..)) || " + "execution(* top.continew.admin.tenant.service..*(..)) || " + "execution(* top.continew.admin.system.mapper.ClientMapper.*(..)) || " + "execution(* top.continew.admin.system.service.ClientService.*(..)) || " + "execution(* top.continew.admin.system.mapper.DictMapper.*(..)) || " + "execution(* top.continew.admin.system.service.DictService.*(..)) || " + "execution(* top.continew.admin.system.mapper.DictItemMapper.*(..)) || " + "execution(* top.continew.admin.system.service.DictItemService.*(..)) || " + "execution(* top.continew.admin.system.mapper.OptionMapper.*(..)) || " + "execution(* top.continew.admin.system.service.OptionService.*(..)) || " + "execution(* top.continew.admin.system.mapper.StorageMapper.*(..)) || " + "execution(* top.continew.admin.system.service.StorageService.*(..))")
+ public void MasterDataSourceMethods() {
+ }
+
+ @Before("MasterDataSourceMethods()")
+ public void switchToMasterDataSource() {
+ if (TenantContextHolder.getIsolationLevel() == TenantIsolationLevel.DATASOURCE) {
+ DynamicDataSourceContextHolder.push(SysConstants.DEFAULT_DATASOURCE);
+ }
+ }
+
+ @After("MasterDataSourceMethods()")
+ public void clearDataSourceContext() {
+ if (TenantContextHolder.getIsolationLevel() == TenantIsolationLevel.DATASOURCE) {
+ DynamicDataSourceContextHolder.poll();
+ }
+ }
+
+}
diff --git a/continew-server/src/main/resources/config/application-dev.yml b/continew-server/src/main/resources/config/application-dev.yml
index e837da3e..a7e88c3d 100644
--- a/continew-server/src/main/resources/config/application-dev.yml
+++ b/continew-server/src/main/resources/config/application-dev.yml
@@ -11,29 +11,37 @@ server:
--- ### 数据源配置
spring.datasource:
type: com.zaxxer.hikari.HikariDataSource
- # 请务必提前创建好名为 continew_admin 的数据库,如果使用其他数据库名请注意同步修改 DB_NAME 配置
- url: jdbc:p6spy:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
- username: ${DB_USER:root}
- password: ${DB_PWD:123456}
- driver-class-name: com.p6spy.engine.spy.P6SpyDriver
-# # PostgreSQL 配置
-# url: jdbc:p6spy:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&stringtype=unspecified
-# username: ${DB_USER:postgres}
-# password: ${DB_PWD:123456}
-# driver-class-name: com.p6spy.engine.spy.P6SpyDriver
- # Hikari 连接池配置
- hikari:
- # 最大连接数量(默认 10,根据实际环境调整)
- # 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
- maximum-pool-size: 20
- # 获取连接超时时间(默认 30000 毫秒,30 秒)
- connection-timeout: 30000
- # 空闲连接最大存活时间(默认 600000 毫秒,10 分钟)
- idle-timeout: 600000
- # 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime(默认 0,禁用)
- keepaliveTime: 30000
- # 连接最大生存时间(默认 1800000 毫秒,30 分钟)
- max-lifetime: 1800000
+ ## 动态数据源配置(可配多主多从:m1、s1...;纯粹多库:mysql、oracle...;混合配置:m1、s1、oracle...)
+ dynamic:
+ # 是否启用 P6Spy(SQL 性能分析组件,该插件有性能损耗,不建议生产环境使用)
+ p6spy: true
+ # 设置默认的数据源或者数据源组(默认:master)
+ primary: master
+ # 严格匹配数据源(true:未匹配到指定数据源时抛异常;false:使用默认数据源;默认 false)
+ strict: false
+ datasource:
+ # 主库配置(可配多个,构成多主)
+ master:
+ url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
+ username: ${DB_USER:root}
+ password: ${DB_PWD:123456}
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ type: ${spring.datasource.type}
+ # Hikari 连接池配置(完整配置请参阅:https://github.com/brettwooldridge/HikariCP)
+ hikari:
+ # 最大连接数量(默认 10,根据实际环境调整)
+ # 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
+ maximum-pool-size: 20
+ # 获取连接超时时间(默认 30000 毫秒,30 秒)
+ connection-timeout: 30000
+ # 空闲连接最大存活时间(默认 600000 毫秒,10 分钟)
+ idle-timeout: 600000
+ # 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime(默认 0,禁用)
+ keepaliveTime: 30000
+ # 连接最大生存时间(默认 1800000 毫秒,30 分钟)
+ max-lifetime: 1800000
+
+
## Liquibase 配置
spring.liquibase:
# 是否启用
@@ -168,7 +176,7 @@ captcha:
expirationInMinutes: 5
--- ### 短信配置
-## 提示:配置文件方式和 [系统管理/系统配置/短信配置] 功能可任选其一方式使用,也可共同使用,但实际开发时建议选择一种,注释或删除另一方
+## 提示:配置文件方式和 [短信配置] 功能可任选其一方式使用,也可共同使用,但实际开发时建议选择一种,注释或删除另一方
sms:
http-log: true
is-print: true
@@ -184,7 +192,7 @@ sms:
# sdk-app-id: 你的应用ID
--- ### 邮件配置
-## 提示:配置文件方式和 [系统管理/系统配置/邮件配置] 功能可任选其一方式使用,实际开发时请注释或删除另一方
+## 提示:配置文件方式和 [邮件配置] 功能可任选其一方式使用,实际开发时请注释或删除另一方
#spring.mail:
# # 根据需要更换
# host: smtp.126.com
diff --git a/continew-server/src/main/resources/config/application-prod.yml b/continew-server/src/main/resources/config/application-prod.yml
index 389ba631..94815ecf 100644
--- a/continew-server/src/main/resources/config/application-prod.yml
+++ b/continew-server/src/main/resources/config/application-prod.yml
@@ -13,29 +13,36 @@ server:
--- ### 数据源配置
spring.datasource:
type: com.zaxxer.hikari.HikariDataSource
- # 请务必提前创建好名为 continew_admin 的数据库,如果使用其他数据库名请注意同步修改 DB_NAME 配置
- url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
- username: ${DB_USER:root}
- password: ${DB_PWD:123456}
- driver-class-name: com.mysql.cj.jdbc.Driver
-# # PostgreSQL 配置
-# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&stringtype=unspecified
-# username: ${DB_USER:postgres}
-# password: ${DB_PWD:123456}
-# driver-class-name: org.postgresql.Driver
- # Hikari 连接池配置
- hikari:
- # 最大连接数量(默认 10,根据实际环境调整)
- # 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
- maximum-pool-size: 20
- # 获取连接超时时间(默认 30000 毫秒,30 秒)
- connection-timeout: 30000
- # 空闲连接最大存活时间(默认 600000 毫秒,10 分钟)
- idle-timeout: 600000
- # 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime(默认 0,禁用)
- keepaliveTime: 30000
- # 连接最大生存时间(默认 1800000 毫秒,30 分钟)
- max-lifetime: 1800000
+ ## 动态数据源配置(可配多主多从:m1、s1...;纯粹多库:mysql、oracle...;混合配置:m1、s1、oracle...)
+ dynamic:
+ # 是否启用 P6Spy(SQL 性能分析组件,该插件有性能损耗,不建议生产环境使用)
+ p6spy: false
+ # 设置默认的数据源或者数据源组(默认:master)
+ primary: master
+ # 严格匹配数据源(true:未匹配到指定数据源时抛异常;false:使用默认数据源;默认 false)
+ strict: false
+ datasource:
+ # 主库配置(可配多个,构成多主)
+ master:
+ url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
+ username: ${DB_USER:root}
+ password: ${DB_PWD:123456}
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ type: ${spring.datasource.type}
+ # Hikari 连接池配置(完整配置请参阅:https://github.com/brettwooldridge/HikariCP)
+ hikari:
+ # 最大连接数量(默认 10,根据实际环境调整)
+ # 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
+ maximum-pool-size: 20
+ # 获取连接超时时间(默认 30000 毫秒,30 秒)
+ connection-timeout: 30000
+ # 空闲连接最大存活时间(默认 600000 毫秒,10 分钟)
+ idle-timeout: 600000
+ # 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime(默认 0,禁用)
+ keepaliveTime: 30000
+ # 连接最大生存时间(默认 1800000 毫秒,30 分钟)
+ max-lifetime: 1800000
+
## Liquibase 配置
spring.liquibase:
# 是否启用
diff --git a/continew-server/src/main/resources/config/application.yml b/continew-server/src/main/resources/config/application.yml
index ebbaba9e..4856d5b8 100644
--- a/continew-server/src/main/resources/config/application.yml
+++ b/continew-server/src/main/resources/config/application.yml
@@ -261,7 +261,6 @@ mybatis-plus:
# 分页插件配置
pagination:
enabled: true
- db-type: MYSQL
--- ### CosId 配置
cosid:
@@ -299,3 +298,32 @@ auth:
- /auth/logout
- /system/user/password
+#多租户配置
+continew-starter.tenant:
+ enabled: true
+ # 多租户忽略的表
+ ignore-tables:
+ - gen_config # 代码生成
+ - gen_field_config
+ - sys_dict # 字典表
+ - sys_dict_item
+ - sys_option #参数
+ - sys_storage # 存储配置
+ - sys_tenant # 租户
+ - sys_tenant_package
+ - sys_tenant_db_connect
+ - sys_app #应用
+ - sys_client #客户端管理
+ - sys_sms_config
+ - sys_sms_log
+ #租户不能使用的菜单
+ ignore-menus:
+ - 1130 #字典管理
+ - 1140
+ - 1150 #系统配置
+ - 2050 #短信日志
+ - 3000 #任务调度
+ - 9000 #代码生成
+ - 7000 #能力开放
+ - 7010 #应用管理
+ - 10010 #租户管理
\ No newline at end of file
diff --git a/continew-server/src/main/resources/db/changelog/db.changelog-master.yaml b/continew-server/src/main/resources/db/changelog/db.changelog-master.yaml
index feefa21e..fe77c111 100644
--- a/continew-server/src/main/resources/db/changelog/db.changelog-master.yaml
+++ b/continew-server/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -11,6 +11,8 @@ databaseChangeLog:
file: db/changelog/mysql/plugin/plugin_open.sql
- include:
file: db/changelog/mysql/plugin/plugin_generator.sql
+ - include:
+ file: db/changelog/mysql/plugin/plugin_tenant.sql
# PostgreSQL
# - include:
# file: db/changelog/postgresql/main_table.sql
diff --git a/continew-server/src/main/resources/db/changelog/mysql/plugin/plugin_tenant.sql b/continew-server/src/main/resources/db/changelog/mysql/plugin/plugin_tenant.sql
new file mode 100644
index 00000000..9744d9f1
--- /dev/null
+++ b/continew-server/src/main/resources/db/changelog/mysql/plugin/plugin_tenant.sql
@@ -0,0 +1,145 @@
+-- liquibase formatted sql
+
+-- changeset 小熊:1
+-- comment 初始化表结构
+
+-- ----------------------------
+-- Table structure for sys_tenant
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_tenant`;
+CREATE TABLE `sys_tenant` (
+`id` bigint NOT NULL COMMENT 'ID',
+`name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '租户名称',
+`tenant_sn` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '租户编号',
+`user_id` bigint DEFAULT NULL COMMENT '租户对应的用户',
+`domain` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '绑定的域名',
+`package_id` bigint NOT NULL COMMENT '租户套餐编号',
+`status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '状态(1:启用;2:禁用)',
+`expire_time` datetime DEFAULT NULL COMMENT '租户过期时间',
+`create_user` bigint NOT NULL COMMENT '创建人',
+`create_time` datetime NOT NULL COMMENT '创建时间',
+`update_user` bigint DEFAULT NULL COMMENT '修改人',
+`update_time` datetime DEFAULT NULL COMMENT '修改时间',
+`isolation_level` tinyint unsigned NOT NULL COMMENT '隔离级别',
+`db_connect_id` bigint DEFAULT NULL COMMENT '数据连接ID',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='租户表';
+
+-- ----------------------------
+-- Table structure for sys_tenant_package
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_tenant_package`;
+CREATE TABLE `sys_tenant_package` (
+ `id` bigint NOT NULL COMMENT 'ID',
+ `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '套餐名称',
+ `menu_ids` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '关联的菜单ids',
+ `menu_check_strictly` bit(1) DEFAULT b'0' COMMENT '菜单选择是否父子节点关联',
+ `status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '状态(1:启用;2:禁用)',
+ `create_user` bigint NOT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='租户套餐表';
+
+-- ----------------------------
+-- Table structure for sys_tenant_db_connect
+-- ----------------------------
+CREATE TABLE `sys_tenant_db_connect` (
+ `id` bigint NOT NULL COMMENT 'ID',
+ `connect_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '连接名称',
+ `type` tinyint(1) NOT NULL COMMENT '连接类型',
+ `host` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '连接主机地址',
+ `port` smallint NOT NULL COMMENT '连接端口',
+ `username` varchar(128) NOT NULL COMMENT '连接用户名',
+ `password` varchar(128) NOT NULL COMMENT '连接密码',
+ `create_user` bigint NOT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='租户数据库连接表';
+
+
+-- changeset 小熊:2
+-- comment 添加租户列和索引
+ALTER TABLE sys_app ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_app_tenant_id ON sys_app(tenant_id);
+ALTER TABLE sys_dept ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_dept_tenant_id ON sys_dept(tenant_id);
+ALTER TABLE sys_file ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_file_tenant_id ON sys_file(tenant_id);
+ALTER TABLE sys_log ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_log_tenant_id ON sys_log(tenant_id);
+ALTER TABLE sys_menu ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_menu_tenant_id ON sys_menu(tenant_id);
+ALTER TABLE sys_message ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_message_tenant_id ON sys_message(tenant_id);
+ALTER TABLE sys_message_log ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_message_log_tenant_id ON sys_message_log(tenant_id);
+ALTER TABLE sys_notice ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_notice_tenant_id ON sys_notice(tenant_id);
+ALTER TABLE sys_notice_log ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_notice_log_tenant_id ON sys_notice_log(tenant_id);
+ALTER TABLE sys_role ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_role_tenant_id ON sys_role(tenant_id);
+ALTER TABLE sys_user ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_user_tenant_id ON sys_user(tenant_id);
+ALTER TABLE sys_role_dept ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_role_dept_tenant_id ON sys_role_dept(tenant_id);
+ALTER TABLE sys_role_menu ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_role_menu_tenant_id ON sys_role_menu(tenant_id);
+ALTER TABLE sys_user_password_history ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_user_password_history_tenant_id ON sys_user_password_history(tenant_id);
+ALTER TABLE sys_user_role ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_user_role_tenant_id ON sys_user_role(tenant_id);
+ALTER TABLE sys_user_social ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 0;
+CREATE INDEX idx_sys_user_social_tenant_id ON sys_user_social(tenant_id);
+
+-- changeset 小熊:3
+-- comment 唯一索引变更
+ALTER TABLE sys_app DROP INDEX `uk_access_key`;
+ALTER TABLE sys_app ADD UNIQUE INDEX `uk_access_key` (`access_key`, `tenant_id`);
+ALTER TABLE sys_dept DROP INDEX `uk_name_parent_id`;
+ALTER TABLE sys_dept ADD UNIQUE INDEX `uk_name_parent_id` (`name`, `parent_id`, `tenant_id`);
+ALTER TABLE sys_menu DROP INDEX `uk_title_parent_id`;
+ALTER TABLE sys_menu ADD UNIQUE INDEX `uk_title_parent_id` (`title`, `parent_id`, `tenant_id`);
+ALTER TABLE sys_role DROP INDEX `uk_name`;
+ALTER TABLE sys_role ADD UNIQUE INDEX `uk_name` (`name`, `tenant_id`);
+ALTER TABLE sys_role DROP INDEX `uk_code`;
+ALTER TABLE sys_role ADD UNIQUE INDEX `uk_code` (`code`, `tenant_id`);
+ALTER TABLE sys_user DROP INDEX `uk_username`;
+ALTER TABLE sys_user ADD UNIQUE INDEX `uk_username` (`username`, `tenant_id`);
+ALTER TABLE sys_user DROP INDEX `uk_email`;
+ALTER TABLE sys_user ADD UNIQUE INDEX `uk_email` (`email`, `tenant_id`);
+ALTER TABLE sys_user DROP INDEX `uk_phone`;
+ALTER TABLE sys_user ADD UNIQUE INDEX `uk_phone` (`phone`, `tenant_id`);
+
+-- changeset 小熊:4
+-- comment 菜单录入
+INSERT INTO `sys_menu`
+(`id`, `title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`, `update_user`, `update_time`, `tenant_id`)
+VALUES
+(10010, '租户管理', 0, 1, '/tenant', 'Tenant', 'Layout', '/tenant/user', 'user-group', b'0', b'0', b'0', NULL, 6, 1, 1, NOW(), NULL, NULL, 0),
+(10015, '租户套餐', 10010, 2, '/tenant/package', 'TenantPackage', 'tenant/package/index', NULL, 'menu', b'0', b'0', b'0', NULL, 2, 1, 1, NOW(), NULL, NULL, 0),
+(10016, '列表', 10015, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:package:list', 1, 1, 1, NOW(), NULL, NULL, 0),
+(10017, '详情', 10015, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:package:detail', 2, 1, 1, NOW(), NULL, NULL, 0),
+(10018, '新增', 10015, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:package:add', 3, 1, 1, NOW(), NULL, NULL, 0),
+(10019, '修改', 10015, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:package:update', 4, 1, 1, NOW(), NULL, NULL, 0),
+(10020, '删除', 10015, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:package:delete', 5, 1, 1, NOW(), NULL, NULL, 0),
+(10022, '租户管理', 10010, 2, '/tenant/user', 'TenantUser', 'tenant/user/index', NULL, 'user-group', b'0', b'0', b'0', NULL, 1, 1, 1, NOW(), NULL, NULL, 0),
+(10023, '列表', 10022, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:user:list', 1, 1, 1, NOW(), NULL, NULL, 0),
+(10024, '详情', 10022, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:user:detail', 2, 1, 1, NOW(), NULL, NULL, 0),
+(10025, '新增', 10022, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:user:add', 3, 1, 1, NOW(), NULL, NULL, 0),
+(10026, '修改', 10022, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:user:update', 4, 1, 1, NOW(), NULL, NULL, 0),
+(10027, '删除', 10022, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'tenant:user:delete', 5, 1, 1, NOW(), NULL, NULL, 0),
+(10028, '账号更新', 10022, 3, NULL, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'tenant:user:editLoginUserInfo', 6, 1, 1, NOW(), NULL, NULL, 0),
+(10040, '数据连接', 10010, 2, '/tenant/dbConnect', 'TenantDbConnect', 'tenant/tenantDbConnect/index', NULL, 'storage', b'0', b'0', b'0', NULL, 3, 1, 1, NOW(), NULL, NULL, 0),
+(10041, '列表', 10040, 3, NULL, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'tenant:dbConnect:list', 1, 1, 1, NOW(), NULL, NULL, 0),
+(10042, '详情', 10040, 3, NULL, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'tenant:dbConnect:detail', 2, 1, 1, NOW(), NULL, NULL, 0),
+(10043, '新增', 10040, 3, NULL, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'tenant:dbConnect:add', 3, 1, 1, NOW(), NULL, NULL, 0),
+(10044, '修改', 10040, 3, NULL, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'tenant:dbConnect:update', 4, 1, 1, NOW(), NULL, NULL, 0),
+(10045, '删除', 10040, 3, NULL, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'tenant:dbConnect:delete', 5, 1, 1, NOW(), NULL, NULL, 0);
+
+
+
diff --git a/continew-server/src/main/resources/db/changelog/mysql/tenant_table.sql b/continew-server/src/main/resources/db/changelog/mysql/tenant_table.sql
new file mode 100644
index 00000000..0087b12e
--- /dev/null
+++ b/continew-server/src/main/resources/db/changelog/mysql/tenant_table.sql
@@ -0,0 +1,241 @@
+-- 数据源级别租户表结构
+
+CREATE TABLE IF NOT EXISTS `sys_menu` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `title` varchar(30) NOT NULL COMMENT '标题',
+ `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '上级菜单ID',
+ `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:目录;2:菜单;3:按钮)',
+ `path` varchar(255) DEFAULT NULL COMMENT '路由地址',
+ `name` varchar(50) DEFAULT NULL COMMENT '组件名称',
+ `component` varchar(255) DEFAULT NULL COMMENT '组件路径',
+ `redirect` varchar(255) DEFAULT NULL COMMENT '重定向地址',
+ `icon` varchar(50) DEFAULT NULL COMMENT '图标',
+ `is_external` bit(1) DEFAULT b'0' COMMENT '是否外链',
+ `is_cache` bit(1) DEFAULT b'0' COMMENT '是否缓存',
+ `is_hidden` bit(1) DEFAULT b'0' COMMENT '是否隐藏',
+ `permission` varchar(100) DEFAULT NULL COMMENT '权限标识',
+ `sort` int NOT NULL DEFAULT 999 COMMENT '排序',
+ `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)',
+ `create_user` bigint(20) NOT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `uk_title_parent_id`(`title`, `parent_id`),
+ INDEX `idx_parent_id`(`parent_id`),
+ INDEX `idx_create_user`(`create_user`),
+ INDEX `idx_update_user`(`update_user`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
+
+CREATE TABLE IF NOT EXISTS `sys_dept` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `name` varchar(30) NOT NULL COMMENT '名称',
+ `parent_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '上级部门ID',
+ `ancestors` varchar(512) NOT NULL DEFAULT '' COMMENT '祖级列表',
+ `description` varchar(200) DEFAULT NULL COMMENT '描述',
+ `sort` int NOT NULL DEFAULT 999 COMMENT '排序',
+ `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)',
+ `is_system` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为系统内置数据',
+ `create_user` bigint(20) NOT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `uk_name_parent_id`(`name`, `parent_id`),
+ INDEX `idx_parent_id`(`parent_id`),
+ INDEX `idx_create_user`(`create_user`),
+ INDEX `idx_update_user`(`update_user`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';
+
+CREATE TABLE IF NOT EXISTS `sys_role` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `name` varchar(30) NOT NULL COMMENT '名称',
+ `code` varchar(30) NOT NULL COMMENT '编码',
+ `data_scope` tinyint(1) NOT NULL DEFAULT 4 COMMENT '数据权限(1:全部数据权限;2:本部门及以下数据权限;3:本部门数据权限;4:仅本人数据权限;5:自定义数据权限)',
+ `description` varchar(200) DEFAULT NULL COMMENT '描述',
+ `sort` int NOT NULL DEFAULT 999 COMMENT '排序',
+ `is_system` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为系统内置数据',
+ `menu_check_strictly` bit(1) DEFAULT b'0' COMMENT '菜单选择是否父子节点关联',
+ `dept_check_strictly` bit(1) DEFAULT b'0' COMMENT '部门选择是否父子节点关联',
+ `create_user` bigint(20) NOT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `uk_name`(`name`),
+ UNIQUE INDEX `uk_code`(`code`),
+ INDEX `idx_create_user`(`create_user`),
+ INDEX `idx_update_user`(`update_user`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
+
+CREATE TABLE IF NOT EXISTS `sys_user` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `username` varchar(64) NOT NULL COMMENT '用户名',
+ `nickname` varchar(30) NOT NULL COMMENT '昵称',
+ `password` varchar(255) DEFAULT NULL COMMENT '密码',
+ `gender` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '性别(0:未知;1:男;2:女)',
+ `email` varchar(255) DEFAULT NULL COMMENT '邮箱',
+ `phone` varchar(255) DEFAULT NULL COMMENT '手机号码',
+ `avatar` longtext DEFAULT NULL COMMENT '头像',
+ `description` varchar(200) DEFAULT NULL COMMENT '描述',
+ `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)',
+ `is_system` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为系统内置数据',
+ `pwd_reset_time` datetime DEFAULT NULL COMMENT '最后一次修改密码时间',
+ `dept_id` bigint(20) NOT NULL COMMENT '部门ID',
+ `create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `uk_username`(`username`),
+ UNIQUE INDEX `uk_email`(`email`),
+ UNIQUE INDEX `uk_phone`(`phone`),
+ INDEX `idx_dept_id`(`dept_id`),
+ INDEX `idx_create_user`(`create_user`),
+ INDEX `idx_update_user`(`update_user`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
+
+CREATE TABLE IF NOT EXISTS `sys_user_password_history` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+ `password` varchar(255) NOT NULL COMMENT '密码',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ INDEX `idx_user_id`(`user_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户历史密码表';
+
+CREATE TABLE IF NOT EXISTS `sys_user_social` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `source` varchar(255) NOT NULL COMMENT '来源',
+ `open_id` varchar(255) NOT NULL COMMENT '开放ID',
+ `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+ `meta_json` text DEFAULT NULL COMMENT '附加信息',
+ `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `uk_source_open_id`(`source`, `open_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户社会化关联表';
+
+CREATE TABLE IF NOT EXISTS `sys_user_role` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+ `role_id` bigint(20) NOT NULL COMMENT '角色ID',
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `uk_user_id_role_id`(`user_id`, `role_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户和角色关联表';
+
+CREATE TABLE IF NOT EXISTS `sys_role_menu` (
+ `role_id` bigint(20) NOT NULL COMMENT '角色ID',
+ `menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
+ PRIMARY KEY (`role_id`, `menu_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色和菜单关联表';
+
+CREATE TABLE IF NOT EXISTS `sys_role_dept` (
+ `role_id` bigint(20) NOT NULL COMMENT '角色ID',
+ `dept_id` bigint(20) NOT NULL COMMENT '部门ID',
+ PRIMARY KEY (`role_id`, `dept_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色和部门关联表';
+
+
+CREATE TABLE IF NOT EXISTS `sys_log` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `trace_id` varchar(255) DEFAULT NULL COMMENT '链路ID',
+ `description` varchar(255) NOT NULL COMMENT '日志描述',
+ `module` varchar(100) NOT NULL COMMENT '所属模块',
+ `request_url` varchar(512) NOT NULL COMMENT '请求URL',
+ `request_method` varchar(10) NOT NULL COMMENT '请求方式',
+ `request_headers` text DEFAULT NULL COMMENT '请求头',
+ `request_body` text DEFAULT NULL COMMENT '请求体',
+ `status_code` int NOT NULL COMMENT '状态码',
+ `response_headers` text DEFAULT NULL COMMENT '响应头',
+ `response_body` mediumtext DEFAULT NULL COMMENT '响应体',
+ `time_taken` bigint(20) NOT NULL COMMENT '耗时(ms)',
+ `ip` varchar(100) DEFAULT NULL COMMENT 'IP',
+ `address` varchar(255) DEFAULT NULL COMMENT 'IP归属地',
+ `browser` varchar(100) DEFAULT NULL COMMENT '浏览器',
+ `os` varchar(100) DEFAULT NULL COMMENT '操作系统',
+ `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:成功;2:失败)',
+ `error_msg` text DEFAULT NULL COMMENT '错误信息',
+ `create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ INDEX `idx_module`(`module`),
+ INDEX `idx_ip`(`ip`),
+ INDEX `idx_address`(`address`),
+ INDEX `idx_create_time`(`create_time`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表';
+
+CREATE TABLE IF NOT EXISTS `sys_message` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `title` varchar(50) NOT NULL COMMENT '标题',
+ `content` text DEFAULT NULL COMMENT '内容',
+ `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:系统消息;2:安全消息)',
+ `path` varchar(255) DEFAULT NULL COMMENT '跳转路径',
+ `scope` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '通知范围(1:所有人;2:指定用户)',
+ `users` json DEFAULT NULL COMMENT '通知用户',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表';
+
+CREATE TABLE IF NOT EXISTS `sys_message_log` (
+ `message_id` bigint(20) NOT NULL COMMENT '消息ID',
+ `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+ `read_time` datetime DEFAULT NULL COMMENT '读取时间',
+ PRIMARY KEY (`message_id`, `user_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息日志表';
+
+CREATE TABLE IF NOT EXISTS `sys_notice` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `title` varchar(150) NOT NULL COMMENT '标题',
+ `content` mediumtext NOT NULL COMMENT '内容',
+ `type` varchar(30) NOT NULL COMMENT '分类',
+ `notice_scope` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '通知范围(1:所有人;2:指定用户)',
+ `notice_users` json DEFAULT NULL COMMENT '通知用户',
+ `notice_methods` json DEFAULT NULL COMMENT '通知方式(1:系统消息;2:登录弹窗)',
+ `is_timing` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否定时',
+ `publish_time` datetime DEFAULT NULL COMMENT '发布时间',
+ `is_top` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否置顶',
+ `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:草稿;2:待发布;3:已发布)',
+ `create_user` bigint(20) NOT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ INDEX `idx_create_user`(`create_user`),
+ INDEX `idx_update_user`(`update_user`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告表';
+
+CREATE TABLE IF NOT EXISTS `sys_notice_log` (
+ `notice_id` bigint(20) NOT NULL COMMENT '公告ID',
+ `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+ `read_time` datetime DEFAULT NULL COMMENT '读取时间',
+ PRIMARY KEY (`notice_id`, `user_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告日志表';
+
+
+CREATE TABLE IF NOT EXISTS `sys_file` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `name` varchar(255) NOT NULL COMMENT '名称',
+ `original_name` varchar(255) NOT NULL COMMENT '原始名称',
+ `size` bigint(20) DEFAULT NULL COMMENT '大小(字节)',
+ `parent_path` varchar(512) NOT NULL DEFAULT '/' COMMENT '上级目录',
+ `path` varchar(512) NOT NULL COMMENT '路径',
+ `extension` varchar(32) DEFAULT NULL COMMENT '扩展名',
+ `content_type` varchar(255) DEFAULT NULL COMMENT '内容类型',
+ `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(0: 目录;1:其他;2:图片;3:文档;4:视频;5:音频)',
+ `sha256` varchar(256) DEFAULT NULL COMMENT 'SHA256值',
+ `metadata` text DEFAULT NULL COMMENT '元数据',
+ `thumbnail_name` varchar(255) DEFAULT NULL COMMENT '缩略图名称',
+ `thumbnail_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小(字节)',
+ `thumbnail_metadata` text DEFAULT NULL COMMENT '缩略图元数据',
+ `storage_id` bigint(20) NOT NULL COMMENT '存储ID',
+ `create_user` bigint(20) NOT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ PRIMARY KEY (`id`),
+ INDEX `idx_type`(`type`),
+ INDEX `idx_sha256`(`sha256`),
+ INDEX `idx_storage_id`(`storage_id`),
+ INDEX `idx_create_user`(`create_user`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表';
diff --git a/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java b/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java
index 86dd31ee..1df4a16a 100644
--- a/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java
+++ b/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java
@@ -19,6 +19,7 @@ package top.continew.admin.auth;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.extra.spring.SpringUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -36,10 +37,13 @@ import top.continew.admin.system.service.DeptService;
import top.continew.admin.system.service.OptionService;
import top.continew.admin.system.service.RoleService;
import top.continew.admin.system.service.UserService;
+import top.continew.starter.core.util.ServletUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.Validator;
-import top.continew.starter.core.util.ServletUtils;
+import top.continew.starter.extension.tenant.TenantHandler;
+import top.continew.starter.extension.tenant.context.TenantContextHolder;
+import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -90,10 +94,21 @@ public abstract class AbstractLoginHandler implements LoginH
protected String authenticate(UserDO user, ClientResp client) {
// 获取权限、角色、密码过期天数
Long userId = user.getId();
- CompletableFuture> permissionFuture = CompletableFuture.supplyAsync(() -> roleService
- .listPermissionByUserId(userId), threadPoolTaskExecutor);
- CompletableFuture> roleFuture = CompletableFuture.supplyAsync(() -> roleService
- .listByUserId(userId), threadPoolTaskExecutor);
+ Long tenantId = TenantContextHolder.getTenantId();
+ CompletableFuture> permissionFuture = CompletableFuture.supplyAsync(() -> {
+ Set permissions = new HashSet<>();
+ SpringUtil.getBean(TenantHandler.class).execute(tenantId, () -> {
+ permissions.addAll(roleService.listPermissionByUserId(userId));
+ });
+ return permissions;
+ }, threadPoolTaskExecutor);
+ CompletableFuture> roleFuture = CompletableFuture.supplyAsync(() -> {
+ Set roles = new HashSet<>();
+ SpringUtil.getBean(TenantHandler.class).execute(tenantId, () -> {
+ roles.addAll(roleService.listByUserId(userId));
+ });
+ return roles;
+ }, threadPoolTaskExecutor);
CompletableFuture passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()));
CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture);
@@ -108,6 +123,7 @@ public abstract class AbstractLoginHandler implements LoginH
userContext.setClientType(client.getClientType());
loginParameter.setExtra(CLIENT_ID, client.getClientId());
userContext.setClientId(client.getClientId());
+ userContext.setTenantId(tenantId);
// 登录并缓存用户信息
StpUtil.login(userContext.getId(), loginParameter.setExtraData(BeanUtil
.beanToMap(new UserExtraContext(ServletUtils.getRequest()))));
diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java
index 87bd8f9d..e7fee1ed 100644
--- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java
@@ -123,4 +123,5 @@ public class AuthServiceImpl implements AuthService {
});
return BeanUtil.copyToList(treeList, RouteResp.class);
}
+
}
diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java
index f313c0c0..ad69e700 100644
--- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java
@@ -29,12 +29,14 @@ 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.context.UserContext;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.admin.common.context.UserExtraContext;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
+import top.continew.starter.extension.tenant.context.TenantContextHolder;
import java.time.LocalDateTime;
import java.util.*;
@@ -50,6 +52,8 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class OnlineUserServiceImpl implements OnlineUserService {
+ private final TenantProperties tenantProperties;
+
@Override
@AutoOperate(type = OnlineUserResp.class, on = "list")
public PageResp page(OnlineUserQuery query, PageQuery pageQuery) {
@@ -88,6 +92,12 @@ public class OnlineUserServiceImpl implements OnlineUserService {
.isMatchClientId(query.getClientId(), userContext)) {
continue;
}
+ //租户数据过滤
+ if (tenantProperties.isEnabled()) {
+ if (!TenantContextHolder.getTenantId().equals(userContext.getTenantId())) {
+ continue;
+ }
+ }
List loginTimeList = query.getLoginTime();
entry.getValue().parallelStream().forEach(token -> {
UserExtraContext extraContext = UserContextHolder.getExtraContext(token);
diff --git a/continew-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java b/continew-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java
index e77de252..75da4f86 100644
--- a/continew-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java
+++ b/continew-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java
@@ -16,6 +16,8 @@
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;
@@ -26,6 +28,7 @@ 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 {
}
\ No newline at end of file
diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/user/UserDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/user/UserDO.java
index 88c4d0c2..92ccb92f 100644
--- a/continew-system/src/main/java/top/continew/admin/system/model/entity/user/UserDO.java
+++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/user/UserDO.java
@@ -108,4 +108,10 @@ public class UserDO extends BaseDO {
* 部门 ID
*/
private Long deptId;
+
+ /**
+ * 租户 ID
+ */
+ @TableField(select = false)
+ private Long tenantId;
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/model/query/MenuQuery.java b/continew-system/src/main/java/top/continew/admin/system/model/query/MenuQuery.java
index ff5691b9..4a8607f3 100644
--- a/continew-system/src/main/java/top/continew/admin/system/model/query/MenuQuery.java
+++ b/continew-system/src/main/java/top/continew/admin/system/model/query/MenuQuery.java
@@ -25,6 +25,7 @@ import top.continew.starter.data.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
+import java.util.List;
/**
* 菜单查询条件
@@ -56,4 +57,12 @@ public class MenuQuery implements Serializable {
public MenuQuery(DisEnableStatusEnum status) {
this.status = status;
}
+
+ /**
+ * 排除的菜单
+ */
+ @Schema(description = "排除的菜单")
+ @Query(columns = "id", type = QueryType.NOT_IN)
+ private List excludeMenuIdList;
+
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/DeptService.java b/continew-system/src/main/java/top/continew/admin/system/service/DeptService.java
index 12ef2d14..1f763d05 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/DeptService.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/DeptService.java
@@ -56,4 +56,12 @@ public interface DeptService extends BaseService deptNames);
+
+ /**
+ * 初始化租户部门
+ *
+ * @param deptName
+ * @return 部门ID
+ */
+ Long initTenantDept(String deptName);
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/MenuService.java b/continew-system/src/main/java/top/continew/admin/system/service/MenuService.java
index 73c46ebb..2322a1c2 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/MenuService.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/MenuService.java
@@ -34,6 +34,13 @@ import java.util.Set;
*/
public interface MenuService extends BaseService, IService {
+ /**
+ * 查询全部菜单
+ *
+ * @return 菜单列表
+ */
+ List listAll(Long tenantId);
+
/**
* 根据用户 ID 查询
*
@@ -43,10 +50,35 @@ public interface MenuService extends BaseService listPermissionByUserId(Long userId);
/**
- * 根据角色 ID 查询
+ * 根据角色id查询
*
- * @param roleId 角色 ID
+ * @param roleId 角色id
* @return 菜单列表
*/
List listByRoleId(Long roleId);
+
+ /**
+ * 递归初始化菜单
+ *
+ * @param menuList 需要初始化的菜单ID
+ * @param oldParentId 原来的父级ID
+ * @param newParentId 新的父级ID
+ */
+ void menuInit(List menuList, Long oldParentId, Long newParentId);
+
+ /**
+ * 删除租户菜单
+ *
+ * @param menuList
+ */
+ void deleteTenantMenus(List menuList);
+
+ /**
+ * 新增租户菜单
+ *
+ * @param menu 新增的菜单
+ * @param pMenu 新增菜单的父级别
+ */
+ void addTenantMenu(MenuDO menu, MenuDO pMenu);
+
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/RoleMenuService.java b/continew-system/src/main/java/top/continew/admin/system/service/RoleMenuService.java
index 3f541b79..083ba5d4 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/RoleMenuService.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/RoleMenuService.java
@@ -16,6 +16,9 @@
package top.continew.admin.system.service;
+import com.baomidou.mybatisplus.extension.service.IService;
+import top.continew.admin.system.model.entity.RoleMenuDO;
+
import java.util.List;
/**
@@ -24,7 +27,7 @@ import java.util.List;
* @author Charles7c
* @since 2023/2/19 10:40
*/
-public interface RoleMenuService {
+public interface RoleMenuService extends IService {
/**
* 新增
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java b/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java
index 5b9c2212..ca566926 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java
@@ -100,4 +100,12 @@ public interface RoleService extends BaseService roleNames);
+
+ /**
+ * 初始化租户角色
+ *
+ * @return 角色ID
+ */
+ Long initTenantRole();
+
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/TenantSysDataService.java b/continew-system/src/main/java/top/continew/admin/system/service/TenantSysDataService.java
new file mode 100644
index 00000000..48016d5c
--- /dev/null
+++ b/continew-system/src/main/java/top/continew/admin/system/service/TenantSysDataService.java
@@ -0,0 +1,31 @@
+/*
+ * 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();
+
+}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/UserService.java b/continew-system/src/main/java/top/continew/admin/system/service/UserService.java
index 252d5548..0f0914a8 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/UserService.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/UserService.java
@@ -155,4 +155,13 @@ public interface UserService extends BaseService deptIds);
+
+ /**
+ * 初始化租户管理员
+ *
+ * @param username
+ * @param password
+ * @return 管理员id
+ */
+ Long initTenantUser(String username, String password, Long deptId);
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java
index 52a8b503..492c055e 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java
@@ -214,4 +214,25 @@ public class DeptServiceImpl extends BaseServiceImpl implements MenuService {
+ private final RoleMenuService roleMenuService;
+ private final RoleMapper roleMapper;
+
@Override
public Long create(MenuReq req) {
String title = req.getTitle();
@@ -90,12 +99,17 @@ public class MenuServiceImpl extends BaseServiceImpl listAll(Long tenantId) {
+ return super.list(new MenuQuery(DisEnableStatusEnum.ENABLE), null);
+ }
+
@Override
public Set listPermissionByUserId(Long userId) {
return baseMapper.selectPermissionByUserId(userId);
}
- @Override
@Cached(key = "#roleId", name = CacheConstants.ROLE_MENU_KEY_PREFIX)
public List listByRoleId(Long roleId) {
if (SysConstants.SUPER_ROLE_ID.equals(roleId)) {
@@ -107,6 +121,63 @@ public class MenuServiceImpl extends BaseServiceImpl menuList, Long oldParentId, Long newParentId) {
+ List 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
+ public void deleteTenantMenus(List menuList) {
+ if (!menuList.isEmpty()) {
+ List 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));
+ }
+ }
+ }
+
+ @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();
+ }
+ 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);
+ }
+
/**
* 标题是否存在
*
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleMenuServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleMenuServiceImpl.java
index 0154ac89..b593b0e7 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleMenuServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleMenuServiceImpl.java
@@ -23,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional;
import top.continew.admin.system.mapper.RoleMenuMapper;
import top.continew.admin.system.model.entity.RoleMenuDO;
import top.continew.admin.system.service.RoleMenuService;
+import top.continew.starter.data.service.impl.ServiceImpl;
import java.util.ArrayList;
import java.util.List;
@@ -36,7 +37,7 @@ import java.util.stream.Collectors;
*/
@Service
@RequiredArgsConstructor
-public class RoleMenuServiceImpl implements RoleMenuService {
+public class RoleMenuServiceImpl extends ServiceImpl implements RoleMenuService {
private final RoleMenuMapper baseMapper;
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java
index c9e89456..2052c342 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java
@@ -69,6 +69,8 @@ public class RoleServiceImpl extends BaseServiceImpl userDOS = userMapper.selectList(null);
+ for (UserDO userDO : userDOS) {
+ StpUtil.logout(userDO.getId());
+ }
+ Wrapper dw = Wrappers.query().eq("1", 1);
+ //部门清除
+ deptMapper.delete(dw);
+ //文件清除
+ List 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);
+ }
+
+}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java
index 9bd3dceb..f6dddd7a 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java
@@ -23,10 +23,7 @@ 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.CharsetUtil;
-import cn.hutool.core.util.EnumUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.*;
import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
@@ -57,6 +54,7 @@ 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;
@@ -80,8 +78,10 @@ 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,6 +734,26 @@ public class UserServiceImpl extends BaseServiceImpl 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 获取用户信息(数据权限)
*
diff --git a/pom.xml b/pom.xml
index 922e7413..c6dd7695 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,13 @@
${revision}
+
+
+ top.continew.admin
+ continew-plugin-tenant
+ 4.0.0-SNAPSHOT
+
+
top.continew.admin