feat(plugin/tenant): 新增多租户插件模块 (#175)

This commit is contained in:
小熊
2025-07-10 20:38:59 +08:00
committed by GitHub
parent 72493f8161
commit ed6dd65a51
70 changed files with 3539 additions and 65 deletions

View File

@@ -55,4 +55,5 @@ public interface AppService extends BaseService<AppResp, AppDetailResp, AppQuery
* @return 应用信息
*/
AppDO getByAccessKey(String accessKey);
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.admin</groupId>
<artifactId>continew-plugin</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-plugin-tenant</artifactId>
<description>多租户插件</description>
<dependencies>
<dependency>
<groupId>top.continew.admin</groupId>
<artifactId>continew-system</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,211 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.common.config.properties.TenantProperties;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.system.model.entity.MenuDO;
import top.continew.admin.system.model.entity.user.UserDO;
import top.continew.admin.system.model.req.user.UserPasswordResetReq;
import top.continew.admin.system.service.*;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.query.TenantQuery;
import top.continew.admin.tenant.model.req.TenantLoginUserInfoReq;
import top.continew.admin.tenant.model.req.TenantReq;
import top.continew.admin.tenant.model.resp.*;
import top.continew.admin.tenant.service.TenantDbConnectService;
import top.continew.admin.tenant.service.TenantPackageService;
import top.continew.admin.tenant.service.TenantService;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.admin.common.base.model.resp.BaseResp;
import top.continew.starter.extension.crud.model.req.IdsReq;
import top.continew.starter.extension.crud.model.resp.IdResp;
import top.continew.starter.extension.tenant.TenantHandler;
import java.util.List;
/**
* 租户管理 API
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Tag(name = "租户管理 API")
@RestController
@AllArgsConstructor
@CrudRequestMapping(value = "/tenant/user", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
public class TenantController extends BaseController<TenantService, TenantResp, TenantDetailResp, TenantQuery, TenantReq> {
private final TenantProperties tenantProperties;
private final DeptService deptService;
private final MenuService menuService;
private final TenantPackageService packageService;
private final RoleService roleService;
private final UserService userService;
private final TenantSysDataService tenantSysDataService;
private final RoleMenuService roleMenuService;
private final TenantDbConnectService dbConnectService;
@GetMapping("/common")
@SaIgnore
@Operation(summary = "多租户通用信息查询", description = "多租户通用信息查询")
public TenantCommonResp common() {
TenantCommonResp commonResp = new TenantCommonResp();
commonResp.setIsEnabled(tenantProperties.isEnabled());
commonResp.setAvailableList(baseService.getAvailableList());
return commonResp;
}
@Override
@DSTransactional
public IdResp<Long> create(TenantReq req) {
//套餐菜单
TenantPackageDetailResp detailResp = packageService.get(req.getPackageId());
CheckUtils.throwIf(detailResp.getMenuIds().isEmpty(), "该套餐无可用菜单");
List<MenuDO> menuRespList = menuService.listByIds(detailResp.getMenuIds());
//租户添加
IdResp<Long> baseIdResp = super.create(req);
//在租户中执行数据插入
SpringUtil.getBean(TenantHandler.class).execute(baseIdResp.getId(), () -> {
//租户部门初始化
Long deptId = deptService.initTenantDept(req.getName());
//租户菜单初始化
menuService.menuInit(menuRespList, 0L, 0L);
//租户角色初始化
Long roleId = roleService.initTenantRole();
//角色绑定菜单
roleMenuService.add(menuService.listAll(baseIdResp.getId()).stream().map(BaseResp::getId).toList(), roleId);
//管理用户初始化
Long userId = userService.initTenantUser(req.getUsername(), req.getPassword(), deptId);
//用户绑定角色
roleService.assignToUsers(roleId, ListUtil.of(userId));
//租户绑定用户
baseService.bindUser(baseIdResp.getId(), userId);
});
return baseIdResp;
}
@Override
public void delete(Long id) {
SpringUtil.getBean(TenantHandler.class).execute(id, () -> {
//系统数据清除
tenantSysDataService.clear();
});
super.delete(id);
}
@Override
public void batchDelete(@Valid IdsReq ids) {
for (Long id : ids.getIds()) {
//在租户中执行数据清除
SpringUtil.getBean(TenantHandler.class).execute(id, () -> {
//系统数据清除
tenantSysDataService.clear();
});
}
super.batchDelete(ids);
}
/**
* 获取租户管理账号用户名
*/
@GetMapping("/loginUser/{tenantId}")
@Operation(summary = "获取租户管理账号信息", description = "获取租户管理账号信息")
@SaCheckPermission("tenant:user:editLoginUserInfo")
public String loginUserInfo(@PathVariable Long tenantId) {
TenantDO tenantDO = baseService.getTenantById(tenantId);
CheckUtils.throwIfNull(tenantDO, "租户不存在");
StringBuilder username = new StringBuilder();
SpringUtil.getBean(TenantHandler.class).execute(tenantDO.getId(), () -> {
UserDO userDO = userService.getById(tenantDO.getUserId());
CheckUtils.throwIfNull(userDO, "租户管理用户不存在");
username.append(userDO.getUsername());
});
return username.toString();
}
/**
* 租户管理账号信息更新
*/
@PutMapping("/loginUser")
@Operation(summary = "租户管理账号信息更新", description = "租户管理账号信息更新")
@SaCheckPermission("tenant:user:editLoginUserInfo")
@DSTransactional
public void editLoginUserInfo(@Validated @RequestBody TenantLoginUserInfoReq req) {
TenantDO tenantDO = baseService.getTenantById(req.getTenantId());
CheckUtils.throwIfNull(tenantDO, "租户不存在");
SpringUtil.getBean(TenantHandler.class).execute(tenantDO.getId(), () -> {
UserDO userDO = userService.getById(tenantDO.getUserId());
CheckUtils.throwIfNull(userDO, "用户不存在");
//修改用户名
if (!req.getUsername().equals(userDO.getUsername())) {
userService.update(Wrappers.lambdaUpdate(UserDO.class)
.set(UserDO::getUsername, req.getUsername())
.eq(BaseIdDO::getId, userDO.getId()));
}
//修改密码
if (StrUtil.isNotEmpty(req.getPassword())) {
String password = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfNull(password, "新密码解密失败");
UserPasswordResetReq passwordResetReq = new UserPasswordResetReq();
passwordResetReq.setNewPassword(password);
userService.resetPassword(passwordResetReq, userDO.getId());
}
});
}
/**
* 查询所有租户套餐
*/
@GetMapping("/all/package")
@Operation(summary = "查询所有租户套餐", description = "查询所有租户套餐")
@SaCheckPermission(value = {"tenant:user:add", "tenant:user:update"}, mode = SaMode.OR)
public List<TenantPackageResp> packageList() {
return packageService.list(null, null);
}
/**
* 查询所有数据库连接
*/
@GetMapping("/all/dbConnect")
@Operation(summary = "获取租户数据连接列表", description = "获取租户数据连接列表")
@SaCheckPermission(value = {"tenant:user:add", "tenant:user:update"}, mode = SaMode.OR)
public List<TenantDbConnectResp> dbConnectList() {
return dbConnectService.list(null, null);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.tenant.model.query.TenantDbConnectQuery;
import top.continew.admin.tenant.model.req.TenantDbConnectReq;
import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
import top.continew.admin.tenant.model.resp.TenantDbConnectResp;
import top.continew.admin.tenant.service.TenantDbConnectService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
/**
* 租户数据连接管理 API
*
* @author 小熊
* @since 2024/12/12 19:13
*/
@Tag(name = "租户数据连接管理 API")
@RestController
@CrudRequestMapping(value = "/tenant/dbConnect", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
public class TenantDbConnectController extends BaseController<TenantDbConnectService, TenantDbConnectResp, TenantDbConnectDetailResp, TenantDbConnectQuery, TenantDbConnectReq> {}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.common.config.properties.TenantProperties;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.model.entity.MenuDO;
import top.continew.admin.system.model.query.MenuQuery;
import top.continew.admin.system.service.MenuService;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.query.TenantPackageQuery;
import top.continew.admin.tenant.model.req.TenantPackageReq;
import top.continew.admin.tenant.model.resp.TenantPackageDetailResp;
import top.continew.admin.tenant.model.resp.TenantPackageResp;
import top.continew.admin.tenant.service.TenantPackageService;
import top.continew.admin.tenant.service.TenantService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.tenant.TenantHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 租户套餐管理 API
*
* @author 小熊
* @since 2024/11/26 11:25
*/
@Tag(name = "租户套餐管理 API")
@RestController
@AllArgsConstructor
@CrudRequestMapping(value = "/tenant/package", api = {Api.LIST, Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE})
public class TenantPackageController extends BaseController<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);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.mapper;
import top.continew.admin.tenant.model.entity.TenantDbConnectDO;
import top.continew.starter.data.mapper.BaseMapper;
/**
* 租户数据连接 Mapper
*
* @author 小熊
* @since 2024/12/12 19:13
*/
public interface TenantDbConnectMapper extends BaseMapper<TenantDbConnectDO> {}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.resp.TenantResp;
import top.continew.starter.data.mapper.BaseMapper;
/**
* 租户 Mapper
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@DS(SysConstants.DEFAULT_DATASOURCE)
@Mapper
public interface TenantMapper extends BaseMapper<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);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.mapper;
import top.continew.starter.data.mapper.BaseMapper;
import top.continew.admin.tenant.model.entity.TenantPackageDO;
/**
* 租户套餐 Mapper
*
* @author 小熊
* @since 2024/11/26 11:25
*/
public interface TenantPackageMapper extends BaseMapper<TenantPackageDO> {
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import top.continew.admin.common.base.model.entity.BaseDO;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 租户实体
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Data
@TableName("sys_tenant")
public class TenantDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户名称
*/
private String name;
/**
* 绑定的域名
*/
private String domain;
/**
* 租户套餐编号
*/
private Long packageId;
/**
* 状态1启用2禁用
*/
private Integer status;
/**
* 租户过期时间
*/
private LocalDateTime expireTime;
/**
* 用户ID
*/
private Long userId;
/**
* 租户编号
*/
private String tenantSn;
/**
* 隔离级别
*/
private Integer isolationLevel;
/**
* 数据连接ID
*/
private Long dbConnectId;
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import top.continew.admin.common.base.model.entity.BaseDO;
import java.io.Serial;
/**
* 租户数据连接实体
*
* @author 小熊
* @since 2024/12/12 19:13
*/
@Data
@TableName("sys_tenant_db_connect")
public class TenantDbConnectDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 连接名称
*/
private String connectName;
/**
* 连接类型
*/
private Integer type;
/**
* 连接主机地址
*/
private String host;
/**
* 连接端口
*/
private Integer port;
/**
* 连接用户名
*/
private String username;
/**
* 连接密码
*/
private String password;
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.entity;
import java.io.Serial;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.continew.admin.common.base.model.entity.BaseDO;
/**
* 租户套餐实体
*
* @author 小熊
* @since 2024/11/26 11:25
*/
@Data
@TableName("sys_tenant_package")
public class TenantPackageDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 套餐名称
*/
private String name;
/**
* 关联的菜单ids
*/
private String menuIds;
/**
* 菜单选择是否父子节点关联
*/
private Boolean menuCheckStrictly;
/**
* 状态1启用2禁用
*/
private Integer status;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import top.continew.starter.core.exception.BusinessException;
@Getter
@AllArgsConstructor
public enum TenantConnectTypeEnum {
MYSQL;
public static TenantConnectTypeEnum getByOrdinal(Integer ordinal) {
for (TenantConnectTypeEnum item : TenantConnectTypeEnum.values()) {
if (item.ordinal() == ordinal) {
return item;
}
}
throw new BusinessException("未知的连接类型");
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.query;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.annotation.Query;
import top.continew.starter.data.enums.QueryType;
/**
* 租户数据连接查询条件
*
* @author 小熊
* @since 2024/12/12 19:13
*/
@Data
@Schema(description = "租户数据连接查询条件")
public class TenantDbConnectQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 连接名称
*/
@Schema(description = "连接名称")
@Query(type = QueryType.EQ)
private String connectName;
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.query;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.annotation.Query;
import top.continew.starter.data.enums.QueryType;
/**
* 租户套餐查询条件
*
* @author 小熊
* @since 2024/11/26 11:25
*/
@Data
@Schema(description = "租户套餐查询条件")
public class TenantPackageQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 套餐名称
*/
@Schema(description = "套餐名称")
@Query(type = QueryType.EQ)
private String name;
/**
* 状态1启用2禁用
*/
@Schema(description = "状态1启用2禁用")
@Query(type = QueryType.EQ)
private Integer status;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.query;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.starter.data.annotation.Query;
import top.continew.starter.data.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
/**
* 租户查询条件
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Data
@Schema(description = "租户查询条件")
public class TenantQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户名称
*/
@Schema(description = "租户名称")
@Query(type = QueryType.LIKE)
private String name;
/**
* 租户套餐编号
*/
@Schema(description = "租户套餐编号")
@Query(type = QueryType.EQ)
private Long packageId;
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
/**
* 创建或修改租户数据连接参数
*
* @author 小熊
* @since 2024/12/12 19:13
*/
@Data
@Schema(description = "创建或修改租户数据连接参数")
public class TenantDbConnectReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 连接名称
*/
@Schema(description = "连接名称")
@NotBlank(message = "连接名称不能为空")
@Length(max = 128, message = "连接名称长度不能超过 {max} 个字符")
private String connectName;
/**
* 连接类型
*/
@Schema(description = "连接类型")
@NotNull(message = "连接类型不能为空")
private Integer type;
/**
* 连接主机地址
*/
@Schema(description = "连接主机地址")
@NotBlank(message = "连接主机地址不能为空")
@Length(max = 128, message = "连接主机地址长度不能超过 {max} 个字符")
private String host;
/**
* 连接端口
*/
@Schema(description = "连接端口")
@NotNull(message = "连接端口不能为空")
private Integer port;
/**
* 连接用户名
*/
@Schema(description = "连接用户名")
@NotBlank(message = "连接用户名不能为空")
@Length(max = 128, message = "连接用户名长度不能超过 {max} 个字符")
private String username;
/**
* 连接密码
*/
@Schema(description = "连接密码")
@NotBlank(message = "连接密码不能为空")
@Length(max = 128, message = "连接密码长度不能超过 {max} 个字符")
private String password;
/**
* ID
*/
@Schema(hidden = true)
private Long id;
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
/**
* @description: 租户登录用户信息
* @author: 小熊
* @create: 2024-12-02 20:41
*/
@Data
public class TenantLoginUserInfoReq implements Serializable {
/**
* 租户id
*/
@NotNull(message = "租户ID不能为空")
private Long tenantId;
/**
* 登录用户名
*/
@NotEmpty(message = "登录用户名不能为空")
private String username;
/**
* 登录密码
*/
private String password;
/**
* ID
*/
@Schema(hidden = true)
private Long id;
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.req;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
/**
* 创建或修改租户套餐参数
*
* @author 小熊
* @since 2024/11/26 11:25
*/
@Data
@Schema(description = "创建或修改租户套餐参数")
public class TenantPackageReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 套餐名称
*/
@Schema(description = "套餐名称")
@NotBlank(message = "套餐名称不能为空")
@Length(max = 64, message = "套餐名称长度不能超过 {max} 个字符")
private String name;
/**
* 关联的菜单ids
*/
@Schema(description = "关联的菜单ids")
private Long[] menuIds;
/**
* 菜单选择是否父子节点关联
*/
@Schema(description = "菜单选择是否父子节点关联")
private Boolean menuCheckStrictly;
/**
* 状态
*/
@Schema(description = "状态")
@NotNull(message = "状态不能为空")
private Integer status;
/**
* ID
*/
@Schema(hidden = true)
private Long id;
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 创建或修改租户参数
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Data
@Schema(description = "创建或修改租户参数")
public class TenantReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户名称
*/
@Schema(description = "租户名称")
@NotBlank(message = "租户名称不能为空")
@Length(max = 64, message = "租户名称长度不能超过 {max} 个字符")
private String name;
/**
* 绑定的域名
*/
@Schema(description = "绑定的域名")
@Length(max = 128, message = "绑定的域名长度不能超过 {max} 个字符")
private String domain;
/**
* 租户套餐编号
*/
@Schema(description = "租户套餐编号")
@NotNull(message = "租户套餐编号不能为空")
private Long packageId;
/**
* 状态1启用2禁用
*/
@Schema(description = "状态")
@NotNull(message = "状态不能为空")
private Integer status;
/**
* 租户过期时间
*/
@Schema(description = "租户过期时间")
@Future(message = "过期时间必须是未来时间")
private LocalDateTime expireTime;
/**
* 用户名
*/
@Schema(description = "用户名", example = "zhangsan")
@NotBlank(message = "用户名不能为空", groups = CrudValidationGroup.Create.class)
@Pattern(regexp = RegexConstants.USERNAME, message = "用户名长度为 4-64 个字符,支持大小写字母、数字、下划线,以字母开头")
private String username;
/**
* 密码(加密)
*/
@Schema(description = "密码(加密)", example = "E7c72TH+LDxKTwavjM99W1MdI9Lljh79aPKiv3XB9MXcplhm7qJ1BJCj28yaflbdVbfc366klMtjLIWQGqb0qw==")
@NotBlank(message = "密码不能为空", groups = CrudValidationGroup.Create.class)
private String password;
/**
* 租户编号
*/
private String tenantSn;
/**
* 隔离级别
*/
@Schema(description = "隔离级别")
@NotNull(message = "隔离级别不能为空", groups = CrudValidationGroup.Create.class)
private Integer isolationLevel;
/**
* 数据连接ID
*/
@Schema(description = "数据连接ID")
private Long dbConnectId;
/**
* ID
*/
@Schema(hidden = true)
private Long id;
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import lombok.Data;
@Data
public class TenantAvailableResp {
private Long id;
private String name;
private String domain;
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import lombok.Data;
import java.util.List;
/**
* @description: 租户通用信息返回
* @author: 小熊
* @create: 2024-11-28 09:53
*/
@Data
public class TenantCommonResp {
/**
* 是否开启了多租户
*/
private Boolean isEnabled;
/**
* 可用租户列表
*/
private List<TenantAvailableResp> availableList;
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.common.base.model.resp.BaseDetailResp;
import java.io.Serial;
/**
* 租户数据连接详情信息
*
* @author 小熊
* @since 2024/12/12 19:13
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "租户数据连接详情信息")
public class TenantDbConnectDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 连接名称
*/
@Schema(description = "连接名称")
@ExcelProperty(value = "连接名称")
private String connectName;
/**
* 连接类型
*/
@Schema(description = "连接类型")
@ExcelProperty(value = "连接类型")
private Integer type;
/**
* 连接主机地址
*/
@Schema(description = "连接主机地址")
@ExcelProperty(value = "连接主机地址")
private String host;
/**
* 连接端口
*/
@Schema(description = "连接端口")
@ExcelProperty(value = "连接端口")
private Integer port;
/**
* 连接用户名
*/
@Schema(description = "连接用户名")
@ExcelProperty(value = "连接用户名")
private String username;
/**
* 连接密码
*/
@Schema(description = "连接密码")
@ExcelProperty(value = "连接密码")
private String password;
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.common.base.model.resp.BaseResp;
import java.io.Serial;
/**
* 租户数据连接信息
*
* @author 小熊
* @since 2024/12/12 19:13
*/
@Data
@Schema(description = "租户数据连接信息")
public class TenantDbConnectResp extends BaseResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 连接名称
*/
@Schema(description = "连接名称")
private String connectName;
/**
* 连接类型
*/
@Schema(description = "连接类型")
private Integer type;
/**
* 连接主机地址
*/
@Schema(description = "连接主机地址")
private String host;
/**
* 连接端口
*/
@Schema(description = "连接端口")
private Integer port;
/**
* 连接用户名
*/
@Schema(description = "连接用户名")
private String username;
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.common.base.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.LocalDateTime;
import java.util.List;
/**
* 租户详情信息
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "租户详情信息")
public class TenantDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户名称
*/
@Schema(description = "租户名称")
@ExcelProperty(value = "租户名称")
private String name;
/**
* 绑定的域名
*/
@Schema(description = "绑定的域名")
@ExcelProperty(value = "绑定的域名")
private String domain;
/**
* 租户套餐编号
*/
@Schema(description = "租户套餐编号")
@ExcelProperty(value = "租户套餐编号")
private Long packageId;
/**
* 状态1启用2禁用
*/
@Schema(description = "状态1启用2禁用")
@ExcelProperty(value = "状态1启用2禁用")
private Integer status;
/**
* 租户过期时间
*/
@Schema(description = "租户过期时间")
@ExcelProperty(value = "租户过期时间")
private LocalDateTime expireTime;
/**
* 绑定的套餐名称
*/
@Schema(description = "绑定的套餐名称")
private String packageName;
/**
* 套餐关联的菜单
*/
@Schema(description = "关联的菜单ids")
private List<Long> menuIds;
/**
* 租户编号
*/
private String tenantSn;
/**
* 租户绑定的管理用户id
*/
private Long userId;
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import java.io.Serial;
import java.time.*;
import java.util.List;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.admin.common.base.model.resp.BaseDetailResp;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
/**
* 租户套餐详情信息
*
* @author 小熊
* @since 2024/11/26 11:25
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "租户套餐详情信息")
public class TenantPackageDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 套餐名称
*/
@Schema(description = "套餐名称")
@ExcelProperty(value = "套餐名称")
private String name;
/**
* 关联的菜单ids
*/
@Schema(description = "关联的菜单ids")
@ExcelProperty(value = "关联的菜单ids")
private List<Long> menuIds;
/**
* 菜单选择是否父子节点关联
*/
@Schema(description = "菜单选择是否父子节点关联")
@ExcelProperty(value = "菜单选择是否父子节点关联")
private Boolean menuCheckStrictly;
/**
* 状态1启用2禁用
*/
@Schema(description = "状态1启用2禁用")
@ExcelProperty(value = "状态1启用2禁用")
private Integer status;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import java.io.Serial;
import java.time.*;
import java.util.List;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.admin.common.base.model.resp.BaseResp;
/**
* 租户套餐信息
*
* @author 小熊
* @since 2024/11/26 11:25
*/
@Data
@Schema(description = "租户套餐信息")
public class TenantPackageResp extends BaseResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 套餐名称
*/
@Schema(description = "套餐名称")
private String name;
/**
* 关联的菜单ids
*/
@Schema(description = "关联的菜单ids")
private List<Long> menuIds;
/**
* 菜单选择是否父子节点关联
*/
@Schema(description = "菜单选择是否父子节点关联")
private Boolean menuCheckStrictly;
/**
* 状态1启用2禁用
*/
@Schema(description = "状态1启用2禁用")
private Integer status;
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.common.base.model.resp.BaseResp;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 租户信息
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Data
@Schema(description = "租户信息")
public class TenantResp extends BaseResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户名称
*/
@Schema(description = "租户名称")
private String name;
/**
* 绑定的域名
*/
@Schema(description = "绑定的域名")
private String domain;
/**
* 租户套餐编号
*/
@Schema(description = "租户套餐编号")
private Long packageId;
/**
* 状态1启用2禁用
*/
@Schema(description = "状态1启用2禁用")
private Integer status;
/**
* 租户过期时间
*/
@Schema(description = "租户过期时间")
private LocalDateTime expireTime;
/**
* 绑定的套餐名称
*/
@Schema(description = "绑定的套餐名称")
private String packageName;
/**
* 租户编号
*/
private String tenantSn;
/**
* 隔离级别
*/
@Schema(description = "隔离级别")
private Integer isolationLevel;
/**
* 数据连接ID
*/
@Schema(description = "数据连接ID")
private Long dbConnectId;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.service;
import org.springframework.jdbc.core.JdbcTemplate;
import top.continew.admin.common.base.service.BaseService;
import top.continew.admin.tenant.model.query.TenantDbConnectQuery;
import top.continew.admin.tenant.model.req.TenantDbConnectReq;
import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
import top.continew.admin.tenant.model.resp.TenantDbConnectResp;
/**
* 租户数据连接业务接口
*
* @author 小熊
* @since 2024/12/12 19:13
*/
public interface TenantDbConnectService extends BaseService<TenantDbConnectResp, TenantDbConnectDetailResp, TenantDbConnectQuery, TenantDbConnectReq> {
JdbcTemplate getConnectJdbcTemplateById(Long id);
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.service;
import top.continew.admin.common.base.service.BaseService;
import top.continew.admin.tenant.model.query.TenantPackageQuery;
import top.continew.admin.tenant.model.req.TenantPackageReq;
import top.continew.admin.tenant.model.resp.TenantPackageDetailResp;
import top.continew.admin.tenant.model.resp.TenantPackageResp;
/**
* 租户套餐业务接口
*
* @author 小熊
* @since 2024/11/26 11:25
*/
public interface TenantPackageService extends BaseService<TenantPackageResp, TenantPackageDetailResp, TenantPackageQuery, TenantPackageReq> {}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.service;
import top.continew.admin.common.base.service.BaseService;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.query.TenantQuery;
import top.continew.admin.tenant.model.req.TenantReq;
import top.continew.admin.tenant.model.resp.TenantAvailableResp;
import top.continew.admin.tenant.model.resp.TenantDetailResp;
import top.continew.admin.tenant.model.resp.TenantResp;
import top.continew.starter.data.service.IService;
import java.util.List;
/**
* 租户业务接口
*
* @author 小熊
* @since 2024/11/26 17:20
*/
public interface TenantService extends BaseService<TenantResp, TenantDetailResp, TenantQuery, TenantReq>, IService<TenantDO> {
/**
* 获取所有可用的租户列表
*/
List<TenantAvailableResp> getAvailableList();
/**
* 租户绑定用户
*/
void bindUser(Long tenantId, Long userId);
/**
* 检查租户状态
*/
void checkStatus();
/**
* 根据id获取租户DO
*/
TenantDO getTenantById(Long id);
/**
* 根据用户id获取租户信息
*/
TenantDO getTenantByUserId(Long userId);
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.service.impl;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import top.continew.admin.common.base.service.BaseServiceImpl;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.tenant.mapper.TenantDbConnectMapper;
import top.continew.admin.tenant.mapper.TenantMapper;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.entity.TenantDbConnectDO;
import top.continew.admin.tenant.model.enums.TenantConnectTypeEnum;
import top.continew.admin.tenant.model.query.TenantDbConnectQuery;
import top.continew.admin.tenant.model.req.TenantDbConnectReq;
import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
import top.continew.admin.tenant.model.resp.TenantDbConnectResp;
import top.continew.admin.tenant.service.TenantDbConnectService;
import top.continew.admin.tenant.util.DbConnectUtil;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import javax.sql.DataSource;
import java.util.List;
/**
* 租户数据连接业务实现
*
* @author 小熊
* @since 2024/12/12 19:13
*/
@Service
@RequiredArgsConstructor
public class TenantDbConnectServiceImpl extends BaseServiceImpl<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;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.admin.common.base.service.BaseServiceImpl;
import top.continew.admin.tenant.mapper.TenantMapper;
import top.continew.admin.tenant.mapper.TenantPackageMapper;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.entity.TenantPackageDO;
import top.continew.admin.tenant.model.query.TenantPackageQuery;
import top.continew.admin.tenant.model.req.TenantPackageReq;
import top.continew.admin.tenant.model.resp.TenantPackageDetailResp;
import top.continew.admin.tenant.model.resp.TenantPackageResp;
import top.continew.admin.tenant.service.TenantPackageService;
import top.continew.starter.core.util.validation.CheckUtils;
import java.util.List;
/**
* 租户套餐业务实现
*
* @author 小熊
* @since 2024/11/26 11:25
*/
@Service
@RequiredArgsConstructor
public class TenantPackageServiceImpl extends BaseServiceImpl<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, "存在关联租户无法删除");
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.service.impl;
import cn.hutool.core.util.StrUtil;
import com.zaxxer.hikari.HikariConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.resp.TenantDbConnectDetailResp;
import top.continew.admin.tenant.service.TenantDbConnectService;
import top.continew.admin.tenant.service.TenantService;
import top.continew.admin.tenant.util.DbConnectUtil;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.extension.tenant.config.TenantDataSource;
import top.continew.starter.extension.tenant.config.TenantProvider;
import top.continew.starter.extension.tenant.context.TenantContext;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
/**
* @description: 租户数据源提供者实现
* @author: 小熊
* @create: 2024-12-12 15:35
*/
@Service
@RequiredArgsConstructor
public class TenantProviderImpl implements TenantProvider {
private final TenantService tenantService;
private final TenantDbConnectService tenantDbConnectService;
@Override
public TenantContext getByTenantId(String tenantId, boolean verify) {
TenantContext context = new TenantContext();
if (StrUtil.isNotEmpty(tenantId) && !SysConstants.DEFAULT_TENANT.equals(tenantId)) {
Long longTenantId = Long.valueOf(tenantId);
TenantDO tenantDO = tenantService.getTenantById(longTenantId);
CheckUtils.throwIfNull(tenantDO, "租户[{}]不存在", tenantId);
CheckUtils.throwIf(verify && DisEnableStatusEnum.DISABLE.getValue()
.equals(tenantDO.getStatus()), "租户[{}]已被禁用", tenantId);
context.setTenantId(longTenantId);
TenantIsolationLevel isolationLevel = TenantIsolationLevel.DATASOURCE.ordinal() == tenantDO
.getIsolationLevel() ? TenantIsolationLevel.DATASOURCE : TenantIsolationLevel.LINE;
context.setIsolationLevel(isolationLevel);
if (isolationLevel.equals(TenantIsolationLevel.DATASOURCE)) {
TenantDbConnectDetailResp dbConnectReq = tenantDbConnectService.get(tenantDO.getDbConnectId());
String dbName = SysConstants.TENANT_DB_PREFIX + tenantDO.getTenantSn();
HikariConfig hikariConfig = DbConnectUtil.formatHikariConfig(dbConnectReq.getHost(), dbConnectReq
.getPort(), dbConnectReq.getUsername(), dbConnectReq.getPassword(), dbName, DbConnectUtil
.getDefaultMysqlConnectParameter());
TenantDataSource source = new TenantDataSource();
source.setPoolName(tenantId);
source.setDriverClassName(hikariConfig.getDriverClassName());
source.setUrl(hikariConfig.getJdbcUrl());
source.setUsername(hikariConfig.getUsername());
source.setPassword(hikariConfig.getPassword());
context.setDataSource(source);
}
} else {
context.setTenantId(Long.valueOf(SysConstants.DEFAULT_TENANT));
context.setIsolationLevel(TenantIsolationLevel.LINE);
}
return context;
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import top.continew.admin.common.base.service.BaseServiceImpl;
import top.continew.admin.common.config.properties.TenantProperties;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.constant.SysConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.tenant.mapper.TenantMapper;
import top.continew.admin.tenant.mapper.TenantPackageMapper;
import top.continew.admin.tenant.model.entity.TenantDO;
import top.continew.admin.tenant.model.entity.TenantPackageDO;
import top.continew.admin.tenant.model.query.TenantQuery;
import top.continew.admin.tenant.model.req.TenantReq;
import top.continew.admin.tenant.model.resp.TenantAvailableResp;
import top.continew.admin.tenant.model.resp.TenantDetailResp;
import top.continew.admin.tenant.model.resp.TenantResp;
import top.continew.admin.tenant.service.TenantDbConnectService;
import top.continew.admin.tenant.service.TenantService;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
import java.util.Arrays;
import java.util.List;
/**
* 租户业务实现
*
* @author 小熊
* @since 2024/11/26 17:20
*/
@Service
@RequiredArgsConstructor
public class TenantServiceImpl extends BaseServiceImpl<TenantMapper, TenantDO, TenantResp, TenantDetailResp, TenantQuery, TenantReq> implements TenantService {
private final TenantPackageMapper packageMapper;
private final TenantProperties tenantProperties;
private final TenantDbConnectService dbConnectService;
@Override
protected void beforeCreate(TenantReq req) {
//租户名称不能重复
ValidationUtils.throwIf(baseMapper.exists(Wrappers.lambdaQuery(TenantDO.class)
.eq(TenantDO::getName, req.getName())), "重复的租户名称");
//录入随机的六位租户编号
req.setTenantSn(generateTenantSn());
}
/**
* 生成六位随机不重复的编号
*/
private String generateTenantSn() {
String tenantSn;
do {
tenantSn = RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER_LOWER, 6);
} while (baseMapper.exists(Wrappers.lambdaQuery(TenantDO.class).eq(TenantDO::getTenantSn, tenantSn)));
return tenantSn;
}
@Override
protected void afterCreate(TenantReq req, TenantDO entity) {
//数据源级别的租户需要创建数据库
if (entity.getIsolationLevel().equals(TenantIsolationLevel.DATASOURCE.ordinal())) {
JdbcTemplate jdbcTemplate = dbConnectService.getConnectJdbcTemplateById(entity.getDbConnectId());
String dbName = SysConstants.TENANT_DB_PREFIX + entity.getTenantSn();
//建库
jdbcTemplate.execute(StrUtil
.format("CREATE DATABASE {} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;", dbName));
jdbcTemplate.execute(StrUtil.format("USE {};", dbName));
//建表
Resource resource = new ClassPathResource("db/changelog/mysql/tenant_table.sql");
String tableSql = resource.readUtf8Str();
Arrays.stream(tableSql.split(";"))
.map(String::trim)
.filter(sql -> !sql.isEmpty())
.forEach(jdbcTemplate::execute);
}
}
@Override
public List<TenantAvailableResp> getAvailableList() {
List<TenantDO> tenantDOS = baseMapper.selectList(Wrappers.lambdaQuery(TenantDO.class)
.select(TenantDO::getName, BaseIdDO::getId, TenantDO::getDomain)
.eq(TenantDO::getStatus, DisEnableStatusEnum.ENABLE.getValue())
.and(t -> t.isNull(TenantDO::getExpireTime).or().ge(TenantDO::getExpireTime, DateUtil.date())));
return BeanUtil.copyToList(tenantDOS, TenantAvailableResp.class);
}
@Override
public PageResp<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;
}
@Override
public TenantDetailResp get(Long id) {
TenantDetailResp detailResp = new TenantDetailResp();
TenantDO tenantDO = getById(id);
if (tenantDO != null) {
BeanUtil.copyProperties(tenantDO, detailResp);
TenantPackageDO packageDO = packageMapper.selectById(tenantDO.getPackageId());
if (packageDO != null) {
detailResp.setPackageName(packageDO.getName());
detailResp.setMenuIds(new JSONArray(packageDO.getMenuIds()).toList(Long.class));
}
}
fill(detailResp);
return detailResp;
}
@Override
public void bindUser(Long tenantId, Long userId) {
update(Wrappers.lambdaUpdate(TenantDO.class).set(TenantDO::getUserId, userId).eq(BaseIdDO::getId, tenantId));
TenantDO entity = getById(tenantId);
RedisUtils.set(CacheConstants.TENANT_KEY + tenantId, entity);
}
@Override
public void checkStatus() {
if (tenantProperties.isEnabled()) {
Long tenantId = TenantContextHolder.getTenantId();
CheckUtils.throwIfNull(tenantId, "未选择租户");
if (tenantId != 0) {
TenantDO tenantDO = baseMapper.selectById(tenantId);
CheckUtils.throwIfNull(tenantDO, "租户不存在");
CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE.getValue(), tenantDO.getStatus(), "此租户已被禁用");
//租户过期
CheckUtils.throwIf(tenantDO.getExpireTime() != null && tenantDO.getExpireTime()
.isBefore(DateUtil.date().toLocalDateTime()), "租户已过期");
//套餐状态
TenantPackageDO packageDO = packageMapper.selectById(tenantDO.getPackageId());
CheckUtils.throwIfNull(tenantDO, "套餐不存在");
CheckUtils.throwIfNotEqual(DisEnableStatusEnum.ENABLE.getValue(), packageDO.getStatus(), "此租户套餐已被禁用");
}
}
}
@Override
@Cached(name = CacheConstants.TENANT_KEY, key = "#id")
public TenantDO getTenantById(Long id) {
return baseMapper.selectById(id);
}
@Override
protected void afterUpdate(TenantReq req, TenantDO entity) {
RedisUtils.set(CacheConstants.TENANT_KEY + entity.getId(), entity);
}
@Override
protected void afterDelete(List<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));
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.tenant.util;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import top.continew.starter.core.exception.BusinessException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 数据连接工具类
* @author: 小熊
* @create: 2024-12-15 18:54
*/
public class DbConnectUtil {
/**
* 格式化HikariConfig
*/
public static HikariConfig formatHikariConfig(String host,
Integer port,
String username,
String password,
String dbName,
Map<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;
}
}

View File

@@ -19,6 +19,7 @@
<module>continew-plugin-schedule</module>
<module>continew-plugin-open</module>
<module>continew-plugin-generator</module>
<module>continew-plugin-tenant</module>
</modules>
<dependencies>