reactor:@TenantIgnore 添加在 Controller 时,自动添加到 TenantProperties 中

This commit is contained in:
YunaiV 2025-05-05 09:33:34 +08:00
parent 5a87b33df2
commit be86cdfd51
8 changed files with 83 additions and 30 deletions

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
@ -19,11 +20,13 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import jakarta.annotation.Resource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.BatchStrategies;
@ -32,14 +35,24 @@ import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@AutoConfiguration
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
@EnableConfigurationProperties(TenantProperties.class)
public class YudaoTenantAutoConfiguration {
@Resource
private ApplicationContext applicationContext;
@Bean
public TenantFrameworkService tenantFrameworkService(TenantApi tenantApi) {
return new TenantFrameworkServiceImpl(tenantApi);
@ -67,13 +80,41 @@ public class YudaoTenantAutoConfiguration {
// ========== WEB ==========
@Bean
public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter(TenantProperties tenantProperties) {
FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TenantContextWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
addIgnoreUrls(tenantProperties);
return registrationBean;
}
/**
* 如果 Controller 接口上 {@link TenantIgnore} 注解那么添加到忽略的 URL
*
* @param tenantProperties 租户配置
*/
private void addIgnoreUrls(TenantProperties tenantProperties) {
// 获得接口对应的 HandlerMethod 集合
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获得有 @TenantIgnore 注解的接口
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = entry.getValue();
if (!handlerMethod.hasMethodAnnotation(TenantIgnore.class)) {
continue;
}
// 添加到忽略的 URL
if (entry.getKey().getPatternsCondition() != null) {
tenantProperties.getIgnoreUrls().addAll(entry.getKey().getPatternsCondition().getPatterns());
}
if (entry.getKey().getPathPatternsCondition() != null) {
tenantProperties.getIgnoreUrls().addAll(
convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));
}
}
}
// ========== Security ==========
@Bean

View File

@ -6,6 +6,7 @@ import cn.hutool.core.util.URLUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.service.file.FileService;
@ -76,6 +77,7 @@ public class FileController {
@GetMapping("/{configId}/get/**")
@PermitAll
@TenantIgnore
@Operation(summary = "下载文件")
@Parameter(name = "configId", description = "配置编号", required = true)
public void getFileContent(HttpServletRequest request,

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mp.controller.admin.open;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO;
@ -35,26 +36,6 @@ public class MpOpenController {
@Resource
private MpAccountService mpAccountService;
/**
* 接收微信公众号的校验签名
*
* 对应 <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">文档</a>
*/
@Operation(summary = "校验签名") // 参见
@GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8")
public String checkSignature(@PathVariable("appId") String appId,
MpOpenCheckSignatureReqVO reqVO) {
log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO);
// 校验请求签名
WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
// 校验通过
if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {
return reqVO.getEchostr();
}
// 校验不通过
return "非法请求";
}
/**
* 接收微信公众号的消息推送
*
@ -62,6 +43,7 @@ public class MpOpenController {
*/
@Operation(summary = "处理消息")
@PostMapping(value = "/{appId}", produces = "application/xml; charset=UTF-8")
@TenantIgnore
public String handleMessage(@PathVariable("appId") String appId,
@RequestBody String content,
MpOpenHandleMessageReqVO reqVO) {
@ -79,6 +61,27 @@ public class MpOpenController {
}
}
/**
* 接收微信公众号的校验签名
*
* 对应 <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">文档</a>
*/
@Operation(summary = "校验签名") // 参见
@GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8")
@TenantIgnore
public String checkSignature(@PathVariable("appId") String appId,
MpOpenCheckSignatureReqVO reqVO) {
log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO);
// 校验请求签名
WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
// 校验通过
if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {
return reqVO.getEchostr();
}
// 校验不通过
return "非法请求";
}
private String handleMessage0(String appId, String content, MpOpenHandleMessageReqVO reqVO) {
// 校验请求签名
WxMpService mppService = mpServiceFactory.getRequiredMpService(appId);

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
@ -62,6 +63,7 @@ public class PayNotifyController {
@PostMapping(value = "/order/{channelId}")
@Operation(summary = "支付渠道的统一【支付】回调")
@PermitAll
@TenantIgnore
public String notifyOrder(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
@ -82,6 +84,7 @@ public class PayNotifyController {
@PostMapping(value = "/refund/{channelId}")
@Operation(summary = "支付渠道的统一【退款】回调")
@PermitAll
@TenantIgnore
public String notifyRefund(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
@ -102,6 +105,7 @@ public class PayNotifyController {
@PostMapping(value = "/transfer/{channelId}")
@Operation(summary = "支付渠道的统一【转账】回调")
@PermitAll
@TenantIgnore
public String notifyTransfer(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.controller.admin.captcha;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
@ -26,6 +27,7 @@ public class CaptchaController {
@PostMapping({"/get"})
@Operation(summary = "获得验证码")
@PermitAll
@TenantIgnore
public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {
assert request.getRemoteHost() != null;
data.setBrowserInfo(getRemoteId(request));
@ -35,6 +37,7 @@ public class CaptchaController {
@PostMapping("/check")
@Operation(summary = "校验验证码")
@PermitAll
@TenantIgnore
public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {
data.setBrowserInfo(getRemoteId(request));
return captchaService.check(data);

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.controller.admin.sms;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import io.swagger.v3.oas.annotations.Operation;
@ -24,6 +25,7 @@ public class SmsCallbackController {
@PostMapping("/aliyun")
@PermitAll
@TenantIgnore
@Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
String text = ServletUtils.getBody(request);
@ -33,6 +35,7 @@ public class SmsCallbackController {
@PostMapping("/tencent")
@PermitAll
@TenantIgnore
@Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
String text = ServletUtils.getBody(request);
@ -43,6 +46,7 @@ public class SmsCallbackController {
@PostMapping("/huawei")
@PermitAll
@TenantIgnore
@Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档")
public CommonResult<Boolean> receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable {
smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody);
@ -51,6 +55,7 @@ public class SmsCallbackController {
@PostMapping("/qiniu")
@PermitAll
@TenantIgnore
@Operation(summary = "七牛云短信的回调", description = "参见 https://developer.qiniu.com/sms/5910/message-push 文档")
public CommonResult<Boolean> receiveQiniuSmsStatus(@RequestBody String requestBody) throws Throwable {
smsSendService.receiveSmsStatus(SmsChannelEnum.QINIU.getCode(), requestBody);

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
@ -39,6 +40,7 @@ public class TenantController {
@GetMapping("/get-id-by-name")
@PermitAll
@TenantIgnore
@Operation(summary = "使用租户名,获得租户编号", description = "登录界面,根据用户的租户名,获得租户编号")
@Parameter(name = "name", description = "租户名", required = true, example = "1024")
public CommonResult<Long> getTenantIdByName(@RequestParam("name") String name) {
@ -48,6 +50,7 @@ public class TenantController {
@GetMapping({ "simple-list" })
@PermitAll
@TenantIgnore
@Operation(summary = "获取租户精简信息列表", description = "只包含被开启的租户,用于【首页】功能的选择租户选项")
public CommonResult<List<TenantRespVO>> getTenantSimpleList() {
List<TenantDO> list = tenantService.getTenantListByStatus(CommonStatusEnum.ENABLE.getStatus());
@ -57,6 +60,7 @@ public class TenantController {
@GetMapping("/get-by-website")
@PermitAll
@TenantIgnore
@Operation(summary = "使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息")
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
public CommonResult<TenantRespVO> getTenantByWebsite(@RequestParam("website") String website) {

View File

@ -273,16 +273,7 @@ yudao:
tenant: # 多租户相关配置项
enable: true
ignore-urls:
- /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
- /admin-api/system/tenant/get-by-website # 基于域名获取租户,不许带租户编号
- /admin-api/system/tenant/simple-list # 获取租户列表,不许带租户编号
- /admin-api/system/captcha/get # 获取图片验证码,和租户无关
- /admin-api/system/captcha/check # 校验图片验证码,和租户无关
- /admin-api/infra/file/*/get/** # 获取图片,和租户无关
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
- /admin-api/pay/notify/** # 支付回调通知,不携带租户编号
- /jmreport/* # 积木报表,无法携带租户编号
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号
ignore-tables:
- system_tenant
- system_tenant_package