mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	refactor(tenant): 移除租户数据源及数据源级隔离适配代码
This commit is contained in:
		| @@ -1,35 +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.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 { | ||||
| } | ||||
| @@ -16,19 +16,11 @@ | ||||
|  | ||||
| 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.autoconfigure.TenantProperties; | ||||
| import top.continew.starter.extension.tenant.config.TenantProvider; | ||||
| import top.continew.starter.extension.tenant.context.TenantContext; | ||||
| import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; | ||||
| @@ -46,39 +38,21 @@ 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(); | ||||
|         // 超级租户默认使用行级隔离 | ||||
|         context.setIsolationLevel(TenantIsolationLevel.LINE); | ||||
|         // 超级租户 | ||||
|         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(); | ||||
|         tenantService.checkStatus(tenantId); | ||||
|         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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -36,11 +36,6 @@ public class TenantCacheConstants { | ||||
|      */ | ||||
|     public static final String TENANT_KEY_PREFIX = "TENANT" + DELIMITER; | ||||
|  | ||||
|     /** | ||||
|      * 租户数据源前缀 | ||||
|      */ | ||||
|     public static final String TENANT_DATASOURCE_KEY_PREFIX = TENANT_KEY_PREFIX + "DATASOURCE" + DELIMITER; | ||||
|  | ||||
|     private TenantCacheConstants() { | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,11 +24,6 @@ package top.continew.admin.tenant.constant; | ||||
|  */ | ||||
| public class TenantConstants { | ||||
|  | ||||
|     /** | ||||
|      * 租户数据库前缀 | ||||
|      */ | ||||
|     public static final String TENANT_DB_PREFIX = "tenant_"; | ||||
|  | ||||
|     /** | ||||
|      * 编码生成器 KEY | ||||
|      */ | ||||
|   | ||||
| @@ -1,55 +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 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); | ||||
|     } | ||||
| } | ||||
| @@ -24,8 +24,8 @@ 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.config.TenantExtensionProperties; | ||||
| 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; | ||||
| @@ -51,7 +51,7 @@ import java.util.List; | ||||
| @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 TenantExtensionProperties tenantExtensionProperties; | ||||
|     private final MenuService menuService; | ||||
|  | ||||
|     @Operation(summary = "查询租户套餐菜单", description = "查询租户套餐菜单树列表") | ||||
| @@ -61,7 +61,7 @@ public class PackageController extends BaseController<PackageService, PackageRes | ||||
|         MenuQuery query = new MenuQuery(); | ||||
|         query.setStatus(DisEnableStatusEnum.ENABLE); | ||||
|         // 过滤掉租户不能使用的菜单 | ||||
|         query.setExcludeMenuIdList(tenantProperties.getIgnoreMenus()); | ||||
|         query.setExcludeMenuIdList(tenantExtensionProperties.getIgnoreMenus()); | ||||
|         return menuService.tree(query, null, true); | ||||
|     } | ||||
| } | ||||
| @@ -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 io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import top.continew.admin.tenant.model.query.PackageQuery; | ||||
| import top.continew.admin.tenant.service.PackageService; | ||||
| import top.continew.starter.extension.crud.model.query.SortQuery; | ||||
| import top.continew.starter.extension.crud.model.resp.LabelValueResp; | ||||
| import top.continew.starter.log.annotation.Log; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 公共 API | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2025/7/15 20:32 | ||||
|  */ | ||||
| @Tag(name = "公共 API") | ||||
| @Log(ignore = true) | ||||
| @Validated | ||||
| @RestController | ||||
| @RequiredArgsConstructor | ||||
| @RequestMapping("/tenant/common") | ||||
| public class TenantCommonController { | ||||
|  | ||||
|     private final PackageService packageService; | ||||
|  | ||||
|     @Operation(summary = "查询套餐字典", description = "查询套餐字典列表") | ||||
|     @GetMapping("/dict/package") | ||||
|     public List<LabelValueResp> listPackageDict(PackageQuery query, SortQuery sortQuery) { | ||||
|         return packageService.listDict(query, sortQuery); | ||||
|     } | ||||
| } | ||||
| @@ -19,14 +19,12 @@ package top.continew.admin.tenant.controller; | ||||
| import cn.dev33.satoken.annotation.SaCheckPermission; | ||||
| import cn.dev33.satoken.annotation.SaIgnore; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import com.baomidou.dynamic.datasource.annotation.DSTransactional; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import jakarta.validation.Valid; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import top.continew.admin.common.base.controller.BaseController; | ||||
| import top.continew.admin.common.config.TenantProperties; | ||||
| import top.continew.admin.common.util.SecureUtils; | ||||
| import top.continew.admin.system.model.entity.user.UserDO; | ||||
| import top.continew.admin.system.model.req.user.UserPasswordResetReq; | ||||
| @@ -44,6 +42,7 @@ 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.tenant.TenantHandler; | ||||
| import top.continew.starter.extension.tenant.autoconfigure.TenantProperties; | ||||
|  | ||||
| /** | ||||
|  * 租户管理 API | ||||
| @@ -71,7 +70,6 @@ public class TenantController extends BaseController<TenantService, TenantResp, | ||||
|         return commonResp; | ||||
|     } | ||||
|  | ||||
|     @DSTransactional(rollbackFor = Exception.class) | ||||
|     @Operation(summary = "修改租户管理员密码", description = "修改租户管理员密码") | ||||
|     @SaCheckPermission("tenant:management:updateAdminUserPwd") | ||||
|     @PutMapping("/{id}/admin/pwd") | ||||
|   | ||||
| @@ -1,30 +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.mapper; | ||||
|  | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
| import top.continew.admin.tenant.model.entity.DatasourceDO; | ||||
| import top.continew.starter.data.mapper.BaseMapper; | ||||
|  | ||||
| /** | ||||
|  * 数据源 Mapper | ||||
|  * | ||||
|  * @author 小熊 | ||||
|  * @since 2024/12/12 19:13 | ||||
|  */ | ||||
| @Mapper | ||||
| public interface DatasourceMapper extends BaseMapper<DatasourceDO> {} | ||||
| @@ -1,78 +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.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 | ||||
| @DictModel | ||||
| @TableName("tenant_datasource") | ||||
| public class DatasourceDO extends BaseDO { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 名称 | ||||
|      */ | ||||
|     private String name; | ||||
|  | ||||
|     /** | ||||
|      * 数据库类型 | ||||
|      */ | ||||
|     private DatasourceDatabaseTypeEnum databaseType; | ||||
|  | ||||
|     /** | ||||
|      * 主机 | ||||
|      */ | ||||
|     private String host; | ||||
|  | ||||
|     /** | ||||
|      * 端口 | ||||
|      */ | ||||
|     private Integer port; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 密码 | ||||
|      */ | ||||
|     @FieldEncrypt | ||||
|     private String password; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     private String description; | ||||
| } | ||||
| @@ -20,7 +20,6 @@ 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; | ||||
| @@ -59,11 +58,6 @@ public class TenantDO extends BaseDO { | ||||
|      */ | ||||
|     private LocalDateTime expireTime; | ||||
|  | ||||
|     /** | ||||
|      * 隔离级别 | ||||
|      */ | ||||
|     private TenantIsolationLevelEnum isolationLevel; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
| @@ -83,9 +77,4 @@ public class TenantDO extends BaseDO { | ||||
|      * 套餐 ID | ||||
|      */ | ||||
|     private Long packageId; | ||||
|  | ||||
|     /** | ||||
|      * 数据源 ID | ||||
|      */ | ||||
|     private Long datasourceId; | ||||
| } | ||||
| @@ -1,133 +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.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(); | ||||
| } | ||||
| @@ -1,49 +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.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; | ||||
| } | ||||
| @@ -1,47 +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.query; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
| import top.continew.starter.data.annotation.Query; | ||||
| import top.continew.starter.data.enums.QueryType; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 数据源查询条件 | ||||
|  * | ||||
|  * @author 小熊 | ||||
|  * @author Charles7c | ||||
|  * @since 2024/12/12 19:13 | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "数据源查询条件") | ||||
| public class DatasourceQuery implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 关键词 | ||||
|      */ | ||||
|     @Schema(description = "关键词", example = "数据源") | ||||
|     @Query(columns = {"name", "description"}, type = QueryType.LIKE) | ||||
|     private String description; | ||||
| } | ||||
| @@ -41,7 +41,7 @@ public class TenantQuery implements Serializable { | ||||
|     /** | ||||
|      * 关键词 | ||||
|      */ | ||||
|     @Schema(description = "关键词", example = "T0001") | ||||
|     @Schema(description = "关键词", example = "Xxx租户") | ||||
|     @Query(columns = {"name", "description"}, type = QueryType.LIKE) | ||||
|     private String description; | ||||
|  | ||||
|   | ||||
| @@ -1,95 +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 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; | ||||
| } | ||||
| @@ -33,8 +33,9 @@ import java.io.Serializable; | ||||
| public class TenantAdminUserPwdUpdateReq implements Serializable { | ||||
|  | ||||
|     /** | ||||
|      * 新密码 | ||||
|      * 新密码(加密) | ||||
|      */ | ||||
|     @Schema(description = "新密码(加密)", example = "E7c72TH+LDxKTwavjM99W1MdI9Lljh79aPKiv3XB9MXcplhm7qJ1BJCj28yaflbdVbfc366klMtjLIWQGqb0qw==") | ||||
|     @NotBlank(message = "新密码不能为空") | ||||
|     private String password; | ||||
| } | ||||
|   | ||||
| @@ -16,8 +16,6 @@ | ||||
|  | ||||
| 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; | ||||
| @@ -27,7 +25,6 @@ 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; | ||||
| @@ -42,7 +39,6 @@ import java.time.LocalDateTime; | ||||
|  * @since 2024/11/26 17:20 | ||||
|  */ | ||||
| @Data | ||||
| @SpelValid | ||||
| @Schema(description = "租户创建或修改请求参数") | ||||
| public class TenantReq implements Serializable { | ||||
|  | ||||
| @@ -52,7 +48,7 @@ public class TenantReq implements Serializable { | ||||
|     /** | ||||
|      * 名称 | ||||
|      */ | ||||
|     @Schema(description = "名称", example = "T0001租户") | ||||
|     @Schema(description = "名称", example = "Xxx租户") | ||||
|     @NotBlank(message = "名称不能为空") | ||||
|     @Length(max = 30, message = "名称长度不能超过 {max} 个字符") | ||||
|     private String name; | ||||
| @@ -60,7 +56,7 @@ public class TenantReq implements Serializable { | ||||
|     /** | ||||
|      * 域名 | ||||
|      */ | ||||
|     @Schema(description = "域名", example = "https://t0001.continew.top/") | ||||
|     @Schema(description = "域名", example = "https://T0sL6RWv0vFh.continew.top/") | ||||
|     @Length(max = 255, message = "域名长度不能超过 {max} 个字符") | ||||
|     private String domain; | ||||
|  | ||||
| @@ -71,17 +67,10 @@ public class TenantReq implements Serializable { | ||||
|     @Future(message = "过期时间必须是未来时间") | ||||
|     private LocalDateTime expireTime; | ||||
|  | ||||
|     /** | ||||
|      * 隔离级别 | ||||
|      */ | ||||
|     @Schema(description = "隔离级别", example = "2") | ||||
|     @NotNull(message = "隔离级别不能为空", groups = CrudValidationGroup.Create.class) | ||||
|     private TenantIsolationLevelEnum isolationLevel; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     @Schema(description = "描述", example = "T0001租户描述") | ||||
|     @Schema(description = "描述", example = "租户描述") | ||||
|     @Length(max = 200, message = "描述长度不能超过 {max} 个字符") | ||||
|     private String description; | ||||
|  | ||||
| @@ -98,13 +87,6 @@ public class TenantReq implements Serializable { | ||||
|     @NotNull(message = "套餐不能为空") | ||||
|     private Long packageId; | ||||
|  | ||||
|     /** | ||||
|      * 数据源 ID | ||||
|      */ | ||||
|     @Schema(description = "数据源 ID") | ||||
|     @SpelNotNull(condition = "#this.isolationLevel == T(top.continew.admin.tenant.model.enums.TenantIsolationLevelEnum).DATASOURCE", message = "数据源不能为空") | ||||
|     private Long datasourceId; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|   | ||||
| @@ -1,37 +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 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; | ||||
| } | ||||
| @@ -1,85 +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 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.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 DatasourceResp extends BaseDetailResp { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 名称 | ||||
|      */ | ||||
|     @Schema(description = "名称", example = "T0001数据源") | ||||
|     @ExcelProperty(value = "名称", order = 2) | ||||
|     private String name; | ||||
|  | ||||
|     /** | ||||
|      * 数据库类型 | ||||
|      */ | ||||
|     @Schema(description = "数据库类型", example = "1") | ||||
|     @ExcelProperty(value = "数据库类型", converter = ExcelBaseEnumConverter.class, order = 3) | ||||
|     private DatasourceDatabaseTypeEnum databaseType; | ||||
|  | ||||
|     /** | ||||
|      * 主机 | ||||
|      */ | ||||
|     @Schema(description = "主机", example = "123.56.195.68") | ||||
|     @ExcelProperty(value = "主机", order = 4) | ||||
|     private String host; | ||||
|  | ||||
|     /** | ||||
|      * 端口 | ||||
|      */ | ||||
|     @Schema(description = "端口", example = "3306") | ||||
|     @ExcelProperty(value = "端口", order = 5) | ||||
|     private Integer port; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @Schema(description = "用户名", example = "root") | ||||
|     @ExcelProperty(value = "用户名", order = 6) | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     @Schema(description = "描述", example = "T0001数据源描述") | ||||
|     @ExcelProperty(value = "描述", order = 7) | ||||
|     private String description; | ||||
| } | ||||
| @@ -40,6 +40,6 @@ public class TenantDetailResp extends TenantResp { | ||||
|      * 租户管理员 | ||||
|      */ | ||||
|     @Schema(description = "租户管理员", example = "666") | ||||
|     @ExcelProperty(value = "租户管理员", order = 13) | ||||
|     @ExcelProperty(value = "租户管理员", order = 11) | ||||
|     private Long adminUser; | ||||
| } | ||||
| @@ -25,8 +25,6 @@ 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.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; | ||||
|  | ||||
| @@ -51,21 +49,21 @@ public class TenantResp extends BaseDetailResp { | ||||
|     /** | ||||
|      * 名称 | ||||
|      */ | ||||
|     @Schema(description = "名称", example = "T0001租户") | ||||
|     @Schema(description = "名称", example = "Xxx租户") | ||||
|     @ExcelProperty(value = "名称", order = 2) | ||||
|     private String name; | ||||
|  | ||||
|     /** | ||||
|      * 编码 | ||||
|      */ | ||||
|     @Schema(description = "编码", example = "T0001") | ||||
|     @Schema(description = "编码", example = "T0sL6RWv0vFh") | ||||
|     @ExcelProperty(value = "编码", order = 3) | ||||
|     private String code; | ||||
|  | ||||
|     /** | ||||
|      * 域名 | ||||
|      */ | ||||
|     @Schema(description = "域名", example = "https://t0001.continew.top/") | ||||
|     @Schema(description = "域名", example = "https://T0sL6RWv0vFh.continew.top/") | ||||
|     @ExcelProperty(value = "域名", order = 4) | ||||
|     private String domain; | ||||
|  | ||||
| @@ -76,17 +74,10 @@ public class TenantResp extends BaseDetailResp { | ||||
|     @ExcelProperty(value = "过期时间", order = 5) | ||||
|     private LocalDateTime expireTime; | ||||
|  | ||||
|     /** | ||||
|      * 隔离级别 | ||||
|      */ | ||||
|     @Schema(description = "隔离级别", example = "2") | ||||
|     @ExcelProperty(value = "隔离级别", converter = ExcelBaseEnumConverter.class, order = 6) | ||||
|     private TenantIsolationLevelEnum isolationLevel; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     @Schema(description = "描述", example = "T0001租户描述") | ||||
|     @Schema(description = "描述", example = "租户描述") | ||||
|     @ExcelProperty(value = "描述", order = 7) | ||||
|     private String description; | ||||
|  | ||||
| @@ -105,25 +96,10 @@ public class TenantResp extends BaseDetailResp { | ||||
|     @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) | ||||
|     @ExcelProperty(value = "套餐名称", order = 10) | ||||
|     private String packageName; | ||||
|  | ||||
|     /** | ||||
|      * 数据源名称 | ||||
|      */ | ||||
|     @Schema(description = "数据源名称", example = "T0001数据源") | ||||
|     @ExcelProperty(value = "数据源名称", order = 12) | ||||
|     private String datasourceName; | ||||
| } | ||||
| @@ -1,50 +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 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); | ||||
| } | ||||
| @@ -40,9 +40,8 @@ public interface TenantService extends BaseService<TenantResp, TenantDetailResp, | ||||
|      * 检查租户状态 | ||||
|      * | ||||
|      * @param id ID | ||||
|      * @return 租户信息 | ||||
|      */ | ||||
|     TenantDO checkStatus(Long id); | ||||
|     void checkStatus(Long id); | ||||
|  | ||||
|     /** | ||||
|      * 获取所有可用的租户列表 | ||||
|   | ||||
| @@ -1,179 +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.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())); | ||||
|     } | ||||
| } | ||||
| @@ -14,7 +14,7 @@ | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package top.continew.admin.tenant.service; | ||||
| package top.continew.admin.tenant.service.impl; | ||||
| 
 | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| @@ -22,6 +22,7 @@ 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 top.continew.admin.tenant.service.PackageMenuService; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| @@ -17,7 +17,7 @@ | ||||
| package top.continew.admin.tenant.service.impl; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import com.baomidou.dynamic.datasource.annotation.DSTransactional; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.springframework.stereotype.Service; | ||||
| import top.continew.admin.common.base.service.BaseServiceImpl; | ||||
| @@ -53,10 +53,8 @@ public class PackageServiceImpl extends BaseServiceImpl<PackageMapper, PackageDO | ||||
|     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); | ||||
|         // 新增信息 | ||||
| @@ -67,7 +65,6 @@ public class PackageServiceImpl extends BaseServiceImpl<PackageMapper, PackageDO | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @DSTransactional(rollbackFor = Exception.class) | ||||
|     public void update(PackageReq req, Long id) { | ||||
|         this.checkNameRepeat(req.getName(), id); | ||||
|         // 更新信息 | ||||
| @@ -95,8 +92,8 @@ public class PackageServiceImpl extends BaseServiceImpl<PackageMapper, PackageDO | ||||
|         deleteMenuIds.removeAll(newMenuIds); | ||||
|         if (CollUtil.isNotEmpty(deleteMenuIds)) { | ||||
|             List<MenuDO> deleteMenus = menuService.listByIds(deleteMenuIds); | ||||
|             tenantIdList.forEach(tenantId -> tenantHandler.execute(tenantId, () -> menuService | ||||
|                 .deleteTenantMenus(deleteMenus))); | ||||
|             tenantIdList.forEach(tenantId -> SpringUtil.getBean(TenantHandler.class) | ||||
|                 .execute(tenantId, () -> menuService.deleteTenantMenus(deleteMenus))); | ||||
|         } | ||||
|         // 如果有新增的菜单则绑定了套餐的租户对应的菜单也会新增 | ||||
|         List<Long> addMenuIds = new ArrayList<>(newMenuIds); | ||||
| @@ -105,8 +102,8 @@ public class PackageServiceImpl extends BaseServiceImpl<PackageMapper, PackageDO | ||||
|             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))); | ||||
|                 tenantIdList.forEach(tenantId -> SpringUtil.getBean(TenantHandler.class) | ||||
|                     .execute(tenantId, () -> menuService.addTenantMenu(addMenu, parentMenu))); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -20,13 +20,11 @@ import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.date.DateUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import com.alicp.jetcache.anno.Cached; | ||||
| import com.baomidou.dynamic.datasource.annotation.DSTransactional; | ||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| 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.TenantProperties; | ||||
| import top.continew.admin.common.enums.DisEnableStatusEnum; | ||||
| import top.continew.admin.tenant.constant.TenantCacheConstants; | ||||
| import top.continew.admin.tenant.constant.TenantConstants; | ||||
| @@ -34,13 +32,11 @@ import top.continew.admin.tenant.handler.TenantDataHandler; | ||||
| 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.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.DatasourceService; | ||||
| import top.continew.admin.tenant.service.PackageMenuService; | ||||
| import top.continew.admin.tenant.service.PackageService; | ||||
| import top.continew.admin.tenant.service.TenantService; | ||||
| @@ -48,6 +44,7 @@ import top.continew.starter.cache.redisson.util.RedisUtils; | ||||
| import top.continew.starter.core.util.validation.CheckUtils; | ||||
| import top.continew.starter.extension.crud.model.entity.BaseIdDO; | ||||
| import top.continew.starter.extension.tenant.TenantHandler; | ||||
| import top.continew.starter.extension.tenant.autoconfigure.TenantProperties; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
| @@ -68,11 +65,9 @@ public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, T | ||||
|     private final IdGeneratorProvider idGeneratorProvider; | ||||
|     private final PackageMenuService packageMenuService; | ||||
|     private final PackageService packageService; | ||||
|     private final DatasourceService datasourceService; | ||||
|     private final TenantDataHandler tenantDataHandler; | ||||
|  | ||||
|     @Override | ||||
|     @DSTransactional(rollbackFor = Exception.class) | ||||
|     public Long create(TenantReq req) { | ||||
|         this.checkNameRepeat(req.getName(), null); | ||||
|         // 检查租户套餐 | ||||
| @@ -82,10 +77,6 @@ public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, T | ||||
|         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); | ||||
| @@ -123,25 +114,24 @@ public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, T | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public TenantDO checkStatus(Long id) { | ||||
|     public void checkStatus(Long id) { | ||||
|         TenantDO tenant = this.getById(id); | ||||
|         if (!tenantProperties.isEnabled() || id.equals(tenantProperties.getSuperTenantId())) { | ||||
|             return tenant; | ||||
|             return; | ||||
|         } | ||||
|         CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE.getValue(), tenant.getStatus(), "此租户已被禁用"); | ||||
|         CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE, 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; | ||||
|         CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE, tenantPackage.getStatus(), "此租户套餐已被禁用"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<TenantAvailableResp> getAvailableList() { | ||||
|         List<TenantDO> tenantList = baseMapper.selectList(Wrappers.lambdaQuery(TenantDO.class) | ||||
|             .select(TenantDO::getName, BaseIdDO::getId, TenantDO::getDomain) | ||||
|             .eq(TenantDO::getStatus, DisEnableStatusEnum.ENABLE.getValue()) | ||||
|             .eq(TenantDO::getStatus, DisEnableStatusEnum.ENABLE) | ||||
|             .and(t -> t.isNull(TenantDO::getExpireTime).or().ge(TenantDO::getExpireTime, DateUtil.date()))); | ||||
|         return BeanUtil.copyToList(tenantList, TenantAvailableResp.class); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user