feat: 搭建后端多租户名片服务

- 初始化 Spring Boot 多模块工程与通用基础能力

- 增加租户、组织、用户、名片、文件、统计等业务模块

- 补充数据库迁移脚本与基础测试
This commit is contained in:
2026-03-20 12:43:21 +08:00
parent 1a2a078c0f
commit 9ef50288e9
95 changed files with 6722 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.easycard</groupId>
<artifactId>easycard-parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>easycard-module-tenant</artifactId>
<name>easycard-module-tenant</name>
<dependencies>
<dependency>
<groupId>com.easycard</groupId>
<artifactId>easycard-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.easycard</groupId>
<artifactId>easycard-module-user</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.easycard</groupId>
<artifactId>easycard-module-card</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.easycard</groupId>
<artifactId>easycard-module-file</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.easycard</groupId>
<artifactId>easycard-module-stat</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,37 @@
package com.easycard.module.tenant.controller;
import com.easycard.common.api.ApiResponse;
import com.easycard.module.tenant.service.PlatformTenantService;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/v1/platform/miniapps")
public class PlatformMiniappController {
private final PlatformTenantService platformTenantService;
public PlatformMiniappController(PlatformTenantService platformTenantService) {
this.platformTenantService = platformTenantService;
}
@GetMapping
@PreAuthorize("hasAuthority('PLATFORM_SUPER_ADMIN')")
public ApiResponse<List<PlatformTenantService.MiniappConfigView>> listMiniapps() {
return ApiResponse.success(platformTenantService.listMiniappConfigs());
}
@PutMapping("/{tenantId}")
@PreAuthorize("hasAuthority('PLATFORM_SUPER_ADMIN')")
public ApiResponse<PlatformTenantService.MiniappConfigView> updateMiniapp(@PathVariable Long tenantId, @Valid @RequestBody MiniappConfigRequest request) {
return ApiResponse.success(platformTenantService.updateMiniappConfig(tenantId, request.toCommand()));
}
}

View File

@@ -0,0 +1,140 @@
package com.easycard.module.tenant.controller;
import com.easycard.common.api.ApiResponse;
import com.easycard.common.exception.BusinessException;
import com.easycard.module.tenant.service.PlatformTenantService;
import com.easycard.module.tenant.vo.TenantDetailResp;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/v1/platform/tenants")
public class PlatformTenantController {
private final PlatformTenantService platformTenantService;
public PlatformTenantController(PlatformTenantService platformTenantService) {
this.platformTenantService = platformTenantService;
}
@GetMapping
@PreAuthorize("hasAuthority('PLATFORM_SUPER_ADMIN')")
public ApiResponse<List<TenantDetailResp>> listTenants() {
return ApiResponse.success(platformTenantService.listTenants());
}
@GetMapping("/{tenantId}")
@PreAuthorize("hasAuthority('PLATFORM_SUPER_ADMIN')")
public ApiResponse<TenantDetailResp> getTenant(@PathVariable Long tenantId) {
TenantDetailResp tenant = platformTenantService.getTenantById(tenantId)
.orElseThrow(() -> new BusinessException("TENANT_NOT_FOUND", "租户不存在"));
return ApiResponse.success(tenant);
}
@PostMapping
@PreAuthorize("hasAuthority('PLATFORM_SUPER_ADMIN')")
public ApiResponse<TenantDetailResp> createTenant(@Valid @RequestBody CreateTenantRequest request) {
return ApiResponse.success(platformTenantService.createTenant(request.toCommand()));
}
@PutMapping("/{tenantId}")
@PreAuthorize("hasAuthority('PLATFORM_SUPER_ADMIN')")
public ApiResponse<TenantDetailResp> updateTenant(@PathVariable Long tenantId, @Valid @RequestBody UpdateTenantRequest request) {
return ApiResponse.success(platformTenantService.updateTenant(tenantId, request.toCommand()));
}
@DeleteMapping("/{tenantId}")
@PreAuthorize("hasAuthority('PLATFORM_SUPER_ADMIN')")
public ApiResponse<Void> deleteTenant(@PathVariable Long tenantId) {
platformTenantService.deleteTenant(tenantId);
return ApiResponse.success(null);
}
}
record CreateTenantRequest(
@NotBlank(message = "租户名称不能为空") String tenantName,
@NotBlank(message = "管理员账号不能为空") String adminUsername,
@NotBlank(message = "管理员密码不能为空") String adminPassword,
String miniappAppId
) {
PlatformTenantService.CreateTenantCommand toCommand() {
return new PlatformTenantService.CreateTenantCommand(
null,
tenantName,
adminUsername,
adminPassword,
null,
null,
null,
null,
20,
1024,
null,
new PlatformTenantService.MiniappConfigCommand(
"PROD",
miniappAppId,
"",
tenantName + "电子名片",
null,
null,
null,
null,
"v1.0.0",
"DRAFT",
null
)
);
}
}
record UpdateTenantRequest(
@NotBlank(message = "租户名称不能为空") String tenantName,
String miniappAppId
) {
PlatformTenantService.UpdateTenantCommand toCommand() {
return new PlatformTenantService.UpdateTenantCommand(
tenantName, miniappAppId, null, null, null, null, null, null, null
);
}
}
record MiniappConfigRequest(
String envCode,
String miniappAppId,
String miniappAppSecret,
String miniappName,
String miniappOriginalId,
String requestDomain,
String uploadDomain,
String downloadDomain,
String versionTag,
String publishStatus,
String remark
) {
PlatformTenantService.MiniappConfigCommand toCommand() {
return new PlatformTenantService.MiniappConfigCommand(
envCode == null ? "PROD" : envCode,
miniappAppId,
miniappAppSecret,
miniappName,
miniappOriginalId,
requestDomain,
uploadDomain,
downloadDomain,
versionTag,
publishStatus == null ? "DRAFT" : publishStatus,
remark
);
}
}

