定时任务由于在执行时不携带任何租户上下文信息,因此在调用需要租户识别的功能时,往往会抛出租户读取异常。为避免这种情况,应该在相关任务上使用 @IgnoreTenant
注解来跳过租户检查。
@IgnoreTenant
注解的功能public @interface IgnoreTenant {
/**
* 对事务生效,默认为开启
*/
boolean db() default true;
/**
* 对缓存生效,与全局缓存一致
* 全局缓存可通过{@link org.dromara.common.core.constant.GlobalConstants#GLOBAL_REDIS_KEY}或设置该参数为true
*/
boolean cache() default false;
}
@IgnoreTenant
注解提供了两个主要功能:
1. db(): 控制是否对数据库事务生效。默认开启,即忽略租户限制。
2. cache(): 控制是否对缓存生效。如果开启,效果与全局缓存一致。
在定时任务中可以使用 @IgnoreTenant
来跳过租户上下文的注入:
@Component
public class BizJobs {
/**
* 每分钟执行任务
*/
@IgnoreTenant
@EventListener
public void everyMinute(EveryMinuteJobEvent jobEvent) {
// 业务逻辑
}
}
IgnoreTenant#cache
不建议开启,主要因为租户缓存与忽略租户缓存的机制并不相同。如果需要一致的缓存处理行为,建议使用全局缓存,而非简单忽略租户差异。
在消息队列场景下,通常可以在发送消息时附带租户信息。为了确保消息消费端能正确处理租户,可以采用动态租户模式。以下是基于 ruoyi-common-amqp
依赖的实现方式。
使用 TenantMQMessage
类携带租户信息,通过 AmqpTransactionalTemplate
发送消息:
@Service
public class BizServiceImpl {
@Autowired
private AmqpTransactionalTemplate amqpTransactionalTemplate;
@Transactional(rollbackFor = Exception.class)
public void demo() {
TenantMQMessage message = new TenantMQMessage();
amqpTransactionalTemplate.convertAndSend(BizAmqpExchange.SEND_TEST, message).commit();
}
}
在此场景下,TenantMQMessage
是包含租户信息的业务消息类。通过这种方式,租户信息可以随消息一起传递。
@DynamicTenant
动态切换租户消费端使用 @DynamicTenant
注解,通过 SpEL 表达式动态获取消息中的租户信息:
@Component
public class BizReceiver {
@DynamicTenant(value = "#msg.tenantId")
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = BizAmqpExchange.SEND_TEST_QUEUE),
exchange = @Exchange(value = BizAmqpExchange.SEND_TEST, type = ExchangeTypes.FANOUT)
))
public void test(TenantMQMessage msg) {
// 消费逻辑
}
}
@DynamicTenant(value = "#msg.tenantId")
通过 SpEL 从消息对象中提取租户 ID,实现动态租户上下文的切换。
在非 Web 环境中,需要将租户操作封装到 TenantHelper.ignore
中执行,或在相关方法上使用 @DynamicTenant
注解以确保正确读取租户信息。
通过在定时任务和消息队列场景中合理使用 @IgnoreTenant
和 @DynamicTenant
注解,您可以轻松应对租户信息无法读取的问题,并确保系统多租户环境下的稳定性和一致性。
powered by kaifamiao