feat:【system 系统管理】租户支持匹配多域名、微信小程序 appid 等

This commit is contained in:
YunaiV
2025-08-19 22:58:44 +08:00
parent 97a981c294
commit df2ef43f85
17 changed files with 138 additions and 77 deletions

View File

@@ -1,14 +1,15 @@
package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 租户 Response VO")
@Data
@@ -36,8 +37,8 @@ public class TenantRespVO {
@DictFormat(DictTypeConstants.COMMON_STATUS)
private Integer status;
@Schema(description = "绑定域名", example = "https://www.iocoder.cn")
private String website;
@Schema(description = "绑定域名数组", example = "https://www.iocoder.cn")
private List<String> websites;
@Schema(description = "租户套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long packageId;

View File

@@ -3,14 +3,15 @@ package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 租户创建/修改 Request VO")
@Data
@@ -34,8 +35,8 @@ public class TenantSaveReqVO {
@NotNull(message = "租户状态")
private Integer status;
@Schema(description = "绑定域名", example = "https://www.iocoder.cn")
private String website;
@Schema(description = "绑定域名数组", example = "https://www.iocoder.cn")
private List<String> websites;
@Schema(description = "租户套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "租户套餐编号不能为空")

View File

@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.system.controller.app.tenant;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.controller.app.tenant.vo.AppTenantRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "用户 App - 租户")
@RestController
@RequestMapping("/system/tenant")
public class AppTenantController {
@Resource
private TenantService tenantService;
@GetMapping("/get-by-website")
@PermitAll
@TenantIgnore
@Operation(summary = "使用域名,获得租户信息", description = "根据用户的域名,获得租户信息")
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
public CommonResult<AppTenantRespVO> getTenantByWebsite(@RequestParam("website") String website) {
TenantDO tenant = tenantService.getTenantByWebsite(website);
if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) {
return success(null);
}
return success(BeanUtils.toBean(tenant, AppTenantRespVO.class));
}
}

View File

@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.system.controller.app.tenant.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - 租户 Response VO")
@Data
public class AppTenantRespVO {
@Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "租户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
private String name;
}

View File

@@ -2,13 +2,16 @@ package cn.iocoder.yudao.module.system.dal.dataobject.tenant;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 租户 DO
@@ -60,9 +63,13 @@ public class TenantDO extends BaseDO {
*/
private Integer status;
/**
* 绑定域名
* 绑定域名列表
*
* 1. 考虑到对微信小程序的兼容,也允许传递 appid
* 2. 为什么是数组,考虑到管理后台、会员前台都有独立的域名,又或者多个管理后台
*/
private String website;
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> websites;
/**
* 租户套餐编号
*

View File

@@ -3,17 +3,13 @@ package cn.iocoder.yudao.module.system.dal.mysql.tenant;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 租户 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface TenantMapper extends BaseMapperX<TenantDO> {
@@ -31,8 +27,9 @@ public interface TenantMapper extends BaseMapperX<TenantDO> {
return selectOne(TenantDO::getName, name);
}
default TenantDO selectByWebsite(String website) {
return selectOne(TenantDO::getWebsite, website);
default List<TenantDO> selectListByWebsite(String website) {
return selectList(new LambdaQueryWrapperX<TenantDO>()
.apply(MyBatisUtils.findInSet("websites", website)));
}
default Long selectCountByPackageId(Long packageId) {

View File

@@ -9,11 +9,6 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 租户套餐 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface TenantPackageMapper extends BaseMapperX<TenantPackageDO> {

View File

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.service.tenant;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -102,7 +101,7 @@ public class TenantServiceImpl implements TenantService {
// 校验租户名称是否重复
validTenantNameDuplicate(createReqVO.getName(), null);
// 校验租户域名是否重复
validTenantWebsiteDuplicate(createReqVO.getWebsite(), null);
validTenantWebsiteDuplicate(createReqVO.getWebsites(), null);
// 校验套餐被禁用
TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());
@@ -148,7 +147,7 @@ public class TenantServiceImpl implements TenantService {
// 校验租户名称是否重复
validTenantNameDuplicate(updateReqVO.getName(), updateReqVO.getId());
// 校验租户域名是否重复
validTenantWebsiteDuplicate(updateReqVO.getWebsite(), updateReqVO.getId());
validTenantWebsiteDuplicate(updateReqVO.getWebsites(), updateReqVO.getId());
// 校验套餐被禁用
TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId());
@@ -175,21 +174,19 @@ public class TenantServiceImpl implements TenantService {
}
}
private void validTenantWebsiteDuplicate(String website, Long id) {
if (StrUtil.isEmpty(website)) {
private void validTenantWebsiteDuplicate(List<String> websites, Long excludeId) {
if (CollUtil.isEmpty(websites)) {
return;
}
TenantDO tenant = tenantMapper.selectByWebsite(website);
if (tenant == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同名字的租户
if (id == null) {
throw exception(TENANT_WEBSITE_DUPLICATE, website);
}
if (!tenant.getId().equals(id)) {
throw exception(TENANT_WEBSITE_DUPLICATE, website);
}
websites.forEach(website -> {
List<TenantDO> tenants = tenantMapper.selectListByWebsite(website);
if (excludeId != null) {
tenants.removeIf(tenant -> tenant.getId().equals(excludeId));
}
if (CollUtil.isNotEmpty(tenants)) {
throw exception(TENANT_WEBSITE_DUPLICATE, website);
}
});
}
@Override
@@ -263,7 +260,8 @@ public class TenantServiceImpl implements TenantService {
@Override
public TenantDO getTenantByWebsite(String website) {
return tenantMapper.selectByWebsite(website);
List<TenantDO> tenants = tenantMapper.selectListByWebsite(website);
return CollUtil.getFirst(tenants);
}
@Override