View File

@@ -0,0 +1,36 @@
package com.easycard.module.tenant.controller;
import com.easycard.common.api.ApiResponse;
import com.easycard.module.tenant.service.PlatformTenantService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/tenant/miniapp-config")
public class TenantMiniappController {
private final PlatformTenantService platformTenantService;
public TenantMiniappController(PlatformTenantService platformTenantService) {
this.platformTenantService = platformTenantService;
}
@GetMapping
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
public ApiResponse<PlatformTenantService.MiniappConfigView> getCurrentMiniappConfig() {
return ApiResponse.success(platformTenantService.getCurrentTenantMiniappConfig());
}
@PutMapping
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
public ApiResponse<PlatformTenantService.MiniappConfigView> updateCurrentMiniappConfig(@RequestBody UpdateTenantMiniappConfigRequest request) {
return ApiResponse.success(platformTenantService.updateCurrentTenantMiniappConfig(request == null ? null : request.miniappAppId()));
}
}
record UpdateTenantMiniappConfigRequest(String miniappAppId) {
}

View File

@@ -0,0 +1,168 @@
package com.easycard.module.tenant.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
@TableName("sys_tenant")
public class TenantDO {
@TableId(type = IdType.AUTO)
private Long id;
private String tenantCode;
private String tenantName;
private String tenantShortName;
private String contactName;
private String contactPhone;
private String contactEmail;
private String tenantStatus;
private LocalDateTime expireAt;
private Integer userLimit;
private Integer storageLimitMb;
private String remark;
private Long createdBy;
private LocalDateTime createdTime;
private Long updatedBy;
private LocalDateTime updatedTime;
@TableLogic
private Integer deleted;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public String getTenantName() {
return tenantName;
}
public void setTenantName(String tenantName) {
this.tenantName = tenantName;
}
public String getTenantShortName() {
return tenantShortName;
}
public void setTenantShortName(String tenantShortName) {
this.tenantShortName = tenantShortName;
}
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getContactPhone() {
return contactPhone;
}
public void setContactPhone(String contactPhone) {
this.contactPhone = contactPhone;
}
public String getContactEmail() {
return contactEmail;
}
public void setContactEmail(String contactEmail) {
this.contactEmail = contactEmail;
}
public String getTenantStatus() {
return tenantStatus;
}
public void setTenantStatus(String tenantStatus) {
this.tenantStatus = tenantStatus;
}
public LocalDateTime getExpireAt() {
return expireAt;
}
public void setExpireAt(LocalDateTime expireAt) {
this.expireAt = expireAt;
}
public Integer getUserLimit() {
return userLimit;
}
public void setUserLimit(Integer userLimit) {
this.userLimit = userLimit;
}
public Integer getStorageLimitMb() {
return storageLimitMb;
}
public void setStorageLimitMb(Integer storageLimitMb) {
this.storageLimitMb = storageLimitMb;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Long getCreatedBy() {
return createdBy;
}
public void setCreatedBy(Long createdBy) {
this.createdBy = createdBy;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public Long getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(Long updatedBy) {
this.updatedBy = updatedBy;
}
public LocalDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(LocalDateTime updatedTime) {
this.updatedTime = updatedTime;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
}

View File

@@ -0,0 +1,186 @@
package com.easycard.module.tenant.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
@TableName("tenant_miniapp_config")
public class TenantMiniappConfigDO {
@TableId(type = IdType.AUTO)
private Long id;
private Long tenantId;
private String envCode;
private String miniappAppId;
private String miniappAppSecret;
private String miniappName;
private String miniappOriginalId;
private String requestDomain;
private String uploadDomain;
private String downloadDomain;
private String versionTag;
private String publishStatus;
private LocalDateTime lastPublishedAt;
private String remark;
private Long createdBy;
private LocalDateTime createdTime;
private Long updatedBy;
private LocalDateTime updatedTime;
@TableLogic
private Integer deleted;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
public String getEnvCode() {
return envCode;
}
public void setEnvCode(String envCode) {
this.envCode = envCode;
}
public String getMiniappAppId() {
return miniappAppId;
}
public void setMiniappAppId(String miniappAppId) {
this.miniappAppId = miniappAppId;
}
public String getMiniappAppSecret() {
return miniappAppSecret;
}
public void setMiniappAppSecret(String miniappAppSecret) {
this.miniappAppSecret = miniappAppSecret;
}
public String getMiniappName() {
return miniappName;
}
public void setMiniappName(String miniappName) {
this.miniappName = miniappName;
}
public String getMiniappOriginalId() {
return miniappOriginalId;
}
public void setMiniappOriginalId(String miniappOriginalId) {
this.miniappOriginalId = miniappOriginalId;
}
public String getRequestDomain() {
return requestDomain;
}
public void setRequestDomain(String requestDomain) {
this.requestDomain = requestDomain;
}
public String getUploadDomain() {
return uploadDomain;
}
public void setUploadDomain(String uploadDomain) {
this.uploadDomain = uploadDomain;
}
public String getDownloadDomain() {
return downloadDomain;
}
public void setDownloadDomain(String downloadDomain) {
this.downloadDomain = downloadDomain;
}
public String getVersionTag() {
return versionTag;
}
public void setVersionTag(String versionTag) {
this.versionTag = versionTag;
}
public String getPublishStatus() {
return publishStatus;
}
public void setPublishStatus(String publishStatus) {
this.publishStatus = publishStatus;
}
public LocalDateTime getLastPublishedAt() {
return lastPublishedAt;
}
public void setLastPublishedAt(LocalDateTime lastPublishedAt) {
this.lastPublishedAt = lastPublishedAt;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Long getCreatedBy() {
return createdBy;
}
public void setCreatedBy(Long createdBy) {
this.createdBy = createdBy;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public Long getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(Long updatedBy) {
this.updatedBy = updatedBy;
}
public LocalDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(LocalDateTime updatedTime) {
this.updatedTime = updatedTime;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
}

View File

@@ -0,0 +1,7 @@
package com.easycard.module.tenant.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.easycard.module.tenant.dal.entity.TenantDO;
public interface TenantMapper extends BaseMapper<TenantDO> {
}

View File

@@ -0,0 +1,7 @@
package com.easycard.module.tenant.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.easycard.module.tenant.dal.entity.TenantMiniappConfigDO;
public interface TenantMiniappConfigMapper extends BaseMapper<TenantMiniappConfigDO> {
}

View File

@@ -0,0 +1,90 @@
package com.easycard.module.tenant.service;
import com.easycard.common.tenant.TenantContext;
import com.easycard.module.tenant.vo.TenantDetailResp;
import java.util.List;
import java.util.Optional;
public interface PlatformTenantService {
List<TenantDetailResp> listTenants();
Optional<TenantDetailResp> getTenantById(Long tenantId);
TenantDetailResp createTenant(CreateTenantCommand command);
TenantDetailResp updateTenant(Long tenantId, UpdateTenantCommand command);
void deleteTenant(Long tenantId);
List<MiniappConfigView> listMiniappConfigs();
MiniappConfigView updateMiniappConfig(Long tenantId, MiniappConfigCommand command);
MiniappConfigView getCurrentTenantMiniappConfig();
MiniappConfigView updateCurrentTenantMiniappConfig(String miniappAppId);
Optional<TenantContext> resolveTenantContextByMiniappAppId(String miniappAppId);
record CreateTenantCommand(
String tenantCode,
String tenantName,
String adminUsername,
String adminPassword,
String tenantShortName,
String contactName,
String contactPhone,
String contactEmail,
Integer userLimit,
Integer storageLimitMb,
String remark,
MiniappConfigCommand miniappConfig
) {
}
record UpdateTenantCommand(
String tenantName,
String miniappAppId,
String tenantShortName,
String contactName,
String contactPhone,
String contactEmail,
Integer userLimit,
Integer storageLimitMb,
String remark
) {
}
record MiniappConfigCommand(
String envCode,
String miniappAppId,
String miniappAppSecret,
String miniappName,
String miniappOriginalId,
String requestDomain,
String uploadDomain,
String downloadDomain,
String versionTag,
String publishStatus,
String remark
) {
}
record MiniappConfigView(
Long tenantId,
String tenantCode,
String tenantName,
String envCode,
String miniappAppId,
String miniappName,
String miniappOriginalId,
String requestDomain,
String uploadDomain,
String downloadDomain,
String versionTag,
String publishStatus
) {
}
}

View File

@@ -0,0 +1,573 @@
package com.easycard.module.tenant.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.easycard.common.auth.LoginUser;
import com.easycard.common.auth.SecurityUtils;
import com.easycard.common.exception.BusinessException;
import com.easycard.common.tenant.TenantContext;
import com.easycard.module.card.dal.entity.CardProfileDO;
import com.easycard.module.card.dal.entity.CardProfileSpecialtyDO;
import com.easycard.module.card.dal.mapper.CardProfileMapper;
import com.easycard.module.card.dal.mapper.CardProfileSpecialtyMapper;
import com.easycard.module.file.dal.entity.FileAssetDO;
import com.easycard.module.file.dal.entity.FileAssetUsageDO;
import com.easycard.module.file.dal.mapper.FileAssetMapper;
import com.easycard.module.file.dal.mapper.FileAssetUsageMapper;
import com.easycard.module.org.dal.entity.OrgDepartmentDO;
import com.easycard.module.org.dal.entity.OrgFirmPracticeAreaDO;
import com.easycard.module.org.dal.entity.OrgFirmProfileDO;
import com.easycard.module.org.dal.mapper.OrgDepartmentMapper;
import com.easycard.module.org.dal.mapper.OrgFirmPracticeAreaMapper;
import com.easycard.module.org.dal.mapper.OrgFirmProfileMapper;
import com.easycard.module.stat.dal.entity.CardShareLogDO;
import com.easycard.module.stat.dal.entity.CardStatDailyDO;
import com.easycard.module.stat.dal.entity.CardViewLogDO;
import com.easycard.module.stat.dal.mapper.CardShareLogMapper;
import com.easycard.module.stat.dal.mapper.CardStatDailyMapper;
import com.easycard.module.stat.dal.mapper.CardViewLogMapper;
import com.easycard.module.tenant.dal.entity.TenantDO;
import com.easycard.module.tenant.dal.entity.TenantMiniappConfigDO;
import com.easycard.module.tenant.dal.mapper.TenantMapper;
import com.easycard.module.tenant.dal.mapper.TenantMiniappConfigMapper;
import com.easycard.module.tenant.service.PlatformTenantService;
import com.easycard.module.tenant.vo.TenantDetailResp;
import com.easycard.module.user.dal.entity.SysLoginLogDO;
import com.easycard.module.user.dal.entity.SysOperationLogDO;
import com.easycard.module.user.dal.entity.SysRoleDO;
import com.easycard.module.user.dal.entity.SysUserDO;
import com.easycard.module.user.dal.entity.SysUserRoleDO;
import com.easycard.module.user.dal.mapper.SysLoginLogMapper;
import com.easycard.module.user.dal.mapper.SysOperationLogMapper;
import com.easycard.module.user.dal.mapper.SysRoleMapper;
import com.easycard.module.user.dal.mapper.SysUserMapper;
import com.easycard.module.user.dal.mapper.SysUserRoleMapper;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@Primary
public class PlatformTenantServiceImpl implements PlatformTenantService {
private final TenantMapper tenantMapper;
private final TenantMiniappConfigMapper tenantMiniappConfigMapper;
private final SysUserMapper sysUserMapper;
private final SysRoleMapper sysRoleMapper;
private final SysUserRoleMapper sysUserRoleMapper;
private final SysLoginLogMapper sysLoginLogMapper;
private final SysOperationLogMapper sysOperationLogMapper;
private final OrgDepartmentMapper orgDepartmentMapper;
private final OrgFirmProfileMapper orgFirmProfileMapper;
private final OrgFirmPracticeAreaMapper orgFirmPracticeAreaMapper;
private final CardProfileMapper cardProfileMapper;
private final CardProfileSpecialtyMapper cardProfileSpecialtyMapper;
private final FileAssetMapper fileAssetMapper;
private final FileAssetUsageMapper fileAssetUsageMapper;
private final CardViewLogMapper cardViewLogMapper;
private final CardShareLogMapper cardShareLogMapper;
private final CardStatDailyMapper cardStatDailyMapper;
private final PasswordEncoder passwordEncoder;
public PlatformTenantServiceImpl(
TenantMapper tenantMapper,
TenantMiniappConfigMapper tenantMiniappConfigMapper,
SysUserMapper sysUserMapper,
SysRoleMapper sysRoleMapper,
SysUserRoleMapper sysUserRoleMapper,
SysLoginLogMapper sysLoginLogMapper,
SysOperationLogMapper sysOperationLogMapper,
OrgDepartmentMapper orgDepartmentMapper,
OrgFirmProfileMapper orgFirmProfileMapper,
OrgFirmPracticeAreaMapper orgFirmPracticeAreaMapper,
CardProfileMapper cardProfileMapper,
CardProfileSpecialtyMapper cardProfileSpecialtyMapper,
FileAssetMapper fileAssetMapper,
FileAssetUsageMapper fileAssetUsageMapper,
CardViewLogMapper cardViewLogMapper,
CardShareLogMapper cardShareLogMapper,
CardStatDailyMapper cardStatDailyMapper,
PasswordEncoder passwordEncoder
) {
this.tenantMapper = tenantMapper;
this.tenantMiniappConfigMapper = tenantMiniappConfigMapper;
this.sysUserMapper = sysUserMapper;
this.sysRoleMapper = sysRoleMapper;
this.sysUserRoleMapper = sysUserRoleMapper;
this.sysLoginLogMapper = sysLoginLogMapper;
this.sysOperationLogMapper = sysOperationLogMapper;
this.orgDepartmentMapper = orgDepartmentMapper;
this.orgFirmProfileMapper = orgFirmProfileMapper;
this.orgFirmPracticeAreaMapper = orgFirmPracticeAreaMapper;
this.cardProfileMapper = cardProfileMapper;
this.cardProfileSpecialtyMapper = cardProfileSpecialtyMapper;
this.fileAssetMapper = fileAssetMapper;
this.fileAssetUsageMapper = fileAssetUsageMapper;
this.cardViewLogMapper = cardViewLogMapper;
this.cardShareLogMapper = cardShareLogMapper;
this.cardStatDailyMapper = cardStatDailyMapper;
this.passwordEncoder = passwordEncoder;
}
@Override
public List<TenantDetailResp> listTenants() {
List<TenantDO> tenants = tenantMapper.selectList(
Wrappers.<TenantDO>lambdaQuery().eq(TenantDO::getDeleted, 0).orderByDesc(TenantDO::getId)
);
if (tenants.isEmpty()) {
return List.of();
}
List<Long> tenantIds = tenants.stream().map(TenantDO::getId).toList();
List<TenantMiniappConfigDO> miniappConfigs = tenantMiniappConfigMapper.selectList(
Wrappers.<TenantMiniappConfigDO>lambdaQuery()
.in(TenantMiniappConfigDO::getTenantId, tenantIds)
.eq(TenantMiniappConfigDO::getDeleted, 0)
.eq(TenantMiniappConfigDO::getEnvCode, "PROD")
);
Map<Long, TenantMiniappConfigDO> miniappConfigMap = new HashMap<>();
for (TenantMiniappConfigDO miniappConfig : miniappConfigs) {
miniappConfigMap.put(miniappConfig.getTenantId(), miniappConfig);
}
return tenants.stream()
.map(tenant -> toResp(tenant, miniappConfigMap.get(tenant.getId())))
.toList();
}
@Override
public Optional<TenantDetailResp> getTenantById(Long tenantId) {
TenantDO tenant = tenantMapper.selectById(tenantId);
if (tenant == null || Integer.valueOf(1).equals(tenant.getDeleted())) {
return Optional.empty();
}
TenantMiniappConfigDO miniappConfig = tenantMiniappConfigMapper.selectOne(
Wrappers.<TenantMiniappConfigDO>lambdaQuery()
.eq(TenantMiniappConfigDO::getTenantId, tenantId)
.eq(TenantMiniappConfigDO::getEnvCode, "PROD")
.eq(TenantMiniappConfigDO::getDeleted, 0)
.last("LIMIT 1")
);
return Optional.of(toResp(tenant, miniappConfig));
}
@Override
@Transactional
public TenantDetailResp createTenant(CreateTenantCommand command) {
String tenantCode = StringUtils.hasText(command.tenantCode())
? command.tenantCode()
: generateTenantCode(command.tenantName(), command.miniappConfig() == null ? null : command.miniappConfig().miniappAppId());
validateTenantUniqueness(tenantCode, command.miniappConfig() == null ? null : command.miniappConfig().miniappAppId(), null);
TenantDO tenant = new TenantDO();
tenant.setTenantCode(tenantCode);
tenant.setTenantName(command.tenantName());
tenant.setTenantShortName(command.tenantShortName());
tenant.setContactName(command.contactName());
tenant.setContactPhone(command.contactPhone());
tenant.setContactEmail(command.contactEmail());
tenant.setTenantStatus("ENABLED");
tenant.setExpireAt(null);
tenant.setUserLimit(command.userLimit());
tenant.setStorageLimitMb(command.storageLimitMb());
tenant.setRemark(command.remark());
tenant.setCreatedBy(1L);
tenant.setUpdatedBy(1L);
tenant.setDeleted(0);
tenantMapper.insert(tenant);
createBuiltinRoles(tenant.getId());
createTenantAdminUser(tenant.getId(), command.adminUsername(), command.adminPassword(), command.tenantName());
TenantMiniappConfigDO miniappConfig = saveMiniappConfigInternal(tenant.getId(), command.miniappConfig(), null, 1L);
return toResp(tenant, miniappConfig);
}
@Override
@Transactional
public TenantDetailResp updateTenant(Long tenantId, UpdateTenantCommand command) {
TenantDO tenant = getRequiredTenant(tenantId);
String miniappAppId = StringUtils.hasText(command.miniappAppId()) ? command.miniappAppId().trim() : null;
validateTenantUniqueness(null, miniappAppId, tenantId);
tenant.setTenantName(command.tenantName());
tenant.setTenantShortName(command.tenantShortName());
tenant.setContactName(command.contactName());
tenant.setContactPhone(command.contactPhone());
tenant.setContactEmail(command.contactEmail());
tenant.setUserLimit(command.userLimit());
tenant.setStorageLimitMb(command.storageLimitMb());
tenant.setRemark(command.remark());
tenant.setUpdatedBy(1L);
tenantMapper.updateById(tenant);
TenantMiniappConfigDO miniappConfig = tenantMiniappConfigMapper.selectOne(Wrappers.<TenantMiniappConfigDO>lambdaQuery()
.eq(TenantMiniappConfigDO::getTenantId, tenantId)
.eq(TenantMiniappConfigDO::getEnvCode, "PROD")
.eq(TenantMiniappConfigDO::getDeleted, 0)
.last("LIMIT 1"));
if (miniappConfig != null || miniappAppId != null) {
miniappConfig = saveMiniappConfigInternal(
tenantId,
new MiniappConfigCommand(
miniappConfig == null ? "PROD" : miniappConfig.getEnvCode(),
miniappAppId,
miniappConfig == null ? null : miniappConfig.getMiniappAppSecret(),
miniappConfig == null ? tenant.getTenantName() + "电子名片" : miniappConfig.getMiniappName(),
miniappConfig == null ? null : miniappConfig.getMiniappOriginalId(),
miniappConfig == null ? null : miniappConfig.getRequestDomain(),
miniappConfig == null ? null : miniappConfig.getUploadDomain(),
miniappConfig == null ? null : miniappConfig.getDownloadDomain(),
miniappConfig == null ? "v1.0.0" : miniappConfig.getVersionTag(),
miniappConfig == null ? "DRAFT" : miniappConfig.getPublishStatus(),
miniappConfig == null ? null : miniappConfig.getRemark()
),
miniappConfig,
1L
);
}
return toResp(tenant, miniappConfig);
}
@Override
@Transactional
public void deleteTenant(Long tenantId) {
TenantDO tenant = getRequiredTenant(tenantId);
sysUserRoleMapper.delete(Wrappers.<SysUserRoleDO>lambdaQuery().eq(SysUserRoleDO::getTenantId, tenantId));
sysLoginLogMapper.delete(Wrappers.<SysLoginLogDO>lambdaQuery().eq(SysLoginLogDO::getTenantId, tenantId));
sysOperationLogMapper.delete(Wrappers.<SysOperationLogDO>lambdaQuery().eq(SysOperationLogDO::getTenantId, tenantId));
cardViewLogMapper.delete(Wrappers.<CardViewLogDO>lambdaQuery().eq(CardViewLogDO::getTenantId, tenantId));
cardShareLogMapper.delete(Wrappers.<CardShareLogDO>lambdaQuery().eq(CardShareLogDO::getTenantId, tenantId));
cardStatDailyMapper.delete(Wrappers.<CardStatDailyDO>lambdaQuery().eq(CardStatDailyDO::getTenantId, tenantId));
cardProfileSpecialtyMapper.deleteForceByTenantId(tenantId);
cardProfileMapper.delete(Wrappers.<CardProfileDO>lambdaQuery().eq(CardProfileDO::getTenantId, tenantId));
fileAssetUsageMapper.delete(Wrappers.<FileAssetUsageDO>lambdaQuery().eq(FileAssetUsageDO::getTenantId, tenantId));
fileAssetMapper.delete(Wrappers.<FileAssetDO>lambdaQuery().eq(FileAssetDO::getTenantId, tenantId));
orgDepartmentMapper.delete(Wrappers.<OrgDepartmentDO>lambdaQuery().eq(OrgDepartmentDO::getTenantId, tenantId));
orgFirmPracticeAreaMapper.delete(Wrappers.<OrgFirmPracticeAreaDO>lambdaQuery().eq(OrgFirmPracticeAreaDO::getTenantId, tenantId));
orgFirmProfileMapper.delete(Wrappers.<OrgFirmProfileDO>lambdaQuery().eq(OrgFirmProfileDO::getTenantId, tenantId));
sysUserMapper.delete(Wrappers.<SysUserDO>lambdaQuery().eq(SysUserDO::getTenantId, tenantId));
sysRoleMapper.delete(Wrappers.<SysRoleDO>lambdaQuery().eq(SysRoleDO::getTenantId, tenantId));
tenantMiniappConfigMapper.delete(Wrappers.<TenantMiniappConfigDO>lambdaQuery().eq(TenantMiniappConfigDO::getTenantId, tenantId));
tenantMapper.deleteById(tenant.getId());
}
@Override
public List<MiniappConfigView> listMiniappConfigs() {
List<TenantMiniappConfigDO> configs = tenantMiniappConfigMapper.selectList(Wrappers.<TenantMiniappConfigDO>lambdaQuery()
.eq(TenantMiniappConfigDO::getDeleted, 0)
.orderByDesc(TenantMiniappConfigDO::getId));
if (configs.isEmpty()) {
return List.of();
}
List<Long> tenantIds = configs.stream().map(TenantMiniappConfigDO::getTenantId).distinct().toList();
Map<Long, TenantDO> tenantMap = tenantMapper.selectBatchIds(tenantIds).stream()
.collect(HashMap::new, (map, item) -> map.put(item.getId(), item), HashMap::putAll);
return configs.stream().map(config -> {
TenantDO tenant = tenantMap.get(config.getTenantId());
return new MiniappConfigView(
config.getTenantId(),
tenant == null ? "" : tenant.getTenantCode(),
tenant == null ? "" : tenant.getTenantName(),
config.getEnvCode(),
config.getMiniappAppId(),
config.getMiniappName(),
config.getMiniappOriginalId(),
config.getRequestDomain(),
config.getUploadDomain(),
config.getDownloadDomain(),
config.getVersionTag(),
config.getPublishStatus()
);
}).toList();
}
@Override
@Transactional
public MiniappConfigView updateMiniappConfig(Long tenantId, MiniappConfigCommand command) {
TenantDO tenant = getRequiredTenant(tenantId);
validateTenantUniqueness(null, command.miniappAppId(), tenantId);
TenantMiniappConfigDO config = saveMiniappConfigInternal(tenantId, command, getTenantMiniappConfig(tenantId, command.envCode()), 1L);
return toMiniappConfigView(tenant, config);
}
@Override
public MiniappConfigView getCurrentTenantMiniappConfig() {
LoginUser loginUser = SecurityUtils.getRequiredLoginUser();
Long tenantId = getRequiredCurrentTenantId(loginUser);
TenantDO tenant = getRequiredTenant(tenantId);
return toMiniappConfigView(tenant, getTenantMiniappConfig(tenantId, "PROD"));
}
@Override
@Transactional
public MiniappConfigView updateCurrentTenantMiniappConfig(String miniappAppId) {
LoginUser loginUser = SecurityUtils.getRequiredLoginUser();
Long tenantId = getRequiredCurrentTenantId(loginUser);
TenantDO tenant = getRequiredTenant(tenantId);
String normalizedAppId = StringUtils.hasText(miniappAppId) ? miniappAppId.trim() : null;
validateTenantUniqueness(null, normalizedAppId, tenantId);
TenantMiniappConfigDO existed = getTenantMiniappConfig(tenantId, "PROD");
TenantMiniappConfigDO saved = saveMiniappConfigInternal(
tenantId,
new MiniappConfigCommand(
"PROD",
normalizedAppId,
existed == null ? null : existed.getMiniappAppSecret(),
existed == null ? tenant.getTenantName() + "电子名片" : existed.getMiniappName(),
existed == null ? null : existed.getMiniappOriginalId(),
existed == null ? null : existed.getRequestDomain(),
existed == null ? null : existed.getUploadDomain(),
existed == null ? null : existed.getDownloadDomain(),
existed == null ? "v1.0.0" : existed.getVersionTag(),
existed == null ? "DRAFT" : existed.getPublishStatus(),
existed == null ? null : existed.getRemark()
),
existed,
loginUser.userId()
);
return toMiniappConfigView(tenant, saved);
}
@Override
public Optional<TenantContext> resolveTenantContextByMiniappAppId(String miniappAppId) {
if (!StringUtils.hasText(miniappAppId)) {
return Optional.empty();
}
TenantMiniappConfigDO miniappConfig = tenantMiniappConfigMapper.selectOne(
Wrappers.<TenantMiniappConfigDO>lambdaQuery()
.eq(TenantMiniappConfigDO::getMiniappAppId, miniappAppId)
.eq(TenantMiniappConfigDO::getDeleted, 0)
.last("LIMIT 1")
);
if (miniappConfig == null) {
return Optional.empty();
}
TenantDO tenant = tenantMapper.selectById(miniappConfig.getTenantId());
if (tenant == null || Integer.valueOf(1).equals(tenant.getDeleted())) {
return Optional.empty();
}
return Optional.of(new TenantContext(tenant.getId(), tenant.getTenantCode(), miniappAppId));
}
private void validateTenantUniqueness(String tenantCode, String miniappAppId, Long currentTenantId) {
if (StringUtils.hasText(tenantCode)) {
TenantDO existedTenant = tenantMapper.selectOne(Wrappers.<TenantDO>lambdaQuery()
.eq(TenantDO::getTenantCode, tenantCode)
.eq(TenantDO::getDeleted, 0)
.last("LIMIT 1"));
if (existedTenant != null && !existedTenant.getId().equals(currentTenantId)) {
throw new BusinessException("TENANT_CODE_DUPLICATED", "租户编码已存在");
}
}
if (StringUtils.hasText(miniappAppId)) {
TenantMiniappConfigDO existedConfig = tenantMiniappConfigMapper.selectOne(Wrappers.<TenantMiniappConfigDO>lambdaQuery()
.eq(TenantMiniappConfigDO::getMiniappAppId, miniappAppId)
.eq(TenantMiniappConfigDO::getDeleted, 0)
.last("LIMIT 1"));
if (existedConfig != null && !existedConfig.getTenantId().equals(currentTenantId)) {
throw new BusinessException("MINIAPP_APP_ID_DUPLICATED", "小程序 AppID 已存在");
}
}
}
private TenantDO getRequiredTenant(Long tenantId) {
TenantDO tenant = tenantMapper.selectById(tenantId);
if (tenant == null || Integer.valueOf(1).equals(tenant.getDeleted())) {
throw new BusinessException("TENANT_NOT_FOUND", "租户不存在");
}
return tenant;
}
private Long getRequiredCurrentTenantId(LoginUser loginUser) {
if (loginUser.tenantId() == null) {
throw new BusinessException("TENANT_CONTEXT_MISSING", "当前账号未绑定租户");
}
return loginUser.tenantId();
}
private TenantMiniappConfigDO getTenantMiniappConfig(Long tenantId, String envCode) {
return tenantMiniappConfigMapper.selectOne(Wrappers.<TenantMiniappConfigDO>lambdaQuery()
.eq(TenantMiniappConfigDO::getTenantId, tenantId)
.eq(TenantMiniappConfigDO::getEnvCode, envCode)
.eq(TenantMiniappConfigDO::getDeleted, 0)
.last("LIMIT 1"));
}
private TenantMiniappConfigDO saveMiniappConfigInternal(Long tenantId, MiniappConfigCommand command, TenantMiniappConfigDO config, Long operatorId) {
if (command == null) {
return null;
}
if (config == null) {
config = new TenantMiniappConfigDO();
config.setTenantId(tenantId);
config.setCreatedBy(operatorId);
config.setDeleted(0);
}
config.setEnvCode(StringUtils.hasText(command.envCode()) ? command.envCode() : "PROD");
config.setMiniappAppId(StringUtils.hasText(command.miniappAppId()) ? command.miniappAppId().trim() : null);
config.setMiniappAppSecret(command.miniappAppSecret());
config.setMiniappName(command.miniappName());
config.setMiniappOriginalId(command.miniappOriginalId());
config.setRequestDomain(command.requestDomain());
config.setUploadDomain(command.uploadDomain());
config.setDownloadDomain(command.downloadDomain());
config.setVersionTag(command.versionTag());
config.setPublishStatus(command.publishStatus());
config.setRemark(command.remark());
config.setUpdatedBy(operatorId);
if ("PUBLISHED".equals(command.publishStatus())) {
config.setLastPublishedAt(LocalDateTime.now());
}
if (config.getId() == null) {
tenantMiniappConfigMapper.insert(config);
} else {
tenantMiniappConfigMapper.updateById(config);
}
return config;
}
private MiniappConfigView toMiniappConfigView(TenantDO tenant, TenantMiniappConfigDO config) {
if (config == null) {
return new MiniappConfigView(
tenant.getId(),
emptyIfNull(tenant.getTenantCode()),
emptyIfNull(tenant.getTenantName()),
"PROD",
"",
"",
"",
"",
"",
"",
"v1.0.0",
"UNCONFIGURED"
);
}
return new MiniappConfigView(
tenant.getId(),
emptyIfNull(tenant.getTenantCode()),
emptyIfNull(tenant.getTenantName()),
StringUtils.hasText(config.getEnvCode()) ? config.getEnvCode() : "PROD",
emptyIfNull(config.getMiniappAppId()),
emptyIfNull(config.getMiniappName()),
emptyIfNull(config.getMiniappOriginalId()),
emptyIfNull(config.getRequestDomain()),
emptyIfNull(config.getUploadDomain()),
emptyIfNull(config.getDownloadDomain()),
StringUtils.hasText(config.getVersionTag()) ? config.getVersionTag() : "v1.0.0",
StringUtils.hasText(config.getPublishStatus()) ? config.getPublishStatus() : "DRAFT"
);
}
private String emptyIfNull(String value) {
return value == null ? "" : value;
}
private void createBuiltinRoles(Long tenantId) {
insertRoleIfAbsent(tenantId, "TENANT_ADMIN", "租户管理员", "TENANT");
insertRoleIfAbsent(tenantId, "TENANT_USER", "普通用户", "SELF");
}
private void insertRoleIfAbsent(Long tenantId, String roleCode, String roleName, String dataScope) {
SysRoleDO existed = sysRoleMapper.selectOne(Wrappers.<SysRoleDO>lambdaQuery()
.eq(SysRoleDO::getTenantId, tenantId)
.eq(SysRoleDO::getRoleCode, roleCode)
.eq(SysRoleDO::getDeleted, 0)
.last("LIMIT 1"));
if (existed != null) {
return;
}
SysRoleDO role = new SysRoleDO();
role.setTenantId(tenantId);
role.setRoleScope("TENANT");
role.setRoleCode(roleCode);
role.setRoleName(roleName);
role.setDataScope(dataScope);
role.setIsBuiltin(1);
role.setRoleStatus("ENABLED");
role.setCreatedBy(1L);
role.setUpdatedBy(1L);
role.setDeleted(0);
sysRoleMapper.insert(role);
}
private void createTenantAdminUser(Long tenantId, String adminUsername, String adminPassword, String tenantName) {
String username = StringUtils.hasText(adminUsername) ? adminUsername.trim() : "tenant.admin";
String rawPassword = StringUtils.hasText(adminPassword) ? adminPassword : "123456";
SysUserDO existed = sysUserMapper.selectOne(Wrappers.<SysUserDO>lambdaQuery()
.eq(SysUserDO::getTenantId, tenantId)
.eq(SysUserDO::getUsername, username)
.eq(SysUserDO::getDeleted, 0)
.last("LIMIT 1"));
if (existed != null) {
return;
}
SysUserDO user = new SysUserDO();
user.setTenantId(tenantId);
user.setUserType("TENANT");
user.setUsername(username);
user.setPasswordHash(passwordEncoder.encode(rawPassword));
user.setRealName(resolveAdminDisplayName(tenantName));
user.setNickName(resolveAdminDisplayName(tenantName));
user.setGender("UNKNOWN");
user.setUserStatus("ENABLED");
user.setMustUpdatePassword(1);
user.setCreatedBy(1L);
user.setUpdatedBy(1L);
user.setDeleted(0);
sysUserMapper.insert(user);
SysRoleDO adminRole = sysRoleMapper.selectOne(Wrappers.<SysRoleDO>lambdaQuery()
.eq(SysRoleDO::getTenantId, tenantId)
.eq(SysRoleDO::getRoleCode, "TENANT_ADMIN")
.eq(SysRoleDO::getDeleted, 0)
.last("LIMIT 1"));
if (adminRole != null) {
SysUserRoleDO userRole = new SysUserRoleDO();
userRole.setTenantId(tenantId);
userRole.setUserId(user.getId());
userRole.setRoleId(adminRole.getId());
userRole.setCreatedBy(1L);
sysUserRoleMapper.insert(userRole);
}
}
private String generateTenantCode(String tenantName, String miniappAppId) {
if (StringUtils.hasText(miniappAppId)) {
return "tenant-" + miniappAppId.trim().toLowerCase().replaceAll("[^a-z0-9]", "");
}
return "tenant-" + System.currentTimeMillis();
}
private String resolveAdminDisplayName(String tenantName) {
if (!StringUtils.hasText(tenantName)) {
return "租户管理员";
}
return tenantName.length() > 24 ? tenantName.substring(0, 24) : tenantName;
}
private TenantDetailResp toResp(TenantDO tenant, TenantMiniappConfigDO miniappConfig) {
TenantDetailResp resp = new TenantDetailResp();
resp.setId(tenant.getId());
resp.setTenantCode(tenant.getTenantCode());
resp.setTenantName(tenant.getTenantName());
resp.setTenantShortName(tenant.getTenantShortName());
resp.setUserLimit(tenant.getUserLimit());
resp.setStorageLimitMb(tenant.getStorageLimitMb());
resp.setContactName(tenant.getContactName());
resp.setContactPhone(tenant.getContactPhone());
if (miniappConfig != null) {
resp.setMiniappAppId(miniappConfig.getMiniappAppId());
resp.setMiniappName(miniappConfig.getMiniappName());
resp.setMiniappOriginalId(miniappConfig.getMiniappOriginalId());
}
return resp;
}
}

View File

@@ -0,0 +1,105 @@
package com.easycard.module.tenant.vo;
public class TenantDetailResp {
private Long id;
private String tenantCode;
private String tenantName;
private String tenantShortName;
private Integer userLimit;
private Integer storageLimitMb;
private String contactName;
private String contactPhone;
private String miniappAppId;
private String miniappName;
private String miniappOriginalId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public String getTenantName() {
return tenantName;
}
public void setTenantName(String tenantName) {
this.tenantName = tenantName;
}
public String getTenantShortName() {
return tenantShortName;
}
public void setTenantShortName(String tenantShortName) {
this.tenantShortName = tenantShortName;
}
public Integer getUserLimit() {
return userLimit;
}
public void setUserLimit(Integer userLimit) {
this.userLimit = userLimit;
}
public Integer getStorageLimitMb() {
return storageLimitMb;
}
public void setStorageLimitMb(Integer storageLimitMb) {
this.storageLimitMb = storageLimitMb;
}
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getContactPhone() {
return contactPhone;
}
public void setContactPhone(String contactPhone) {
this.contactPhone = contactPhone;
}
public String getMiniappAppId() {
return miniappAppId;
}
public void setMiniappAppId(String miniappAppId) {
this.miniappAppId = miniappAppId;
}
public String getMiniappName() {
return miniappName;
}
public void setMiniappName(String miniappName) {
this.miniappName = miniappName;
}
public String getMiniappOriginalId() {
return miniappOriginalId;
}
public void setMiniappOriginalId(String miniappOriginalId) {
this.miniappOriginalId = miniappOriginalId;
}
}

View File

@@ -0,0 +1,70 @@
package com.easycard.module.tenant.web;
import com.easycard.common.api.ApiResponse;
import com.easycard.common.tenant.TenantContext;
import com.easycard.common.tenant.TenantContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.easycard.module.tenant.service.PlatformTenantService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
@Component
public class MiniappTenantContextFilter extends OncePerRequestFilter {
private static final String MINIAPP_APP_ID_HEADER = "X-Miniapp-Appid";
private final PlatformTenantService platformTenantService;
private final ObjectMapper objectMapper;
public MiniappTenantContextFilter(PlatformTenantService platformTenantService, ObjectMapper objectMapper) {
this.platformTenantService = platformTenantService;
this.objectMapper = objectMapper;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getRequestURI().startsWith("/api/open/");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String miniappAppId = request.getHeader(MINIAPP_APP_ID_HEADER);
if (!StringUtils.hasText(miniappAppId)) {
writeError(response, HttpServletResponse.SC_BAD_REQUEST,
ApiResponse.fail("MINIAPP_APP_ID_MISSING", "缺少请求头 X-Miniapp-Appid"));
return;
}
Optional<TenantContext> tenantContextOptional = platformTenantService.resolveTenantContextByMiniappAppId(miniappAppId);
if (tenantContextOptional.isEmpty()) {
writeError(response, HttpServletResponse.SC_NOT_FOUND,
ApiResponse.fail("TENANT_MINIAPP_NOT_FOUND", "未找到对应的小程序租户配置"));
return;
}
try {
TenantContextHolder.set(tenantContextOptional.get());
filterChain.doFilter(request, response);
} finally {
TenantContextHolder.clear();
}
}
private void writeError(HttpServletResponse response, int status, ApiResponse<Void> apiResponse) throws IOException {
response.setStatus(status);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(objectMapper.writeValueAsString(apiResponse));
}
}