mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-10-22 22:59:55 +08:00
refactor(tenant): 优化租户相关代码
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 是否启用 Tenant 判断注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/5/18 12:03
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Documented
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public @interface ConditionalOnEnabledTenant {
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import top.continew.admin.common.config.TenantProperties;
|
||||
import top.continew.admin.tenant.constant.TenantConstants;
|
||||
import top.continew.admin.tenant.model.entity.DatasourceDO;
|
||||
import top.continew.admin.tenant.model.entity.TenantDO;
|
||||
import top.continew.admin.tenant.model.enums.DatasourceDatabaseTypeEnum;
|
||||
import top.continew.admin.tenant.model.req.DatasourceReq;
|
||||
import top.continew.admin.tenant.service.DatasourceService;
|
||||
import top.continew.admin.tenant.service.TenantService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 默认租户提供者
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 15:35
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultTenantProvider implements TenantProvider {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
private final TenantService tenantService;
|
||||
private final DatasourceService datasourceService;
|
||||
|
||||
@Override
|
||||
public TenantContext getByTenantId(String tenantIdAsString, boolean verify) {
|
||||
TenantContext context = new TenantContext();
|
||||
// 超级租户默认使用行级隔离
|
||||
Long superTenantId = tenantProperties.getSuperTenantId();
|
||||
if (StrUtil.isBlank(tenantIdAsString) || superTenantId.toString().equals(tenantIdAsString)) {
|
||||
context.setTenantId(superTenantId);
|
||||
context.setIsolationLevel(TenantIsolationLevel.LINE);
|
||||
return context;
|
||||
}
|
||||
// 获取租户信息
|
||||
Long tenantId = Long.valueOf(tenantIdAsString);
|
||||
TenantDO tenant = tenantService.checkStatus(tenantId);
|
||||
TenantIsolationLevel isolationLevel = tenant.getIsolationLevel().getLevel();
|
||||
context.setTenantId(tenantId);
|
||||
context.setIsolationLevel(isolationLevel);
|
||||
// 数据源级隔离级别需要提供数据源信息
|
||||
if (TenantIsolationLevel.DATASOURCE == isolationLevel) {
|
||||
// 获取数据源配置
|
||||
DatasourceDO datasource = datasourceService.getById(tenant.getDatasourceId());
|
||||
DatasourceDatabaseTypeEnum databaseType = datasource.getDatabaseType();
|
||||
// 填充数据源信息
|
||||
TenantDataSource tenantDataSource = new TenantDataSource();
|
||||
tenantDataSource.setPoolName(tenantIdAsString);
|
||||
tenantDataSource.setDriverClassName(databaseType.getDriverClassName());
|
||||
tenantDataSource.setUrl(databaseType.getJdbcUrl(BeanUtil
|
||||
.toBean(datasource, DatasourceReq.class), TenantConstants.TENANT_DB_PREFIX + tenant.getCode()));
|
||||
tenantDataSource.setUsername(datasource.getUsername());
|
||||
tenantDataSource.setPassword(datasource.getPassword());
|
||||
context.setDataSource(tenantDataSource);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
@@ -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.config;
|
||||
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 租户配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/12 13:30
|
||||
*/
|
||||
@Configuration
|
||||
public class TenantConfiguration {
|
||||
|
||||
/**
|
||||
* API 文档分组配置
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi tenantApi() {
|
||||
return GroupedOpenApi.builder().group("tenant").displayName("租户").pathsToMatch("/tenant/**").build();
|
||||
}
|
||||
}
|
@@ -14,25 +14,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.model.enums;
|
||||
package top.continew.admin.tenant.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
import top.continew.admin.common.constant.CacheConstants;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TenantConnectTypeEnum {
|
||||
/**
|
||||
* 租户缓存相关常量
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/14 20:35
|
||||
*/
|
||||
public class TenantCacheConstants {
|
||||
|
||||
MYSQL;
|
||||
/**
|
||||
* 分隔符
|
||||
*/
|
||||
public static final String DELIMITER = CacheConstants.DELIMITER;
|
||||
|
||||
public static TenantConnectTypeEnum getByOrdinal(Integer ordinal) {
|
||||
for (TenantConnectTypeEnum item : TenantConnectTypeEnum.values()) {
|
||||
if (item.ordinal() == ordinal) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
throw new BusinessException("未知的连接类型");
|
||||
/**
|
||||
* 租户前缀
|
||||
*/
|
||||
public static final String TENANT_KEY_PREFIX = "TENANT" + DELIMITER;
|
||||
|
||||
/**
|
||||
* 租户数据源前缀
|
||||
*/
|
||||
public static final String TENANT_DATASOURCE_KEY_PREFIX = TENANT_KEY_PREFIX + "DATASOURCE" + DELIMITER;
|
||||
|
||||
private TenantCacheConstants() {
|
||||
}
|
||||
|
||||
}
|
@@ -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.constant;
|
||||
|
||||
/**
|
||||
* 租户相关常量
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/14 20:27
|
||||
*/
|
||||
public class TenantConstants {
|
||||
|
||||
/**
|
||||
* 租户数据库前缀
|
||||
*/
|
||||
public static final String TENANT_DB_PREFIX = "tenant_";
|
||||
|
||||
/**
|
||||
* 编码生成器 KEY
|
||||
*/
|
||||
public static final String CODE_GENERATOR_KEY = "tenant-code";
|
||||
|
||||
private TenantConstants() {
|
||||
}
|
||||
}
|
@@ -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.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.continew.admin.common.base.controller.BaseController;
|
||||
import top.continew.admin.tenant.model.query.DatasourceQuery;
|
||||
import top.continew.admin.tenant.model.req.DatasourceReq;
|
||||
import top.continew.admin.tenant.model.resp.DatasourceDetailResp;
|
||||
import top.continew.admin.tenant.model.resp.DatasourceResp;
|
||||
import top.continew.admin.tenant.service.DatasourceService;
|
||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
|
||||
/**
|
||||
* 数据源管理 API
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
@Tag(name = "数据源管理 API")
|
||||
@RestController
|
||||
@CrudRequestMapping(value = "/tenant/datasource", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
|
||||
public class DatasourceController extends BaseController<DatasourceService, DatasourceResp, DatasourceDetailResp, DatasourceQuery, DatasourceReq> {
|
||||
|
||||
@Operation(summary = "测试连接", description = "测试数据源连接可用性")
|
||||
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
|
||||
@SaCheckPermission("tenant:datasource:testConnection")
|
||||
@PostMapping("/{id}/test/connection")
|
||||
public void testConnection(@PathVariable Long id) {
|
||||
baseService.testConnection(id);
|
||||
}
|
||||
}
|
@@ -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.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.continew.admin.common.base.controller.BaseController;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.common.config.TenantProperties;
|
||||
import top.continew.admin.system.model.query.MenuQuery;
|
||||
import top.continew.admin.system.service.MenuService;
|
||||
import top.continew.admin.tenant.model.query.PackageQuery;
|
||||
import top.continew.admin.tenant.model.req.PackageReq;
|
||||
import top.continew.admin.tenant.model.resp.PackageDetailResp;
|
||||
import top.continew.admin.tenant.model.resp.PackageResp;
|
||||
import top.continew.admin.tenant.service.PackageService;
|
||||
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
|
||||
import top.continew.starter.extension.crud.enums.Api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 套餐管理 API
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
@Tag(name = "套餐管理 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@CrudRequestMapping(value = "/tenant/package", api = {Api.LIST, Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
|
||||
public class PackageController extends BaseController<PackageService, PackageResp, PackageDetailResp, PackageQuery, PackageReq> {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
private final MenuService menuService;
|
||||
|
||||
@Operation(summary = "查询租户套餐菜单", description = "查询租户套餐菜单树列表")
|
||||
@SaCheckPermission("tenant:package:list")
|
||||
@GetMapping("/menu/tree")
|
||||
public List<Tree<Long>> listMenuTree() {
|
||||
MenuQuery query = new MenuQuery();
|
||||
query.setStatus(DisEnableStatusEnum.ENABLE);
|
||||
// 过滤掉租户不能使用的菜单
|
||||
query.setExcludeMenuIdList(tenantProperties.getIgnoreMenus());
|
||||
return menuService.tree(query, null, true);
|
||||
}
|
||||
}
|
@@ -18,71 +18,52 @@ 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 lombok.RequiredArgsConstructor;
|
||||
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.config.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.system.service.UserService;
|
||||
import top.continew.admin.tenant.model.entity.TenantDO;
|
||||
import top.continew.admin.tenant.model.query.TenantQuery;
|
||||
import top.continew.admin.tenant.model.req.TenantLoginUserInfoReq;
|
||||
import top.continew.admin.tenant.model.req.TenantAdminUserPwdUpdateReq;
|
||||
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.model.resp.TenantCommonResp;
|
||||
import top.continew.admin.tenant.model.resp.TenantDetailResp;
|
||||
import top.continew.admin.tenant.model.resp.TenantResp;
|
||||
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 小熊
|
||||
* @author Charles7c
|
||||
* @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})
|
||||
@RequiredArgsConstructor
|
||||
@CrudRequestMapping(value = "/tenant/management", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
|
||||
public class TenantController extends BaseController<TenantService, TenantResp, TenantDetailResp, TenantQuery, TenantReq> {
|
||||
|
||||
private final 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 = "多租户通用信息查询")
|
||||
@GetMapping("/common")
|
||||
@Operation(summary = "租户通用信息查询", description = "租户通用信息查询")
|
||||
public TenantCommonResp common() {
|
||||
TenantCommonResp commonResp = new TenantCommonResp();
|
||||
commonResp.setIsEnabled(tenantProperties.isEnabled());
|
||||
@@ -90,122 +71,20 @@ public class TenantController extends BaseController<TenantService, TenantResp,
|
||||
return commonResp;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DSTransactional
|
||||
public IdResp<Long> create(TenantReq req) {
|
||||
//套餐菜单
|
||||
TenantPackageDetailResp detailResp = packageService.get(req.getPackageId());
|
||||
CheckUtils.throwIf(detailResp.getMenuIds().isEmpty(), "该套餐无可用菜单");
|
||||
List<MenuDO> menuRespList = menuService.listByIds(detailResp.getMenuIds());
|
||||
//租户添加
|
||||
IdResp<Long> baseIdResp = super.create(req);
|
||||
//在租户中执行数据插入
|
||||
SpringUtil.getBean(TenantHandler.class).execute(baseIdResp.getId(), () -> {
|
||||
//租户部门初始化
|
||||
Long deptId = deptService.initTenantDept(req.getName());
|
||||
//租户菜单初始化
|
||||
menuService.menuInit(menuRespList, 0L, 0L);
|
||||
//租户角色初始化
|
||||
Long roleId = roleService.initTenantRole();
|
||||
//角色绑定菜单
|
||||
roleMenuService.add(menuService.listAll(baseIdResp.getId()).stream().map(BaseResp::getId).toList(), roleId);
|
||||
//管理用户初始化
|
||||
Long userId = userService.initTenantUser(req.getUsername(), req.getPassword(), deptId);
|
||||
//用户绑定角色
|
||||
roleService.assignToUsers(roleId, ListUtil.of(userId));
|
||||
//租户绑定用户
|
||||
baseService.bindUser(baseIdResp.getId(), userId);
|
||||
});
|
||||
return baseIdResp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
SpringUtil.getBean(TenantHandler.class).execute(id, () -> {
|
||||
//系统数据清除
|
||||
tenantSysDataService.clear();
|
||||
});
|
||||
super.delete(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void batchDelete(@Valid IdsReq ids) {
|
||||
for (Long id : ids.getIds()) {
|
||||
//在租户中执行数据清除
|
||||
SpringUtil.getBean(TenantHandler.class).execute(id, () -> {
|
||||
//系统数据清除
|
||||
tenantSysDataService.clear();
|
||||
});
|
||||
}
|
||||
super.batchDelete(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户管理账号用户名
|
||||
*/
|
||||
@GetMapping("/loginUser/{tenantId}")
|
||||
@Operation(summary = "获取租户管理账号信息", description = "获取租户管理账号信息")
|
||||
@SaCheckPermission("tenant:user:editLoginUserInfo")
|
||||
public String loginUserInfo(@PathVariable Long tenantId) {
|
||||
TenantDO tenantDO = baseService.getTenantById(tenantId);
|
||||
CheckUtils.throwIfNull(tenantDO, "租户不存在");
|
||||
StringBuilder username = new StringBuilder();
|
||||
SpringUtil.getBean(TenantHandler.class).execute(tenantDO.getId(), () -> {
|
||||
UserDO userDO = userService.getById(tenantDO.getUserId());
|
||||
CheckUtils.throwIfNull(userDO, "租户管理用户不存在");
|
||||
username.append(userDO.getUsername());
|
||||
});
|
||||
return username.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户管理账号信息更新
|
||||
*/
|
||||
@PutMapping("/loginUser")
|
||||
@Operation(summary = "租户管理账号信息更新", description = "租户管理账号信息更新")
|
||||
@SaCheckPermission("tenant:user:editLoginUserInfo")
|
||||
@DSTransactional
|
||||
public void editLoginUserInfo(@Validated @RequestBody TenantLoginUserInfoReq req) {
|
||||
TenantDO tenantDO = baseService.getTenantById(req.getTenantId());
|
||||
CheckUtils.throwIfNull(tenantDO, "租户不存在");
|
||||
SpringUtil.getBean(TenantHandler.class).execute(tenantDO.getId(), () -> {
|
||||
UserDO userDO = userService.getById(tenantDO.getUserId());
|
||||
CheckUtils.throwIfNull(userDO, "用户不存在");
|
||||
//修改用户名
|
||||
if (!req.getUsername().equals(userDO.getUsername())) {
|
||||
userService.update(Wrappers.lambdaUpdate(UserDO.class)
|
||||
.set(UserDO::getUsername, req.getUsername())
|
||||
.eq(BaseIdDO::getId, userDO.getId()));
|
||||
}
|
||||
//修改密码
|
||||
if (StrUtil.isNotEmpty(req.getPassword())) {
|
||||
String password = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
|
||||
ValidationUtils.throwIfNull(password, "新密码解密失败");
|
||||
UserPasswordResetReq passwordResetReq = new UserPasswordResetReq();
|
||||
passwordResetReq.setNewPassword(password);
|
||||
userService.resetPassword(passwordResetReq, userDO.getId());
|
||||
}
|
||||
@DSTransactional(rollbackFor = Exception.class)
|
||||
@Operation(summary = "修改租户管理员密码", description = "修改租户管理员密码")
|
||||
@SaCheckPermission("tenant:management:updateAdminUserPwd")
|
||||
@PutMapping("/{id}/admin/pwd")
|
||||
public void updateAdminUserPwd(@Valid @RequestBody TenantAdminUserPwdUpdateReq req, @PathVariable Long id) {
|
||||
TenantDO tenant = baseService.getById(id);
|
||||
String encryptPassword = req.getPassword();
|
||||
SpringUtil.getBean(TenantHandler.class).execute(tenant.getId(), () -> {
|
||||
UserDO user = userService.getById(tenant.getAdminUser());
|
||||
String password = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(encryptPassword));
|
||||
ValidationUtils.throwIfNull(password, "新密码解密失败");
|
||||
UserPasswordResetReq passwordResetReq = new UserPasswordResetReq();
|
||||
passwordResetReq.setNewPassword(password);
|
||||
userService.resetPassword(passwordResetReq, user.getId());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有租户套餐
|
||||
*/
|
||||
@GetMapping("/all/package")
|
||||
@Operation(summary = "查询所有租户套餐", description = "查询所有租户套餐")
|
||||
@SaCheckPermission(value = {"tenant:user:add", "tenant:user:update"}, mode = SaMode.OR)
|
||||
public List<TenantPackageResp> packageList() {
|
||||
return packageService.list(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有数据库连接
|
||||
*/
|
||||
@GetMapping("/all/dbConnect")
|
||||
@Operation(summary = "获取租户数据连接列表", description = "获取租户数据连接列表")
|
||||
@SaCheckPermission(value = {"tenant:user:add", "tenant:user:update"}, mode = SaMode.OR)
|
||||
public List<TenantDbConnectResp> dbConnectList() {
|
||||
return dbConnectService.list(null, null);
|
||||
}
|
||||
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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<TenantDbConnectService, TenantDbConnectResp, TenantDbConnectDetailResp, TenantDbConnectQuery, TenantDbConnectReq> {}
|
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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<TenantPackageService, TenantPackageResp, TenantPackageDetailResp, TenantPackageQuery, TenantPackageReq> {
|
||||
|
||||
private final MenuService menuService;
|
||||
private final TenantProperties tenantProperties;
|
||||
private final TenantService tenantService;
|
||||
|
||||
@GetMapping("/menuTree")
|
||||
@SaCheckPermission("tenant:package:get")
|
||||
@Operation(summary = "获取租户套餐菜单", description = "获取租户套餐菜单")
|
||||
public List<Tree<Long>> 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<TenantDO> tenantDOList = tenantService.list(Wrappers.lambdaQuery(TenantDO.class)
|
||||
.eq(TenantDO::getPackageId, id));
|
||||
if (!tenantDOList.isEmpty()) {
|
||||
TenantPackageDetailResp detail = baseService.get(id);
|
||||
List<Long> oldMenuIds = detail.getMenuIds();
|
||||
List<Long> newMenuIds = Arrays.stream(req.getMenuIds()).toList();
|
||||
//删除的菜单
|
||||
List<Long> deleteMenuIds = new ArrayList<>(oldMenuIds);
|
||||
deleteMenuIds.removeAll(newMenuIds);
|
||||
//如果有删除的菜单则绑定了套餐的租户对应的菜单也会删除
|
||||
if (!deleteMenuIds.isEmpty()) {
|
||||
List<MenuDO> deleteMenus = menuService.listByIds(deleteMenuIds);
|
||||
tenantDOList.forEach(tenantDO -> SpringUtil.getBean(TenantHandler.class)
|
||||
.execute(tenantDO.getId(), () -> menuService.deleteTenantMenus(deleteMenus)));
|
||||
}
|
||||
//新增的菜单
|
||||
List<Long> addMenuIds = new ArrayList<>(newMenuIds);
|
||||
addMenuIds.removeAll(oldMenuIds);
|
||||
//如果有新增的菜单则绑定了套餐的租户对应的菜单也会新增
|
||||
if (!addMenuIds.isEmpty()) {
|
||||
List<MenuDO> 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);
|
||||
}
|
||||
}
|
@@ -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.handler;
|
||||
|
||||
import top.continew.admin.tenant.model.req.TenantReq;
|
||||
|
||||
/**
|
||||
* 租户数据处理器
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/2 20:08
|
||||
*/
|
||||
public interface TenantDataHandler {
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
*/
|
||||
void init(TenantReq tenant);
|
||||
|
||||
/**
|
||||
* 清除数据
|
||||
*/
|
||||
void clear();
|
||||
}
|
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.handler;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import top.continew.admin.common.constant.RegexConstants;
|
||||
import top.continew.admin.common.constant.SysConstants;
|
||||
import top.continew.admin.common.enums.DataScopeEnum;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.common.enums.GenderEnum;
|
||||
import top.continew.admin.common.util.SecureUtils;
|
||||
import top.continew.admin.system.mapper.*;
|
||||
import top.continew.admin.system.mapper.user.UserMapper;
|
||||
import top.continew.admin.system.mapper.user.UserPasswordHistoryMapper;
|
||||
import top.continew.admin.system.mapper.user.UserSocialMapper;
|
||||
import top.continew.admin.system.model.entity.DeptDO;
|
||||
import top.continew.admin.system.model.entity.MenuDO;
|
||||
import top.continew.admin.system.model.entity.RoleDO;
|
||||
import top.continew.admin.system.model.entity.user.UserDO;
|
||||
import top.continew.admin.system.service.FileService;
|
||||
import top.continew.admin.system.service.RoleMenuService;
|
||||
import top.continew.admin.system.service.RoleService;
|
||||
import top.continew.admin.tenant.constant.TenantCacheConstants;
|
||||
import top.continew.admin.tenant.mapper.TenantMapper;
|
||||
import top.continew.admin.tenant.model.entity.TenantDO;
|
||||
import top.continew.admin.tenant.model.req.TenantReq;
|
||||
import top.continew.admin.tenant.service.PackageMenuService;
|
||||
import top.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import top.continew.starter.core.util.ExceptionUtils;
|
||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
||||
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
|
||||
import top.continew.starter.extension.tenant.TenantHandler;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 租户数据处理器
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/2 20:12
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TenantDataHandlerForSystem implements TenantDataHandler {
|
||||
|
||||
private final PackageMenuService packageMenuService;
|
||||
private final DeptMapper deptMapper;
|
||||
private final MenuMapper menuMapper;
|
||||
private final RoleMapper roleMapper;
|
||||
private final RoleMenuService roleMenuService;
|
||||
private final RoleMenuMapper roleMenuMapper;
|
||||
private final RoleService roleService;
|
||||
private final TenantMapper tenantMapper;
|
||||
private final FileService fileService;
|
||||
private final LogMapper logMapper;
|
||||
private final MessageMapper messageMapper;
|
||||
private final MessageMapper messageUserMapper;
|
||||
private final NoticeMapper noticeMapper;
|
||||
private final RoleDeptMapper roleDeptMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final UserPasswordHistoryMapper userPasswordHistoryMapper;
|
||||
private final UserRoleMapper userRoleMapper;
|
||||
private final UserSocialMapper userSocialMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void init(TenantReq tenant) {
|
||||
Long tenantId = tenant.getId();
|
||||
SpringUtil.getBean(TenantHandler.class).execute(tenantId, () -> {
|
||||
// 初始化部门
|
||||
Long deptId = this.initDeptData(tenant);
|
||||
// 初始化菜单
|
||||
List<Long> menuIds = packageMenuService.listMenuIdsByPackageId(tenant.getPackageId());
|
||||
List<MenuDO> menuList = menuMapper.lambdaQuery().in(MenuDO::getId, menuIds).list();
|
||||
this.initMenuData(menuList, 0L, 0L);
|
||||
// 初始化角色
|
||||
Long roleId = this.initRoleData(tenant);
|
||||
// 角色绑定菜单
|
||||
roleMenuService.add(menuIds, roleId);
|
||||
// 初始化管理用户
|
||||
Long userId = this.initUserData(tenant, deptId);
|
||||
// 用户绑定角色
|
||||
roleService.assignToUsers(roleId, ListUtil.of(userId));
|
||||
// 租户绑定用户
|
||||
this.bindTenantUser(tenantId, userId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void clear() {
|
||||
// 退出所有用户
|
||||
List<UserDO> userList = userMapper.selectList(null);
|
||||
for (UserDO user : userList) {
|
||||
StpUtil.logout(user.getId());
|
||||
}
|
||||
Wrapper dw = Wrappers.query().eq("1", 1);
|
||||
// 部门清除
|
||||
deptMapper.delete(dw);
|
||||
// 文件清除
|
||||
List<Long> fileIds = fileService.list().stream().map(BaseIdDO::getId).toList();
|
||||
if (!fileIds.isEmpty()) {
|
||||
fileService.delete(fileIds);
|
||||
}
|
||||
// 日志清除
|
||||
logMapper.delete(dw);
|
||||
// 菜单清除
|
||||
menuMapper.delete(dw);
|
||||
// 消息清除
|
||||
messageMapper.delete(dw);
|
||||
messageUserMapper.delete(dw);
|
||||
// 通知清除
|
||||
noticeMapper.delete(dw);
|
||||
// 角色相关数据清除
|
||||
roleMapper.delete(dw);
|
||||
roleDeptMapper.delete(dw);
|
||||
roleMenuMapper.delete(dw);
|
||||
// 用户数据清除
|
||||
userMapper.delete(dw);
|
||||
userPasswordHistoryMapper.delete(dw);
|
||||
userRoleMapper.delete(dw);
|
||||
userSocialMapper.delete(dw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化部门数据
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 部门 ID
|
||||
*/
|
||||
private Long initDeptData(TenantReq tenant) {
|
||||
DeptDO dept = new DeptDO();
|
||||
dept.setName(tenant.getName());
|
||||
dept.setParentId(SysConstants.SUPER_PARENT_ID);
|
||||
dept.setAncestors("0");
|
||||
dept.setDescription("系统初始部门");
|
||||
dept.setSort(1);
|
||||
dept.setStatus(DisEnableStatusEnum.ENABLE);
|
||||
deptMapper.insert(dept);
|
||||
return dept.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归初始化菜单数据
|
||||
*
|
||||
* @param menuList 菜单列表
|
||||
* @param oldParentId 旧父级 ID
|
||||
* @param newParentId 新父级 ID
|
||||
*/
|
||||
private void initMenuData(List<MenuDO> menuList, Long oldParentId, Long newParentId) {
|
||||
List<MenuDO> children = menuList.stream().filter(menuDO -> menuDO.getParentId().equals(oldParentId)).toList();
|
||||
for (MenuDO menu : children) {
|
||||
Long oldId = menu.getId();
|
||||
menu.setId(null);
|
||||
menu.setParentId(newParentId);
|
||||
menuMapper.insert(menu);
|
||||
initMenuData(menuList, oldId, menu.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化角色数据
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 角色 ID
|
||||
*/
|
||||
private Long initRoleData(TenantReq tenant) {
|
||||
RoleDO role = new RoleDO();
|
||||
role.setName("系统管理员");
|
||||
role.setCode(SysConstants.TENANT_ADMIN_ROLE_CODE);
|
||||
role.setDataScope(DataScopeEnum.ALL);
|
||||
role.setDescription("系统初始角色");
|
||||
role.setSort(1);
|
||||
role.setIsSystem(true);
|
||||
role.setMenuCheckStrictly(true);
|
||||
role.setDeptCheckStrictly(true);
|
||||
roleMapper.insert(role);
|
||||
return role.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化用户数据
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @param deptId 部门 ID
|
||||
* @return 用户 ID
|
||||
*/
|
||||
private Long initUserData(TenantReq tenant, Long deptId) {
|
||||
// 解密密码
|
||||
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(tenant.getPassword()));
|
||||
ValidationUtils.throwIfNull(rawPassword, "密码解密失败");
|
||||
ValidationUtils.throwIf(!ReUtil
|
||||
.isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 8-32 个字符,支持大小写字母、数字、特殊字符,至少包含字母和数字");
|
||||
// 初始化用户
|
||||
UserDO user = new UserDO();
|
||||
user.setUsername(tenant.getUsername());
|
||||
user.setNickname("系统管理员");
|
||||
user.setPassword(rawPassword);
|
||||
user.setGender(GenderEnum.UNKNOWN);
|
||||
user.setDescription("系统初始用户");
|
||||
user.setStatus(DisEnableStatusEnum.ENABLE);
|
||||
user.setIsSystem(true);
|
||||
user.setPwdResetTime(LocalDateTime.now());
|
||||
user.setDeptId(deptId);
|
||||
userMapper.insert(user);
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定租户管理员用户
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param userId 用户 ID
|
||||
*/
|
||||
public void bindTenantUser(Long tenantId, Long userId) {
|
||||
tenantMapper.lambdaUpdate().set(TenantDO::getAdminUser, userId).eq(BaseIdDO::getId, tenantId).update();
|
||||
// 更新租户缓存
|
||||
TenantDO entity = tenantMapper.selectById(tenantId);
|
||||
RedisUtils.set(TenantCacheConstants.TENANT_KEY_PREFIX + tenantId, entity);
|
||||
}
|
||||
}
|
@@ -16,13 +16,15 @@
|
||||
|
||||
package top.continew.admin.tenant.mapper;
|
||||
|
||||
import top.continew.admin.tenant.model.entity.TenantDbConnectDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import top.continew.admin.tenant.model.entity.DatasourceDO;
|
||||
import top.continew.starter.data.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* 租户数据连接 Mapper
|
||||
* 数据源 Mapper
|
||||
*
|
||||
* @author 小熊
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
public interface TenantDbConnectMapper extends BaseMapper<TenantDbConnectDO> {}
|
||||
@Mapper
|
||||
public interface DatasourceMapper extends BaseMapper<DatasourceDO> {}
|
@@ -16,14 +16,16 @@
|
||||
|
||||
package top.continew.admin.tenant.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import top.continew.starter.data.mapper.BaseMapper;
|
||||
import top.continew.admin.tenant.model.entity.TenantPackageDO;
|
||||
import top.continew.admin.tenant.model.entity.PackageDO;
|
||||
|
||||
/**
|
||||
* 租户套餐 Mapper
|
||||
* 套餐 Mapper
|
||||
*
|
||||
* @author 小熊
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
public interface TenantPackageMapper extends BaseMapper<TenantPackageDO> {
|
||||
@Mapper
|
||||
public interface PackageMapper extends BaseMapper<PackageDO> {
|
||||
}
|
@@ -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.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import top.continew.admin.tenant.model.entity.PackageMenuDO;
|
||||
import top.continew.starter.data.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* 套餐和菜单关联 Mapper
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/13 20:24
|
||||
*/
|
||||
@Mapper
|
||||
public interface PackageMenuMapper extends BaseMapper<PackageMenuDO> {
|
||||
}
|
@@ -16,16 +16,8 @@
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -34,11 +26,6 @@ import top.continew.starter.data.mapper.BaseMapper;
|
||||
* @author 小熊
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@DS(SysConstants.DEFAULT_DATASOURCE)
|
||||
@Mapper
|
||||
public interface TenantMapper extends BaseMapper<TenantDO> {
|
||||
|
||||
@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<TenantResp> listTenant(IPage page, @Param(Constants.WRAPPER) Wrapper wrapper);
|
||||
|
||||
}
|
@@ -19,49 +19,60 @@ 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 top.continew.admin.tenant.model.enums.DatasourceDatabaseTypeEnum;
|
||||
import top.continew.starter.extension.crud.annotation.DictModel;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 租户数据连接实体
|
||||
* 数据源实体
|
||||
*
|
||||
* @author 小熊
|
||||
* @since Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_tenant_db_connect")
|
||||
public class TenantDbConnectDO extends BaseDO {
|
||||
@DictModel
|
||||
@TableName("tenant_datasource")
|
||||
public class DatasourceDO extends BaseDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 连接名称
|
||||
* 名称
|
||||
*/
|
||||
private String connectName;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 连接类型
|
||||
* 数据库类型
|
||||
*/
|
||||
private Integer type;
|
||||
private DatasourceDatabaseTypeEnum databaseType;
|
||||
|
||||
/**
|
||||
* 连接主机地址
|
||||
* 主机
|
||||
*/
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* 连接端口
|
||||
* 端口
|
||||
*/
|
||||
private Integer port;
|
||||
|
||||
/**
|
||||
* 连接用户名
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 连接密码
|
||||
* 密码
|
||||
*/
|
||||
@FieldEncrypt
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
}
|
@@ -16,35 +16,38 @@
|
||||
|
||||
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 top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.starter.extension.crud.annotation.DictModel;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import top.continew.admin.common.base.model.entity.BaseDO;
|
||||
|
||||
/**
|
||||
* 租户套餐实体
|
||||
* 套餐实体
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_tenant_package")
|
||||
public class TenantPackageDO extends BaseDO {
|
||||
@DictModel
|
||||
@TableName("tenant_package")
|
||||
public class PackageDO extends BaseDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 套餐名称
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 关联的菜单ids
|
||||
* 排序
|
||||
*/
|
||||
private String menuIds;
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 菜单选择是否父子节点关联
|
||||
@@ -52,7 +55,12 @@ public class TenantPackageDO extends BaseDO {
|
||||
private Boolean menuCheckStrictly;
|
||||
|
||||
/**
|
||||
* 状态(1:启用;2:禁用)
|
||||
* 描述
|
||||
*/
|
||||
private Integer status;
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private DisEnableStatusEnum status;
|
||||
}
|
@@ -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.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 套餐和菜单关联实体
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/11 22:01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@TableName("tenant_package_menu")
|
||||
public class PackageMenuDO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 套餐 ID
|
||||
*/
|
||||
private Long packageId;
|
||||
|
||||
/**
|
||||
* 菜单 ID
|
||||
*/
|
||||
private Long menuId;
|
||||
|
||||
public PackageMenuDO(Long packageId, Long menuId) {
|
||||
this.packageId = packageId;
|
||||
this.menuId = menuId;
|
||||
}
|
||||
}
|
@@ -19,6 +19,8 @@ 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 top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.tenant.model.enums.TenantIsolationLevelEnum;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -27,57 +29,63 @@ import java.time.LocalDateTime;
|
||||
* 租户实体
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_tenant")
|
||||
@TableName("tenant")
|
||||
public class TenantDO extends BaseDO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 绑定的域名
|
||||
* 编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 租户套餐编号
|
||||
*/
|
||||
private Long packageId;
|
||||
|
||||
/**
|
||||
* 状态(1:启用;2:禁用)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 租户过期时间
|
||||
* 过期时间
|
||||
*/
|
||||
private LocalDateTime expireTime;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private String tenantSn;
|
||||
|
||||
/**
|
||||
* 隔离级别
|
||||
*/
|
||||
private Integer isolationLevel;
|
||||
private TenantIsolationLevelEnum isolationLevel;
|
||||
|
||||
/**
|
||||
* 数据连接ID
|
||||
* 描述
|
||||
*/
|
||||
private Long dbConnectId;
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private DisEnableStatusEnum status;
|
||||
|
||||
/**
|
||||
* 租户管理员
|
||||
*/
|
||||
private Long adminUser;
|
||||
|
||||
/**
|
||||
* 套餐 ID
|
||||
*/
|
||||
private Long packageId;
|
||||
|
||||
/**
|
||||
* 数据源 ID
|
||||
*/
|
||||
private Long datasourceId;
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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 cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import top.continew.admin.tenant.model.req.DatasourceReq;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Connection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据源数据库类型枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author 小熊
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum DatasourceDatabaseTypeEnum implements BaseEnum<Integer> {
|
||||
|
||||
/**
|
||||
* MySQL
|
||||
*/
|
||||
MYSQL(1, "MySQL", "com.mysql.cj.jdbc.Driver") {
|
||||
@Override
|
||||
public DataSource buildDataSource(DatasourceReq datasource) {
|
||||
return DataSourceBuilder.create()
|
||||
.url(this.getJdbcUrl(datasource, null))
|
||||
.driverClassName(this.getDriverClassName())
|
||||
.username(datasource.getUsername())
|
||||
.password(datasource.getPassword())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testConnection(DatasourceReq datasource) {
|
||||
DataSource dataSource = this.buildDataSource(datasource);
|
||||
try (Connection ignored = dataSource.getConnection()) {
|
||||
log.info("数据源 [{}] 测试连接成功", datasource.getName());
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException("数据源 [%s] 测试连接失败".formatted(datasource.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJdbcUrl(DatasourceReq datasource, String databaseName) {
|
||||
StringBuilder urlBuilder = new StringBuilder("jdbc:mysql://%s:%s".formatted(datasource.getHost(), datasource
|
||||
.getPort()));
|
||||
if (StrUtil.isNotBlank(databaseName)) {
|
||||
urlBuilder.append(StringConstants.SLASH).append(databaseName);
|
||||
urlBuilder.append(StringConstants.QUESTION_MARK);
|
||||
urlBuilder.append(URLUtil.buildQuery(this.getDefaultParameters(), StandardCharsets.UTF_8));
|
||||
}
|
||||
return urlBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getDefaultParameters() {
|
||||
Map<String, String> parameter = MapUtil.newHashMap(8);
|
||||
parameter.put("serverTimezone", "Asia/Shanghai");
|
||||
parameter.put("useSSL", "true");
|
||||
parameter.put("useUnicode", "true");
|
||||
parameter.put("characterEncoding", "utf8");
|
||||
parameter.put("rewriteBatchedStatements", "true");
|
||||
parameter.put("autoReconnect", "true");
|
||||
parameter.put("allowPublicKeyRetrieval", "true");
|
||||
parameter.put("nullCatalogMeansCurrent", "true");
|
||||
return parameter;
|
||||
}
|
||||
};
|
||||
|
||||
private final Integer value;
|
||||
private final String description;
|
||||
private final String driverClassName;
|
||||
|
||||
/**
|
||||
* 构建数据源
|
||||
*
|
||||
* @param datasource 数据源配置
|
||||
* @return 数据源
|
||||
*/
|
||||
public abstract DataSource buildDataSource(DatasourceReq datasource);
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*
|
||||
* @param datasource 数据源配置
|
||||
*/
|
||||
public abstract void testConnection(DatasourceReq datasource);
|
||||
|
||||
/**
|
||||
* 获取 JDBC URL
|
||||
*
|
||||
* @param datasource 数据源配置
|
||||
* @param databaseName 数据库名称
|
||||
* @return JDBC URL
|
||||
*/
|
||||
public abstract String getJdbcUrl(DatasourceReq datasource, String databaseName);
|
||||
|
||||
/**
|
||||
* 获取默认数据库连接参数
|
||||
*
|
||||
* @return 默认数据库连接参数
|
||||
*/
|
||||
public abstract Map<String, String> getDefaultParameters();
|
||||
}
|
@@ -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.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.continew.starter.core.enums.BaseEnum;
|
||||
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||
|
||||
/**
|
||||
* 租户隔离级别枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/14 20:31
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum TenantIsolationLevelEnum implements BaseEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 行级
|
||||
*/
|
||||
LINE(1, "行级", TenantIsolationLevel.LINE),
|
||||
|
||||
/**
|
||||
* 数据源级
|
||||
*/
|
||||
DATASOURCE(2, "数据源级", TenantIsolationLevel.DATASOURCE);
|
||||
|
||||
private final Integer value;
|
||||
private final String description;
|
||||
private final TenantIsolationLevel level;
|
||||
}
|
@@ -16,34 +16,32 @@
|
||||
|
||||
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 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 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "租户数据连接查询条件")
|
||||
public class TenantDbConnectQuery implements Serializable {
|
||||
@Schema(description = "数据源查询条件")
|
||||
public class DatasourceQuery implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 连接名称
|
||||
* 关键词
|
||||
*/
|
||||
@Schema(description = "连接名称")
|
||||
@Query(type = QueryType.EQ)
|
||||
private String connectName;
|
||||
@Schema(description = "关键词", example = "数据源")
|
||||
@Query(columns = {"name", "description"}, type = QueryType.LIKE)
|
||||
private String description;
|
||||
}
|
@@ -16,40 +16,40 @@
|
||||
|
||||
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 lombok.Data;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.starter.data.annotation.Query;
|
||||
import top.continew.starter.data.enums.QueryType;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 租户套餐查询条件
|
||||
* 套餐查询条件
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "租户套餐查询条件")
|
||||
public class TenantPackageQuery implements Serializable {
|
||||
@Schema(description = "套餐查询条件")
|
||||
public class PackageQuery implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 套餐名称
|
||||
* 关键词
|
||||
*/
|
||||
@Schema(description = "套餐名称")
|
||||
@Query(type = QueryType.EQ)
|
||||
private String name;
|
||||
@Schema(description = "关键词", example = "初级套餐")
|
||||
@Query(columns = {"name", "description"}, type = QueryType.LIKE)
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态(1:启用;2:禁用)
|
||||
* 状态
|
||||
*/
|
||||
@Schema(description = "状态(1:启用;2:禁用)")
|
||||
@Schema(description = "状态", example = "1")
|
||||
@Query(type = QueryType.EQ)
|
||||
private Integer status;
|
||||
private DisEnableStatusEnum status;
|
||||
}
|
@@ -28,6 +28,7 @@ import java.io.Serializable;
|
||||
* 租户查询条件
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@Data
|
||||
@@ -38,17 +39,16 @@ public class TenantQuery implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
* 关键词
|
||||
*/
|
||||
@Schema(description = "租户名称")
|
||||
@Query(type = QueryType.LIKE)
|
||||
private String name;
|
||||
@Schema(description = "关键词", example = "T0001")
|
||||
@Query(columns = {"name", "description"}, type = QueryType.LIKE)
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 租户套餐编号
|
||||
* 套餐 ID
|
||||
*/
|
||||
@Schema(description = "租户套餐编号")
|
||||
@Schema(description = "套餐 ID", example = "1")
|
||||
@Query(type = QueryType.EQ)
|
||||
private Long packageId;
|
||||
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 top.continew.admin.tenant.model.enums.DatasourceDatabaseTypeEnum;
|
||||
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 数据源创建或修改请求参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "数据源创建或修改请求参数")
|
||||
public class DatasourceReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "名称", example = "T0001数据源")
|
||||
@NotBlank(message = "名称不能为空")
|
||||
@Length(max = 30, message = "名称长度不能超过 {max} 个字符")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 数据库类型
|
||||
*/
|
||||
@Schema(description = "数据库类型", example = "1")
|
||||
@NotNull(message = "数据库类型无效")
|
||||
private DatasourceDatabaseTypeEnum databaseType;
|
||||
|
||||
/**
|
||||
* 主机
|
||||
*/
|
||||
@Schema(description = "主机", example = "123.56.195.68")
|
||||
@NotBlank(message = "主机不能为空")
|
||||
@Length(max = 128, message = "主机长度不能超过 {max} 个字符")
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* 端口
|
||||
*/
|
||||
@Schema(description = "端口", example = "3306")
|
||||
@NotNull(message = "端口不能为空")
|
||||
private Integer port;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@Schema(description = "用户名", example = "root")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Length(max = 128, message = "用户名长度不能超过 {max} 个字符")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Schema(description = "密码", example = "jXo1Mwsuoz+XhLy6tOhdzbTJ3gIDxciTAnCjcOO8akglghVDO3jR5pqOp95LkSBp1Yd9bltYzWDNjNvL6yD3TQ==")
|
||||
@NotBlank(message = "密码不能为空", groups = CrudValidationGroup.Create.class)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述", example = "T0001数据源描述")
|
||||
@Length(max = 200, message = "描述长度不能超过 {max} 个字符")
|
||||
private String description;
|
||||
}
|
@@ -16,61 +16,67 @@
|
||||
|
||||
package top.continew.admin.tenant.model.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
|
||||
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;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建或修改租户套餐参数
|
||||
* 套餐创建或修改请求参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建或修改租户套餐参数")
|
||||
public class TenantPackageReq implements Serializable {
|
||||
@Schema(description = "套餐创建或修改请求参数")
|
||||
public class PackageReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 套餐名称
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "套餐名称")
|
||||
@NotBlank(message = "套餐名称不能为空")
|
||||
@Length(max = 64, message = "套餐名称长度不能超过 {max} 个字符")
|
||||
@Schema(description = "名称", example = "初级套餐")
|
||||
@NotBlank(message = "名称不能为空")
|
||||
@Length(max = 30, message = "名称长度不能超过 {max} 个字符")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 关联的菜单ids
|
||||
* 排序
|
||||
*/
|
||||
@Schema(description = "关联的菜单ids")
|
||||
private Long[] menuIds;
|
||||
@Schema(description = "排序", example = "1")
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 菜单选择是否父子节点关联
|
||||
*/
|
||||
@Schema(description = "菜单选择是否父子节点关联")
|
||||
@Schema(description = "菜单选择是否父子节点关联", example = "true")
|
||||
private Boolean menuCheckStrictly;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述", example = "初级套餐描述")
|
||||
@Length(max = 200, message = "描述长度不能超过 {max} 个字符")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@Schema(description = "状态")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
@Schema(description = "状态", example = "1")
|
||||
private DisEnableStatusEnum status;
|
||||
|
||||
/**
|
||||
* ID
|
||||
* 关联的菜单 ID 列表
|
||||
*/
|
||||
@Schema(hidden = true)
|
||||
private Long id;
|
||||
@Schema(description = "关联的菜单 ID 列表", example = "[1000, 1010, 1011]")
|
||||
private List<Long> menuIds = new ArrayList<>();
|
||||
}
|
@@ -17,40 +17,24 @@
|
||||
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 jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @description: 租户登录用户信息
|
||||
* @author: 小熊
|
||||
* @create: 2024-12-02 20:41
|
||||
* 租户管理员密码修改请求参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @since 2024/12/2 20:41
|
||||
*/
|
||||
@Data
|
||||
public class TenantLoginUserInfoReq implements Serializable {
|
||||
@Schema(description = "租户管理员密码修改请求参数")
|
||||
public class TenantAdminUserPwdUpdateReq implements Serializable {
|
||||
|
||||
/**
|
||||
* 租户id
|
||||
*/
|
||||
@NotNull(message = "租户ID不能为空")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 登录用户名
|
||||
*/
|
||||
@NotEmpty(message = "登录用户名不能为空")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 登录密码
|
||||
* 新密码
|
||||
*/
|
||||
@NotBlank(message = "新密码不能为空")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@Schema(hidden = true)
|
||||
private Long id;
|
||||
}
|
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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;
|
||||
}
|
@@ -16,6 +16,8 @@
|
||||
|
||||
package top.continew.admin.tenant.model.req;
|
||||
|
||||
import cn.sticki.spel.validator.constrain.SpelNotNull;
|
||||
import cn.sticki.spel.validator.jakarta.SpelValid;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Future;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -24,6 +26,8 @@ import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import top.continew.admin.common.constant.RegexConstants;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.tenant.model.enums.TenantIsolationLevelEnum;
|
||||
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
|
||||
|
||||
import java.io.Serial;
|
||||
@@ -31,53 +35,75 @@ import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 创建或修改租户参数
|
||||
* 租户创建或修改请求参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建或修改租户参数")
|
||||
@SpelValid
|
||||
@Schema(description = "租户创建或修改请求参数")
|
||||
public class TenantReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "租户名称")
|
||||
@NotBlank(message = "租户名称不能为空")
|
||||
@Length(max = 64, message = "租户名称长度不能超过 {max} 个字符")
|
||||
@Schema(description = "名称", example = "T0001租户")
|
||||
@NotBlank(message = "名称不能为空")
|
||||
@Length(max = 30, message = "名称长度不能超过 {max} 个字符")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 绑定的域名
|
||||
* 域名
|
||||
*/
|
||||
@Schema(description = "绑定的域名")
|
||||
@Length(max = 128, message = "绑定的域名长度不能超过 {max} 个字符")
|
||||
@Schema(description = "域名", example = "https://t0001.continew.top/")
|
||||
@Length(max = 255, message = "域名长度不能超过 {max} 个字符")
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 租户套餐编号
|
||||
* 过期时间
|
||||
*/
|
||||
@Schema(description = "租户套餐编号")
|
||||
@NotNull(message = "租户套餐编号不能为空")
|
||||
@Schema(description = "过期时间", example = "2023-08-08 08:08:08", type = "string")
|
||||
@Future(message = "过期时间必须是未来时间")
|
||||
private LocalDateTime expireTime;
|
||||
|
||||
/**
|
||||
* 隔离级别
|
||||
*/
|
||||
@Schema(description = "隔离级别", example = "2")
|
||||
@NotNull(message = "隔离级别不能为空", groups = CrudValidationGroup.Create.class)
|
||||
private TenantIsolationLevelEnum isolationLevel;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述", example = "T0001租户描述")
|
||||
@Length(max = 200, message = "描述长度不能超过 {max} 个字符")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@Schema(description = "状态", example = "1")
|
||||
private DisEnableStatusEnum status;
|
||||
|
||||
/**
|
||||
* 套餐 ID
|
||||
*/
|
||||
@Schema(description = "套餐 ID", example = "1")
|
||||
@NotNull(message = "套餐不能为空")
|
||||
private Long packageId;
|
||||
|
||||
/**
|
||||
* 状态(1:启用;2:禁用)
|
||||
* 数据源 ID
|
||||
*/
|
||||
@Schema(description = "状态")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 租户过期时间
|
||||
*/
|
||||
@Schema(description = "租户过期时间")
|
||||
@Future(message = "过期时间必须是未来时间")
|
||||
private LocalDateTime expireTime;
|
||||
@Schema(description = "数据源 ID")
|
||||
@SpelNotNull(condition = "#this.isolationLevel == T(top.continew.admin.tenant.model.enums.TenantIsolationLevelEnum).DATASOURCE", message = "数据源不能为空")
|
||||
private Long datasourceId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
@@ -95,22 +121,10 @@ public class TenantReq implements Serializable {
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
* 编码
|
||||
*/
|
||||
private String tenantSn;
|
||||
|
||||
/**
|
||||
* 隔离级别
|
||||
*/
|
||||
@Schema(description = "隔离级别")
|
||||
@NotNull(message = "隔离级别不能为空", groups = CrudValidationGroup.Create.class)
|
||||
private Integer isolationLevel;
|
||||
|
||||
/**
|
||||
* 数据连接ID
|
||||
*/
|
||||
@Schema(description = "数据连接ID")
|
||||
private Long dbConnectId;
|
||||
@Schema(hidden = true)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* ID
|
||||
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 java.io.Serial;
|
||||
|
||||
/**
|
||||
* 数据源详情响应参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "数据源详情响应参数")
|
||||
public class DatasourceDetailResp extends DatasourceResp {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
@@ -21,62 +21,65 @@ 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 top.continew.admin.tenant.model.enums.DatasourceDatabaseTypeEnum;
|
||||
import top.continew.starter.excel.converter.ExcelBaseEnumConverter;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 租户数据连接详情信息
|
||||
* 数据源响应参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@Schema(description = "租户数据连接详情信息")
|
||||
public class TenantDbConnectDetailResp extends BaseDetailResp {
|
||||
@Schema(description = "数据源响应参数")
|
||||
public class DatasourceResp extends BaseDetailResp {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 连接名称
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "连接名称")
|
||||
@ExcelProperty(value = "连接名称")
|
||||
private String connectName;
|
||||
@Schema(description = "名称", example = "T0001数据源")
|
||||
@ExcelProperty(value = "名称", order = 2)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 连接类型
|
||||
* 数据库类型
|
||||
*/
|
||||
@Schema(description = "连接类型")
|
||||
@ExcelProperty(value = "连接类型")
|
||||
private Integer type;
|
||||
@Schema(description = "数据库类型", example = "1")
|
||||
@ExcelProperty(value = "数据库类型", converter = ExcelBaseEnumConverter.class, order = 3)
|
||||
private DatasourceDatabaseTypeEnum databaseType;
|
||||
|
||||
/**
|
||||
* 连接主机地址
|
||||
* 主机
|
||||
*/
|
||||
@Schema(description = "连接主机地址")
|
||||
@ExcelProperty(value = "连接主机地址")
|
||||
@Schema(description = "主机", example = "123.56.195.68")
|
||||
@ExcelProperty(value = "主机", order = 4)
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* 连接端口
|
||||
* 端口
|
||||
*/
|
||||
@Schema(description = "连接端口")
|
||||
@ExcelProperty(value = "连接端口")
|
||||
@Schema(description = "端口", example = "3306")
|
||||
@ExcelProperty(value = "端口", order = 5)
|
||||
private Integer port;
|
||||
|
||||
/**
|
||||
* 连接用户名
|
||||
* 用户名
|
||||
*/
|
||||
@Schema(description = "连接用户名")
|
||||
@ExcelProperty(value = "连接用户名")
|
||||
@Schema(description = "用户名", example = "root")
|
||||
@ExcelProperty(value = "用户名", order = 6)
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 连接密码
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "连接密码")
|
||||
@ExcelProperty(value = "连接密码")
|
||||
private String password;
|
||||
@Schema(description = "描述", example = "T0001数据源描述")
|
||||
@ExcelProperty(value = "描述", order = 7)
|
||||
private String description;
|
||||
}
|
@@ -16,50 +16,34 @@
|
||||
|
||||
package top.continew.admin.tenant.model.resp;
|
||||
|
||||
import cn.crane4j.annotation.AssembleMethod;
|
||||
import cn.crane4j.annotation.ContainerMethod;
|
||||
import cn.crane4j.annotation.MappingType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import top.continew.admin.tenant.service.PackageMenuService;
|
||||
|
||||
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 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "租户套餐信息")
|
||||
public class TenantPackageResp extends BaseResp {
|
||||
@Schema(description = "套餐详情响应参数")
|
||||
@AssembleMethod(key = "id", prop = ":menuIds", targetType = PackageMenuService.class, method = @ContainerMethod(bindMethod = "listMenuIdsByPackageId", type = MappingType.ORDER_OF_KEYS))
|
||||
public class PackageDetailResp extends PackageResp {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 套餐名称
|
||||
* 关联的菜单 ID 列表
|
||||
*/
|
||||
@Schema(description = "套餐名称")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 关联的菜单ids
|
||||
*/
|
||||
@Schema(description = "关联的菜单ids")
|
||||
@Schema(description = "关联的菜单 ID 列表", example = "[1000, 1010, 1011]")
|
||||
private List<Long> menuIds;
|
||||
|
||||
/**
|
||||
* 菜单选择是否父子节点关联
|
||||
*/
|
||||
@Schema(description = "菜单选择是否父子节点关联")
|
||||
private Boolean menuCheckStrictly;
|
||||
|
||||
/**
|
||||
* 状态(1:启用;2:禁用)
|
||||
*/
|
||||
@Schema(description = "状态(1:启用;2:禁用)")
|
||||
private Integer status;
|
||||
}
|
@@ -16,57 +16,63 @@
|
||||
|
||||
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;
|
||||
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 top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.starter.excel.converter.ExcelBaseEnumConverter;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 租户套餐详情信息
|
||||
* 套餐响应参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@Schema(description = "租户套餐详情信息")
|
||||
public class TenantPackageDetailResp extends BaseDetailResp {
|
||||
@Schema(description = "套餐响应参数")
|
||||
public class PackageResp extends BaseDetailResp {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 套餐名称
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "套餐名称")
|
||||
@ExcelProperty(value = "套餐名称")
|
||||
@Schema(description = "名称", example = "初级套餐")
|
||||
@ExcelProperty(value = "名称", order = 2)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 关联的菜单ids
|
||||
* 排序
|
||||
*/
|
||||
@Schema(description = "关联的菜单ids")
|
||||
@ExcelProperty(value = "关联的菜单ids")
|
||||
private List<Long> menuIds;
|
||||
@Schema(description = "排序", example = "1")
|
||||
@ExcelProperty(value = "排序", order = 3)
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 菜单选择是否父子节点关联
|
||||
*/
|
||||
@Schema(description = "菜单选择是否父子节点关联")
|
||||
@ExcelProperty(value = "菜单选择是否父子节点关联")
|
||||
@Schema(description = "菜单选择是否父子节点关联", example = "true")
|
||||
@ExcelProperty(value = "菜单选择是否父子节点关联", order = 4)
|
||||
private Boolean menuCheckStrictly;
|
||||
|
||||
/**
|
||||
* 状态(1:启用;2:禁用)
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "状态(1:启用;2:禁用)")
|
||||
@ExcelProperty(value = "状态(1:启用;2:禁用)")
|
||||
private Integer status;
|
||||
@Schema(description = "描述", example = "初级套餐")
|
||||
@ExcelProperty(value = "描述", order = 5)
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@Schema(description = "状态", example = "1")
|
||||
@ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class, order = 6)
|
||||
private DisEnableStatusEnum status;
|
||||
}
|
@@ -21,15 +21,16 @@ import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: 租户通用信息返回
|
||||
* @author: 小熊
|
||||
* @create: 2024-11-28 09:53
|
||||
* 租户通用信息返回
|
||||
*
|
||||
* @author 小熊
|
||||
* @since 2024/11/28 09:53
|
||||
*/
|
||||
@Data
|
||||
public class TenantCommonResp {
|
||||
|
||||
/**
|
||||
* 是否开启了多租户
|
||||
* 是否开启了租户
|
||||
*/
|
||||
private Boolean isEnabled;
|
||||
|
||||
|
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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;
|
||||
|
||||
}
|
@@ -16,84 +16,30 @@
|
||||
|
||||
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 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@Schema(description = "租户详情信息")
|
||||
public class TenantDetailResp extends BaseDetailResp {
|
||||
@Schema(description = "租户详情响应参数")
|
||||
public class TenantDetailResp extends TenantResp {
|
||||
|
||||
@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<Long> menuIds;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private String tenantSn;
|
||||
|
||||
/**
|
||||
* 租户绑定的管理用户id
|
||||
*/
|
||||
private Long userId;
|
||||
@Schema(description = "租户管理员", example = "666")
|
||||
@ExcelProperty(value = "租户管理员", order = 13)
|
||||
private Long adminUser;
|
||||
}
|
@@ -16,76 +16,114 @@
|
||||
|
||||
package top.continew.admin.tenant.model.resp;
|
||||
|
||||
import cn.crane4j.annotation.AssembleMethod;
|
||||
import cn.crane4j.annotation.ContainerMethod;
|
||||
import cn.crane4j.annotation.Mapping;
|
||||
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.BaseResp;
|
||||
import top.continew.admin.common.base.model.resp.BaseDetailResp;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.tenant.model.enums.TenantIsolationLevelEnum;
|
||||
import top.continew.admin.tenant.service.DatasourceService;
|
||||
import top.continew.admin.tenant.service.PackageService;
|
||||
import top.continew.starter.excel.converter.ExcelBaseEnumConverter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 租户信息
|
||||
* 租户响应参数
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "租户信息")
|
||||
public class TenantResp extends BaseResp {
|
||||
@ExcelIgnoreUnannotated
|
||||
@Schema(description = "租户响应参数")
|
||||
public class TenantResp extends BaseDetailResp {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "租户名称")
|
||||
@Schema(description = "名称", example = "T0001租户")
|
||||
@ExcelProperty(value = "名称", order = 2)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 绑定的域名
|
||||
* 编码
|
||||
*/
|
||||
@Schema(description = "绑定的域名")
|
||||
@Schema(description = "编码", example = "T0001")
|
||||
@ExcelProperty(value = "编码", order = 3)
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
@Schema(description = "域名", example = "https://t0001.continew.top/")
|
||||
@ExcelProperty(value = "域名", order = 4)
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 租户套餐编号
|
||||
* 过期时间
|
||||
*/
|
||||
@Schema(description = "租户套餐编号")
|
||||
private Long packageId;
|
||||
|
||||
/**
|
||||
* 状态(1:启用;2:禁用)
|
||||
*/
|
||||
@Schema(description = "状态(1:启用;2:禁用)")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 租户过期时间
|
||||
*/
|
||||
@Schema(description = "租户过期时间")
|
||||
@Schema(description = "过期时间", example = "2023-08-08 08:08:08")
|
||||
@ExcelProperty(value = "过期时间", order = 5)
|
||||
private LocalDateTime expireTime;
|
||||
|
||||
/**
|
||||
* 绑定的套餐名称
|
||||
*/
|
||||
@Schema(description = "绑定的套餐名称")
|
||||
private String packageName;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private String tenantSn;
|
||||
|
||||
/**
|
||||
* 隔离级别
|
||||
*/
|
||||
@Schema(description = "隔离级别")
|
||||
private Integer isolationLevel;
|
||||
@Schema(description = "隔离级别", example = "2")
|
||||
@ExcelProperty(value = "隔离级别", converter = ExcelBaseEnumConverter.class, order = 6)
|
||||
private TenantIsolationLevelEnum isolationLevel;
|
||||
|
||||
/**
|
||||
* 数据连接ID
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "数据连接ID")
|
||||
private Long dbConnectId;
|
||||
@Schema(description = "描述", example = "T0001租户描述")
|
||||
@ExcelProperty(value = "描述", order = 7)
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@Schema(description = "状态", example = "1")
|
||||
@ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class, order = 8)
|
||||
private DisEnableStatusEnum status;
|
||||
|
||||
/**
|
||||
* 套餐 ID
|
||||
*/
|
||||
@Schema(description = "套餐 ID", example = "1")
|
||||
@ExcelProperty(value = "套餐 ID", order = 9)
|
||||
@AssembleMethod(props = @Mapping(src = "name", ref = "packageName"), targetType = PackageService.class, method = @ContainerMethod(bindMethod = "get", resultType = PackageResp.class))
|
||||
private Long packageId;
|
||||
|
||||
/**
|
||||
* 数据源 ID
|
||||
*/
|
||||
@Schema(description = "数据源 ID", example = "1")
|
||||
@ExcelProperty(value = "数据源 ID", order = 10)
|
||||
@AssembleMethod(props = @Mapping(src = "name", ref = "datasourceName"), targetType = DatasourceService.class, method = @ContainerMethod(bindMethod = "get", resultType = DatasourceResp.class))
|
||||
private Long datasourceId;
|
||||
|
||||
/**
|
||||
* 套餐名称
|
||||
*/
|
||||
@Schema(description = "套餐名称", example = "初级套餐")
|
||||
@ExcelProperty(value = "套餐名称", order = 11)
|
||||
private String packageName;
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
*/
|
||||
@Schema(description = "数据源名称", example = "T0001数据源")
|
||||
@ExcelProperty(value = "数据源名称", order = 12)
|
||||
private String datasourceName;
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.DatasourceDO;
|
||||
import top.continew.admin.tenant.model.query.DatasourceQuery;
|
||||
import top.continew.admin.tenant.model.req.DatasourceReq;
|
||||
import top.continew.admin.tenant.model.resp.DatasourceDetailResp;
|
||||
import top.continew.admin.tenant.model.resp.DatasourceResp;
|
||||
import top.continew.starter.data.service.IService;
|
||||
|
||||
/**
|
||||
* 数据源业务接口
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
public interface DatasourceService extends BaseService<DatasourceResp, DatasourceDetailResp, DatasourceQuery, DatasourceReq>, IService<DatasourceDO> {
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*
|
||||
* @param id ID
|
||||
*/
|
||||
void testConnection(Long id);
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*
|
||||
* @param databaseName 数据库名称
|
||||
* @param id ID
|
||||
*/
|
||||
void initDb(String databaseName, Long id);
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 java.util.List;
|
||||
|
||||
/**
|
||||
* 套餐和菜单关联业务接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/13 20:44
|
||||
*/
|
||||
public interface PackageMenuService {
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*
|
||||
* @param menuIds 菜单 ID 列表
|
||||
* @param packageId 套餐 ID
|
||||
* @return 是否成功(true:成功;false:无变更/失败)
|
||||
*/
|
||||
boolean add(List<Long> menuIds, Long packageId);
|
||||
|
||||
/**
|
||||
* 根据套餐 ID 查询
|
||||
*
|
||||
* @param packageId 套餐 ID
|
||||
* @return 菜单 ID 列表
|
||||
*/
|
||||
List<Long> listMenuIdsByPackageId(Long packageId);
|
||||
}
|
@@ -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;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import top.continew.admin.tenant.mapper.PackageMenuMapper;
|
||||
import top.continew.admin.tenant.model.entity.PackageMenuDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 套餐和菜单关联业务实现
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/7/13 20:45
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PackageMenuServiceImpl implements PackageMenuService {
|
||||
|
||||
private final PackageMenuMapper baseMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean add(List<Long> menuIds, Long packageId) {
|
||||
// 检查是否有变更
|
||||
List<Long> oldMenuIdList = baseMapper.lambdaQuery()
|
||||
.select(PackageMenuDO::getMenuId)
|
||||
.eq(PackageMenuDO::getPackageId, packageId)
|
||||
.list()
|
||||
.stream()
|
||||
.map(PackageMenuDO::getMenuId)
|
||||
.toList();
|
||||
if (CollUtil.isEmpty(CollUtil.disjunction(menuIds, oldMenuIdList))) {
|
||||
return false;
|
||||
}
|
||||
// 删除原有关联
|
||||
baseMapper.lambdaUpdate().eq(PackageMenuDO::getPackageId, packageId).remove();
|
||||
// 保存最新关联
|
||||
List<PackageMenuDO> newList = menuIds.stream().map(menuId -> new PackageMenuDO(packageId, menuId)).toList();
|
||||
return baseMapper.insertBatch(newList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listMenuIdsByPackageId(Long packageId) {
|
||||
return baseMapper.lambdaQuery()
|
||||
.select(PackageMenuDO::getMenuId)
|
||||
.eq(PackageMenuDO::getPackageId, packageId)
|
||||
.list()
|
||||
.stream()
|
||||
.map(PackageMenuDO::getMenuId)
|
||||
.toList();
|
||||
}
|
||||
}
|
@@ -17,15 +17,17 @@
|
||||
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;
|
||||
import top.continew.admin.tenant.model.entity.PackageDO;
|
||||
import top.continew.admin.tenant.model.query.PackageQuery;
|
||||
import top.continew.admin.tenant.model.req.PackageReq;
|
||||
import top.continew.admin.tenant.model.resp.PackageDetailResp;
|
||||
import top.continew.admin.tenant.model.resp.PackageResp;
|
||||
import top.continew.starter.data.service.IService;
|
||||
|
||||
/**
|
||||
* 租户套餐业务接口
|
||||
* 套餐业务接口
|
||||
*
|
||||
* @author 小熊
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
public interface TenantPackageService extends BaseService<TenantPackageResp, TenantPackageDetailResp, TenantPackageQuery, TenantPackageReq> {}
|
||||
public interface PackageService extends BaseService<PackageResp, PackageDetailResp, PackageQuery, PackageReq>, IService<PackageDO> {}
|
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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<TenantDbConnectResp, TenantDbConnectDetailResp, TenantDbConnectQuery, TenantDbConnectReq> {
|
||||
|
||||
JdbcTemplate getConnectJdbcTemplateById(Long id);
|
||||
|
||||
}
|
@@ -31,33 +31,21 @@ import java.util.List;
|
||||
* 租户业务接口
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
public interface TenantService extends BaseService<TenantResp, TenantDetailResp, TenantQuery, TenantReq>, IService<TenantDO> {
|
||||
|
||||
/**
|
||||
* 检查租户状态
|
||||
*
|
||||
* @param id ID
|
||||
* @return 租户信息
|
||||
*/
|
||||
TenantDO checkStatus(Long id);
|
||||
|
||||
/**
|
||||
* 获取所有可用的租户列表
|
||||
*/
|
||||
List<TenantAvailableResp> getAvailableList();
|
||||
|
||||
/**
|
||||
* 租户绑定用户
|
||||
*/
|
||||
void bindUser(Long tenantId, Long userId);
|
||||
|
||||
/**
|
||||
* 检查租户状态
|
||||
*/
|
||||
void checkStatus();
|
||||
|
||||
/**
|
||||
* 根据id获取租户DO
|
||||
*/
|
||||
TenantDO getTenantById(Long id);
|
||||
|
||||
/**
|
||||
* 根据用户id获取租户信息
|
||||
*/
|
||||
TenantDO getTenantByUserId(Long userId);
|
||||
|
||||
}
|
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.io.resource.ClassPathResource;
|
||||
import cn.hutool.core.io.resource.Resource;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alicp.jetcache.anno.Cached;
|
||||
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.util.SecureUtils;
|
||||
import top.continew.admin.tenant.constant.TenantCacheConstants;
|
||||
import top.continew.admin.tenant.mapper.DatasourceMapper;
|
||||
import top.continew.admin.tenant.mapper.TenantMapper;
|
||||
import top.continew.admin.tenant.model.entity.DatasourceDO;
|
||||
import top.continew.admin.tenant.model.entity.TenantDO;
|
||||
import top.continew.admin.tenant.model.query.DatasourceQuery;
|
||||
import top.continew.admin.tenant.model.req.DatasourceReq;
|
||||
import top.continew.admin.tenant.model.resp.DatasourceDetailResp;
|
||||
import top.continew.admin.tenant.model.resp.DatasourceResp;
|
||||
import top.continew.admin.tenant.service.DatasourceService;
|
||||
import top.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ExceptionUtils;
|
||||
import top.continew.starter.core.util.validation.CheckUtils;
|
||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据源业务实现
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/12/12 19:13
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatasourceServiceImpl extends BaseServiceImpl<DatasourceMapper, DatasourceDO, DatasourceResp, DatasourceDetailResp, DatasourceQuery, DatasourceReq> implements DatasourceService {
|
||||
|
||||
private final TenantMapper tenantMapper;
|
||||
|
||||
@Override
|
||||
@Cached(name = TenantCacheConstants.TENANT_DATASOURCE_KEY_PREFIX, key = "#id")
|
||||
public DatasourceDetailResp get(Long id) {
|
||||
return super.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCreate(DatasourceReq req) {
|
||||
// 解密密码
|
||||
req.setPassword(this.decryptPassword(req.getPassword(), null));
|
||||
// 检查是否重复
|
||||
this.checkRepeat(req, null);
|
||||
// 测试连接
|
||||
req.getDatabaseType().testConnection(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(DatasourceReq req, Long id) {
|
||||
DatasourceDO oldDatasource = super.getById(id);
|
||||
// 解密密码
|
||||
req.setPassword(this.decryptPassword(req.getPassword(), oldDatasource));
|
||||
// 检查是否重复
|
||||
this.checkRepeat(req, id);
|
||||
CheckUtils.throwIf(oldDatasource.getDatabaseType() != req.getDatabaseType(), "数据库类型不能修改");
|
||||
// 测试连接
|
||||
boolean isUpdated = this.isUpdated(req, oldDatasource);
|
||||
if (isUpdated) {
|
||||
req.getDatabaseType().testConnection(req);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpdate(DatasourceReq req, DatasourceDO entity) {
|
||||
RedisUtils.delete(TenantCacheConstants.TENANT_DATASOURCE_KEY_PREFIX + entity.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDelete(List<Long> ids) {
|
||||
CheckUtils.throwIf(tenantMapper.lambdaQuery().in(TenantDO::getDatasourceId, ids).exists(), "所选数据源存在关联租户,不允许删除");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterDelete(List<Long> ids) {
|
||||
ids.forEach(id -> RedisUtils.delete(TenantCacheConstants.TENANT_DATASOURCE_KEY_PREFIX + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testConnection(Long id) {
|
||||
DatasourceDO datasource = super.getById(id);
|
||||
datasource.getDatabaseType().testConnection(BeanUtil.copyProperties(datasource, DatasourceReq.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initDb(String databaseName, Long id) {
|
||||
DatasourceDO datasource = super.getById(id);
|
||||
DataSource ds = datasource.getDatabaseType()
|
||||
.buildDataSource(BeanUtil.copyProperties(datasource, DatasourceReq.class));
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);
|
||||
// 建库
|
||||
jdbcTemplate.execute("CREATE DATABASE %s CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
.formatted(databaseName));
|
||||
jdbcTemplate.execute("USE %s;".formatted(databaseName));
|
||||
// TODO 初始化数据
|
||||
Resource resource = new ClassPathResource("db/changelog/mysql/tenant_table.sql");
|
||||
Arrays.stream(resource.readUtf8Str().split(StringConstants.SEMICOLON))
|
||||
.map(String::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.forEach(jdbcTemplate::execute);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密密码
|
||||
*
|
||||
* @param encryptPassword 加密的密码
|
||||
* @param oldDatasource 旧数据源
|
||||
* @return 解密后的密码
|
||||
*/
|
||||
private String decryptPassword(String encryptPassword, DatasourceDO oldDatasource) {
|
||||
// 修改时,密码为空将不更改密码
|
||||
if (oldDatasource != null && StrUtil.isBlank(encryptPassword)) {
|
||||
return oldDatasource.getPassword();
|
||||
}
|
||||
// 解密
|
||||
String decryptPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(encryptPassword));
|
||||
ValidationUtils.throwIfNull(decryptPassword, "密码解密失败");
|
||||
ValidationUtils.throwIf(decryptPassword.length() > 128, "密码长度不能超过 128 个字符");
|
||||
return decryptPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据源是否存在
|
||||
*
|
||||
* @param req 数据源信息
|
||||
* @param id ID
|
||||
*/
|
||||
private void checkRepeat(DatasourceReq req, Long id) {
|
||||
CheckUtils.throwIf(baseMapper.lambdaQuery()
|
||||
.eq(DatasourceDO::getHost, req.getHost())
|
||||
.eq(DatasourceDO::getPort, req.getPort())
|
||||
.eq(DatasourceDO::getUsername, req.getUsername())
|
||||
.ne(id != null, DatasourceDO::getId, id)
|
||||
.exists(), "相同配置数据源已存在");
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否更新了配置
|
||||
*
|
||||
* @param req 数据源请求参数
|
||||
* @param oldDatasource 旧数据源
|
||||
* @return 是否更新了配置
|
||||
*/
|
||||
private boolean isUpdated(DatasourceReq req, DatasourceDO oldDatasource) {
|
||||
return !(oldDatasource.getHost().equals(req.getHost()) && oldDatasource.getPort()
|
||||
.equals(req.getPort()) && oldDatasource.getUsername().equals(req.getUsername()) && oldDatasource
|
||||
.getPassword()
|
||||
.equals(req.getPassword()));
|
||||
}
|
||||
}
|
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.collection.CollUtil;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import top.continew.admin.common.base.service.BaseServiceImpl;
|
||||
import top.continew.admin.system.model.entity.MenuDO;
|
||||
import top.continew.admin.system.service.MenuService;
|
||||
import top.continew.admin.tenant.mapper.PackageMapper;
|
||||
import top.continew.admin.tenant.mapper.TenantMapper;
|
||||
import top.continew.admin.tenant.model.entity.PackageDO;
|
||||
import top.continew.admin.tenant.model.entity.TenantDO;
|
||||
import top.continew.admin.tenant.model.query.PackageQuery;
|
||||
import top.continew.admin.tenant.model.req.PackageReq;
|
||||
import top.continew.admin.tenant.model.resp.PackageDetailResp;
|
||||
import top.continew.admin.tenant.model.resp.PackageResp;
|
||||
import top.continew.admin.tenant.service.PackageMenuService;
|
||||
import top.continew.admin.tenant.service.PackageService;
|
||||
import top.continew.starter.core.util.validation.CheckUtils;
|
||||
import top.continew.starter.extension.tenant.TenantHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 套餐业务实现
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 11:25
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PackageServiceImpl extends BaseServiceImpl<PackageMapper, PackageDO, PackageResp, PackageDetailResp, PackageQuery, PackageReq> implements PackageService {
|
||||
|
||||
private final PackageMenuService packageMenuService;
|
||||
private final MenuService menuService;
|
||||
private final TenantMapper tenantMapper;
|
||||
private final TenantHandler tenantHandler;
|
||||
|
||||
@Override
|
||||
@DSTransactional(rollbackFor = Exception.class)
|
||||
public Long create(PackageReq req) {
|
||||
this.checkNameRepeat(req.getName(), null);
|
||||
// 新增信息
|
||||
Long id = super.create(req);
|
||||
// 保存套餐和菜单关联
|
||||
packageMenuService.add(req.getMenuIds(), id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DSTransactional(rollbackFor = Exception.class)
|
||||
public void update(PackageReq req, Long id) {
|
||||
this.checkNameRepeat(req.getName(), id);
|
||||
// 更新信息
|
||||
super.update(req, id);
|
||||
// 保存套餐和菜单关联
|
||||
boolean isSaveMenuSuccess = packageMenuService.add(req.getMenuIds(), id);
|
||||
if (!isSaveMenuSuccess) {
|
||||
return;
|
||||
}
|
||||
// 更新租户菜单
|
||||
List<Long> tenantIdList = tenantMapper.lambdaQuery()
|
||||
.select(TenantDO::getId)
|
||||
.eq(TenantDO::getPackageId, id)
|
||||
.list()
|
||||
.stream()
|
||||
.map(TenantDO::getId)
|
||||
.toList();
|
||||
if (CollUtil.isEmpty(tenantIdList)) {
|
||||
return;
|
||||
}
|
||||
List<Long> oldMenuIds = packageMenuService.listMenuIdsByPackageId(id);
|
||||
List<Long> newMenuIds = req.getMenuIds();
|
||||
// 如果有删除的菜单则绑定了套餐的租户对应的菜单也会删除
|
||||
List<Long> deleteMenuIds = new ArrayList<>(oldMenuIds);
|
||||
deleteMenuIds.removeAll(newMenuIds);
|
||||
if (CollUtil.isNotEmpty(deleteMenuIds)) {
|
||||
List<MenuDO> deleteMenus = menuService.listByIds(deleteMenuIds);
|
||||
tenantIdList.forEach(tenantId -> tenantHandler.execute(tenantId, () -> menuService
|
||||
.deleteTenantMenus(deleteMenus)));
|
||||
}
|
||||
// 如果有新增的菜单则绑定了套餐的租户对应的菜单也会新增
|
||||
List<Long> addMenuIds = new ArrayList<>(newMenuIds);
|
||||
addMenuIds.removeAll(oldMenuIds);
|
||||
if (CollUtil.isNotEmpty(addMenuIds)) {
|
||||
List<MenuDO> addMenus = menuService.listByIds(addMenuIds);
|
||||
for (MenuDO addMenu : addMenus) {
|
||||
MenuDO parentMenu = addMenu.getParentId() != 0 ? menuService.getById(addMenu.getParentId()) : null;
|
||||
tenantIdList.forEach(tenantId -> tenantHandler.execute(tenantId, () -> menuService
|
||||
.addTenantMenu(addMenu, parentMenu)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDelete(List<Long> ids) {
|
||||
CheckUtils.throwIf(tenantMapper.lambdaQuery().in(TenantDO::getPackageId, ids).exists(), "所选套餐存在关联租户,不允许删除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 名称是否存在
|
||||
*
|
||||
* @param name 名称
|
||||
* @param id ID
|
||||
*/
|
||||
private void checkNameRepeat(String name, Long id) {
|
||||
CheckUtils.throwIf(baseMapper.lambdaQuery()
|
||||
.eq(PackageDO::getName, name)
|
||||
.ne(id != null, PackageDO::getId, id)
|
||||
.exists(), "名称为 [{}] 的套餐已存在", name);
|
||||
}
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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<TenantDbConnectMapper, TenantDbConnectDO, TenantDbConnectResp, TenantDbConnectDetailResp, TenantDbConnectQuery, TenantDbConnectReq> 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<Long> 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<Long> 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;
|
||||
}
|
||||
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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<TenantPackageMapper, TenantPackageDO, TenantPackageResp, TenantPackageDetailResp, TenantPackageQuery, TenantPackageReq> 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<Long> ids) {
|
||||
CheckUtils.throwIf(tenantMapper.selectCount(Wrappers.lambdaQuery(TenantDO.class)
|
||||
.in(TenantDO::getPackageId, ids)) > 0, "存在关联租户无法删除");
|
||||
}
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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;
|
||||
}
|
||||
|
||||
}
|
@@ -18,185 +18,157 @@ 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 cn.hutool.extra.spring.SpringUtil;
|
||||
import com.alicp.jetcache.anno.Cached;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
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 me.ahoo.cosid.provider.IdGeneratorProvider;
|
||||
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.config.TenantProperties;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.tenant.constant.TenantCacheConstants;
|
||||
import top.continew.admin.tenant.constant.TenantConstants;
|
||||
import top.continew.admin.tenant.handler.TenantDataHandler;
|
||||
import top.continew.admin.tenant.mapper.TenantMapper;
|
||||
import top.continew.admin.tenant.mapper.TenantPackageMapper;
|
||||
import top.continew.admin.tenant.model.entity.PackageDO;
|
||||
import top.continew.admin.tenant.model.entity.TenantDO;
|
||||
import top.continew.admin.tenant.model.entity.TenantPackageDO;
|
||||
import top.continew.admin.tenant.model.enums.TenantIsolationLevelEnum;
|
||||
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.DatasourceService;
|
||||
import top.continew.admin.tenant.service.PackageMenuService;
|
||||
import top.continew.admin.tenant.service.PackageService;
|
||||
import top.continew.admin.tenant.service.TenantService;
|
||||
import top.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import top.continew.starter.core.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 top.continew.starter.extension.tenant.TenantHandler;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 租户业务实现
|
||||
*
|
||||
* @author 小熊
|
||||
* @author Charles7c
|
||||
* @since 2024/11/26 17:20
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, TenantResp, TenantDetailResp, TenantQuery, TenantReq> implements TenantService {
|
||||
|
||||
private final TenantPackageMapper packageMapper;
|
||||
private final TenantProperties tenantProperties;
|
||||
private final TenantDbConnectService dbConnectService;
|
||||
private final IdGeneratorProvider idGeneratorProvider;
|
||||
private final PackageMenuService packageMenuService;
|
||||
private final PackageService packageService;
|
||||
private final DatasourceService datasourceService;
|
||||
private final TenantDataHandler tenantDataHandler;
|
||||
|
||||
@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);
|
||||
@DSTransactional(rollbackFor = Exception.class)
|
||||
public Long create(TenantReq req) {
|
||||
this.checkNameRepeat(req.getName(), null);
|
||||
// 检查租户套餐
|
||||
List<Long> menuIds = packageMenuService.listMenuIdsByPackageId(req.getPackageId());
|
||||
CheckUtils.throwIfEmpty(menuIds, "所选套餐无可用菜单");
|
||||
// 生成租户编码
|
||||
req.setCode(this.generateCode());
|
||||
// 新增信息
|
||||
Long id = super.create(req);
|
||||
// 初始化数据库
|
||||
if (TenantIsolationLevelEnum.DATASOURCE.equals(req.getIsolationLevel())) {
|
||||
datasourceService.initDb(TenantConstants.TENANT_DB_PREFIX + req.getCode(), req.getDatasourceId());
|
||||
}
|
||||
// 初始化租户数据
|
||||
req.setId(id);
|
||||
tenantDataHandler.init(req);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(TenantReq req, Long id) {
|
||||
this.checkNameRepeat(req.getName(), id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpdate(TenantReq req, TenantDO entity) {
|
||||
// 更新租户缓存
|
||||
RedisUtils.set(TenantCacheConstants.TENANT_KEY_PREFIX + entity.getId(), entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDelete(List<Long> ids) {
|
||||
// 在租户中执行数据清除
|
||||
for (Long id : ids) {
|
||||
SpringUtil.getBean(TenantHandler.class).execute(id, tenantDataHandler::clear);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterDelete(List<Long> ids) {
|
||||
ids.forEach(id -> RedisUtils.delete(TenantCacheConstants.TENANT_KEY_PREFIX + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cached(name = TenantCacheConstants.TENANT_KEY_PREFIX, key = "#id")
|
||||
public TenantDO getById(Serializable id) {
|
||||
return super.getById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TenantDO checkStatus(Long id) {
|
||||
TenantDO tenant = this.getById(id);
|
||||
if (!tenantProperties.isEnabled() || id.equals(tenantProperties.getSuperTenantId())) {
|
||||
return tenant;
|
||||
}
|
||||
CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE.getValue(), tenant.getStatus(), "此租户已被禁用");
|
||||
CheckUtils.throwIf(tenant.getExpireTime() != null && tenant.getExpireTime()
|
||||
.isBefore(LocalDateTime.now()), "此租户已过期");
|
||||
// 检查套餐
|
||||
PackageDO tenantPackage = packageService.getById(tenant.getPackageId());
|
||||
CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE.getValue(), tenantPackage.getStatus(), "此租户套餐已被禁用");
|
||||
return tenant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TenantAvailableResp> getAvailableList() {
|
||||
List<TenantDO> tenantDOS = baseMapper.selectList(Wrappers.lambdaQuery(TenantDO.class)
|
||||
List<TenantDO> tenantList = 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);
|
||||
return BeanUtil.copyToList(tenantList, TenantAvailableResp.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResp<TenantResp> 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<TenantResp> list = baseMapper.listTenant(new Page<>(pageQuery.getPage(), pageQuery
|
||||
.getSize()), queryWrapper);
|
||||
PageResp<TenantResp> pageResp = PageResp.build(list, TenantResp.class);
|
||||
return pageResp;
|
||||
/**
|
||||
* 名称是否存在
|
||||
*
|
||||
* @param name 名称
|
||||
* @param id ID
|
||||
*/
|
||||
private void checkNameRepeat(String name, Long id) {
|
||||
CheckUtils.throwIf(baseMapper.lambdaQuery()
|
||||
.eq(TenantDO::getName, name)
|
||||
.ne(id != null, TenantDO::getId, id)
|
||||
.exists(), "名称为 [{}] 的租户已存在", name);
|
||||
}
|
||||
|
||||
@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;
|
||||
/**
|
||||
* 生成租户编码
|
||||
*
|
||||
* @return 租户编码
|
||||
*/
|
||||
private String generateCode() {
|
||||
String code;
|
||||
do {
|
||||
code = idGeneratorProvider.getRequired(TenantConstants.CODE_GENERATOR_KEY).generateAsString();
|
||||
} while (baseMapper.lambdaQuery().eq(TenantDO::getCode, code).exists());
|
||||
return code;
|
||||
}
|
||||
|
||||
@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<Long> 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));
|
||||
}
|
||||
|
||||
}
|
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.tenant.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<String, String> 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<String, String> 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<String, String> getDefaultMysqlConnectParameter() {
|
||||
Map<String, String> 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;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user