Commit 3443aa6f5f28577087c74e24812698cd802dd507

Authored by YunaiV
1 parent 2706463e

优化本地缓存的刷新实现,数据变更时,强制刷新

Showing 18 changed files with 288 additions and 341 deletions
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/aop/TenantIgnoreAspect.java
1 1 package cn.iocoder.yudao.framework.tenant.core.aop;
2 2  
3 3 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  4 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
4 5 import lombok.extern.slf4j.Slf4j;
5 6 import org.aspectj.lang.ProceedingJoinPoint;
6 7 import org.aspectj.lang.annotation.Around;
... ... @@ -11,6 +12,8 @@ import org.aspectj.lang.annotation.Aspect;
11 12 * 例如说,一个定时任务,读取所有数据,进行处理。
12 13 * 又例如说,读取所有数据,进行缓存。
13 14 *
  15 + * 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致
  16 + *
14 17 * @author 芋道源码
15 18 */
16 19 @Aspect
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/util/TenantUtils.java
... ... @@ -37,6 +37,22 @@ public class TenantUtils {
37 37 }
38 38  
39 39 /**
  40 + * 忽略租户,执行对应的逻辑
  41 + *
  42 + * @param runnable 逻辑
  43 + */
  44 + public static void executeIgnore(Runnable runnable) {
  45 + Boolean oldIgnore = TenantContextHolder.isIgnore();
  46 + try {
  47 + TenantContextHolder.setIgnore(true);
  48 + // 执行逻辑
  49 + runnable.run();
  50 + } finally {
  51 + TenantContextHolder.setIgnore(oldIgnore);
  52 + }
  53 + }
  54 +
  55 + /**
40 56 * 将多租户编号,添加到 header 中
41 57 *
42 58 * @param headers HTTP 请求 headers
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
1 1 package cn.iocoder.yudao.module.infra.service.file;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.io.resource.ResourceUtil;
5 4 import cn.hutool.core.util.IdUtil;
6 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
... ... @@ -20,7 +19,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
20 19 import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
21 20 import lombok.Getter;
22 21 import lombok.extern.slf4j.Slf4j;
23   -import org.springframework.context.annotation.Lazy;
24 22 import org.springframework.scheduling.annotation.Scheduled;
25 23 import org.springframework.stereotype.Service;
26 24 import org.springframework.transaction.annotation.Transactional;
... ... @@ -79,20 +77,36 @@ public class FileConfigServiceImpl implements FileConfigService {
79 77 @Resource
80 78 private Validator validator;
81 79  
82   - @Resource
83   - @Lazy // 注入自己,所以延迟加载
84   - private FileConfigService self;
85   -
86 80 @Override
87 81 @PostConstruct
88 82 public void initFileClients() {
89   - // 获取文件配置,如果有更新
90   - List<FileConfigDO> configs = loadFileConfigIfUpdate(maxUpdateTime);
91   - if (CollUtil.isEmpty(configs)) {
  83 + initLocalCacheIfUpdate(null);
  84 + }
  85 +
  86 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  87 + public void schedulePeriodicRefresh() {
  88 + initLocalCacheIfUpdate(this.maxUpdateTime);
  89 + }
  90 +
  91 + /**
  92 + * 刷新本地缓存
  93 + *
  94 + * @param maxUpdateTime 最大更新时间
  95 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  96 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
  97 + */
  98 + private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  99 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  100 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  101 + if (maxUpdateTime != null
  102 + && fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  103 + log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
92 104 return;
93 105 }
  106 + List<FileConfigDO> configs = fileConfigMapper.selectList();
  107 + log.info("[initLocalCacheIfUpdate][缓存文件配置,数量为:{}]", configs.size());
94 108  
95   - // 创建或更新支付 Client
  109 + // 第二步:构建缓存。创建或更新文件 Client
96 110 configs.forEach(config -> {
97 111 fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
98 112 // 如果是 master,进行设置
... ... @@ -101,35 +115,8 @@ public class FileConfigServiceImpl implements FileConfigService {
101 115 }
102 116 });
103 117  
104   - // 写入缓存
105   - maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
106   - log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size());
107   - }
108   -
109   - @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
110   - public void schedulePeriodicRefresh() {
111   - self.initFileClients();
112   - }
113   -
114   - /**
115   - * 如果文件配置发生变化,从数据库中获取最新的全量文件配置。
116   - * 如果未发生变化,则返回空
117   - *
118   - * @param maxUpdateTime 当前文件配置的最大更新时间
119   - * @return 文件配置列表
120   - */
121   - private List<FileConfigDO> loadFileConfigIfUpdate(LocalDateTime maxUpdateTime) {
122   - // 第一步,判断是否要更新。
123   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
124   - log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]");
125   - } else { // 判断数据库中是否有更新的文件配置
126   - if (fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
127   - return null;
128   - }
129   - log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]");
130   - }
131   - // 第二步,如果有更新,则从数据库加载所有文件配置
132   - return fileConfigMapper.selectList();
  118 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  119 + this.maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
133 120 }
134 121  
135 122 @Override
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelService.java
... ... @@ -22,7 +22,7 @@ public interface PayChannelService {
22 22 /**
23 23 * 初始化支付客户端
24 24 */
25   - void initPayClients();
  25 + void initLocalCache();
26 26  
27 27 /**
28 28 * 创建支付渠道
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java
1 1 package cn.iocoder.yudao.module.pay.service.merchant;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.util.ObjectUtil;
5 4 import cn.hutool.json.JSONUtil;
6 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
... ... @@ -10,7 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
10 9 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
11 10 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
12 11 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
13   -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
  12 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
14 13 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO;
15 14 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO;
16 15 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO;
... ... @@ -20,7 +19,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
20 19 import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper;
21 20 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
22 21 import lombok.extern.slf4j.Slf4j;
23   -import org.springframework.context.annotation.Lazy;
24 22 import org.springframework.scheduling.annotation.Scheduled;
25 23 import org.springframework.stereotype.Service;
26 24 import org.springframework.validation.annotation.Validated;
... ... @@ -66,53 +64,47 @@ public class PayChannelServiceImpl implements PayChannelService {
66 64 @Resource
67 65 private Validator validator;
68 66  
69   - @Resource
70   - @Lazy // 注入自己,所以延迟加载
71   - private PayChannelService self;
72   -
  67 + /**
  68 + * 初始化 {@link #payClientFactory} 缓存
  69 + */
73 70 @Override
74 71 @PostConstruct
75   - @TenantIgnore // 忽略自动化租户,全局初始化本地缓存
76   - public void initPayClients() {
77   - // 获取支付渠道,如果有更新
78   - List<PayChannelDO> payChannels = loadPayChannelIfUpdate(maxUpdateTime);
79   - if (CollUtil.isEmpty(payChannels)) {
80   - return;
81   - }
82   -
83   - // 创建或更新支付 Client
84   - payChannels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
85   - payChannel.getCode(), payChannel.getConfig()));
86   -
87   - // 写入缓存
88   - maxUpdateTime = CollectionUtils.getMaxValue(payChannels, PayChannelDO::getUpdateTime);
89   - log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size());
  72 + public void initLocalCache() {
  73 + initLocalCacheIfUpdate(null);
90 74 }
91 75  
92 76 @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
93 77 public void schedulePeriodicRefresh() {
94   - self.initPayClients();
  78 + initLocalCacheIfUpdate(this.maxUpdateTime);
95 79 }
96 80  
97 81 /**
98   - * 如果支付渠道发生变化,从数据库中获取最新的全量支付渠道。
99   - * 如果未发生变化,则返回空
  82 + * 刷新本地缓存
100 83 *
101   - * @param maxUpdateTime 当前支付渠道的最大更新时间
102   - * @return 支付渠道列表
  84 + * @param maxUpdateTime 最大更新时间
  85 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  86 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
103 87 */
104   - private List<PayChannelDO> loadPayChannelIfUpdate(LocalDateTime maxUpdateTime) {
105   - // 第一步,判断是否要更新。
106   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
107   - log.info("[loadPayChannelIfUpdate][首次加载全量支付渠道]");
108   - } else { // 判断数据库中是否有更新的支付渠道
109   - if (channelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
110   - return null;
  88 + private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  89 + // 注意:忽略自动多租户,因为要全局初始化缓存
  90 + TenantUtils.executeIgnore(() -> {
  91 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  92 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  93 + if (maxUpdateTime != null
  94 + && channelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  95 + log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
  96 + return;
111 97 }
112   - log.info("[loadPayChannelIfUpdate][增量加载全量支付渠道]");
113   - }
114   - // 第二步,如果有更新,则从数据库加载所有支付渠道
115   - return channelMapper.selectList();
  98 + List<PayChannelDO> channels = channelMapper.selectList();
  99 + log.info("[initLocalCacheIfUpdate][缓存支付渠道,数量为:{}]", channels.size());
  100 +
  101 + // 第二步:构建缓存。创建或更新支付 Client
  102 + channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
  103 + payChannel.getCode(), payChannel.getConfig()));
  104 +
  105 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  106 + this.maxUpdateTime = CollectionUtils.getMaxValue(channels, PayChannelDO::getUpdateTime);
  107 + });
116 108 }
117 109  
118 110 @Override
... ...
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceTest.java
... ... @@ -49,7 +49,6 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
49 49 @Test
50 50 public void testCreateWechatVersion2Channel_success() {
51 51 // 准备参数
52   -
53 52 WXPayClientConfig v2Config = getV2Config();
54 53 PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
55 54 o.setCode(PayChannelEnum.WX_PUB.getCode());
... ...
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
... ... @@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS &quot;pay_channel&quot; (
43 43 "updater" varchar(64) NULL DEFAULT '',
44 44 "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
45 45 "deleted" bit(1) NOT NULL DEFAULT FALSE,
  46 + "tenant_id" bigint not null default '0',
46 47 PRIMARY KEY ("id")
47 48 ) COMMENT = '支付渠道';
48 49  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsChannelRefreshConsumer.java
... ... @@ -23,7 +23,7 @@ public class SmsChannelRefreshConsumer extends AbstractChannelMessageListener&lt;Sm
23 23 @Override
24 24 public void onMessage(SmsChannelRefreshMessage message) {
25 25 log.info("[onMessage][收到 SmsChannel 刷新消息]");
26   - smsChannelService.initSmsClients();
  26 + smsChannelService.initLocalCache();
27 27 }
28 28  
29 29 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
... ... @@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 5 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
6 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7   -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
  7 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 8 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
9 9 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
10 10 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
... ... @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap;
17 17 import com.google.common.collect.ImmutableMultimap;
18 18 import com.google.common.collect.Multimap;
19 19 import lombok.extern.slf4j.Slf4j;
20   -import org.springframework.context.annotation.Lazy;
21 20 import org.springframework.scheduling.annotation.Scheduled;
22 21 import org.springframework.stereotype.Service;
23 22 import org.springframework.validation.annotation.Validated;
... ... @@ -73,58 +72,55 @@ public class DeptServiceImpl implements DeptService {
73 72 @Resource
74 73 private DeptProducer deptProducer;
75 74  
76   - @Resource
77   - @Lazy // 注入自己,所以延迟加载
78   - private DeptService self;
79   -
  75 + /**
  76 + * 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
  77 + */
80 78 @Override
81 79 @PostConstruct
82   - @TenantIgnore // 初始化缓存,无需租户过滤
83 80 public synchronized void initLocalCache() {
84   - // 获取部门列表,如果有更新
85   - List<DeptDO> deptList = loadDeptIfUpdate(maxUpdateTime);
86   - if (CollUtil.isEmpty(deptList)) {
87   - return;
88   - }
89   -
90   - // 构建缓存
91   - ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
92   - ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
93   - deptList.forEach(sysRoleDO -> {
94   - builder.put(sysRoleDO.getId(), sysRoleDO);
95   - parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
96   - });
97   - // 设置缓存
98   - deptCache = builder.build();
99   - parentDeptCache = parentBuilder.build();
100   - maxUpdateTime = CollectionUtils.getMaxValue(deptList, DeptDO::getUpdateTime);
101   - log.info("[initLocalCache][初始化 Dept 数量为 {}]", deptList.size());
  81 + initLocalCacheIfUpdate(null);
102 82 }
103 83  
104 84 @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
105 85 public void schedulePeriodicRefresh() {
106   - self.initLocalCache();
  86 + initLocalCacheIfUpdate(this.maxUpdateTime);
107 87 }
108 88  
109 89 /**
110   - * 如果部门发生变化,从数据库中获取最新的全量部门。
111   - * 如果未发生变化,则返回空
  90 + * 刷新本地缓存
112 91 *
113   - * @param maxUpdateTime 当前部门的最大更新时间
114   - * @return 部门列表
  92 + * @param maxUpdateTime 最大更新时间
  93 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  94 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
115 95 */
116   - protected List<DeptDO> loadDeptIfUpdate(LocalDateTime maxUpdateTime) {
117   - // 第一步,判断是否要更新。
118   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
119   - log.info("[loadDeptIfUpdate][首次加载全量部门]");
120   - } else { // 判断数据库中是否有更新的部门
121   - if (deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
122   - return null;
  96 + private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  97 + // 注意:忽略自动多租户,因为要全局初始化缓存
  98 + TenantUtils.executeIgnore(() -> {
  99 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  100 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  101 + if (maxUpdateTime != null
  102 + && deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  103 + log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
  104 + return;
123 105 }
124   - log.info("[loadDeptIfUpdate][增量加载全量部门]");
125   - }
126   - // 第二步,如果有更新,则从数据库加载所有部门
127   - return deptMapper.selectList();
  106 + List<DeptDO> depts = deptMapper.selectList();
  107 + log.info("[initLocalCacheIfUpdate][缓存部门,数量为:{}]", depts.size());
  108 +
  109 + // 第二步:构建缓存。创建或更新支付 Client
  110 + // 构建缓存
  111 + ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
  112 + ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
  113 + depts.forEach(sysRoleDO -> {
  114 + builder.put(sysRoleDO.getId(), sysRoleDO);
  115 + parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
  116 + });
  117 + // 设置缓存
  118 + deptCache = builder.build();
  119 + parentDeptCache = parentBuilder.build();
  120 +
  121 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  122 + this.maxUpdateTime = CollectionUtils.getMaxValue(depts, DeptDO::getUpdateTime);
  123 + });
128 124 }
129 125  
130 126 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
... ... @@ -24,7 +24,9 @@ import org.springframework.validation.annotation.Validated;
24 24 import javax.annotation.PostConstruct;
25 25 import javax.annotation.Resource;
26 26 import java.time.LocalDateTime;
27   -import java.util.*;
  27 +import java.util.Collection;
  28 +import java.util.List;
  29 +import java.util.Map;
28 30  
29 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30 32 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
... ... @@ -74,42 +76,37 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
74 76 @Override
75 77 @PostConstruct
76 78 public void initLocalCache() {
77   - // 获取客户端列表,如果有更新
78   - List<OAuth2ClientDO> tenantList = loadOAuth2ClientIfUpdate(maxUpdateTime);
79   - if (CollUtil.isEmpty(tenantList)) {
80   - return;
81   - }
82   -
83   - // 写入缓存
84   - clientCache = convertMap(tenantList, OAuth2ClientDO::getClientId);
85   - maxUpdateTime = getMaxValue(tenantList, OAuth2ClientDO::getUpdateTime);
86   - log.info("[initLocalCache][初始化 OAuth2Client 数量为 {}]", tenantList.size());
  79 + initLocalCacheIfUpdate(null);
87 80 }
88 81  
89 82 @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
90 83 public void schedulePeriodicRefresh() {
91   - initLocalCache();
  84 + initLocalCacheIfUpdate(this.maxUpdateTime);
92 85 }
93 86  
94 87 /**
95   - * 如果客户端发生变化,从数据库中获取最新的全量客户端。
96   - * 如果未发生变化,则返回空
  88 + * 刷新本地缓存
97 89 *
98   - * @param maxUpdateTime 当前客户端的最大更新时间
99   - * @return 客户端列表
  90 + * @param maxUpdateTime 最大更新时间
  91 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  92 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
100 93 */
101   - private List<OAuth2ClientDO> loadOAuth2ClientIfUpdate(LocalDateTime maxUpdateTime) {
102   - // 第一步,判断是否要更新。
103   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
104   - log.info("[loadOAuth2ClientIfUpdate][首次加载全量客户端]");
105   - } else { // 判断数据库中是否有更新的客户端
106   - if (oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
107   - return null;
108   - }
109   - log.info("[loadOAuth2ClientIfUpdate][增量加载全量客户端]");
  94 + private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  95 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  96 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  97 + if (maxUpdateTime != null
  98 + && oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  99 + log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
  100 + return;
110 101 }
111   - // 第二步,如果有更新,则从数据库加载所有客户端
112   - return oauth2ClientMapper.selectList();
  102 + List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
  103 + log.info("[initLocalCacheIfUpdate][缓存 OAuth2 客户端,数量为:{}]", clients.size());
  104 +
  105 + // 第二步:构建缓存。
  106 + clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
  107 +
  108 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  109 + this.maxUpdateTime = getMaxValue(clients, OAuth2ClientDO::getUpdateTime);
113 110 }
114 111  
115 112 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
... ... @@ -102,6 +102,7 @@ public class MenuServiceImpl implements MenuService {
102 102 * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
103 103 */
104 104 private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  105 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
105 106 // 如果没有增量的数据变化,则不进行本地缓存的刷新
106 107 if (maxUpdateTime != null
107 108 && menuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
... ... @@ -111,7 +112,7 @@ public class MenuServiceImpl implements MenuService {
111 112 List<MenuDO> menuList = menuMapper.selectList();
112 113 log.info("[initLocalCacheIfUpdate][缓存菜单,数量为:{}]", menuList.size());
113 114  
114   - // 构建缓存
  115 + // 第二步:构建缓存。
115 116 ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
116 117 ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
117 118 menuList.forEach(menuDO -> {
... ... @@ -123,7 +124,7 @@ public class MenuServiceImpl implements MenuService {
123 124 menuCache = menuCacheBuilder.build();
124 125 permissionMenuCache = permMenuCacheBuilder.build();
125 126  
126   - // 设置最新的 maxUpdateTime,用于下次的增量判断
  127 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
127 128 this.maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
128 129 }
129 130  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
9 9 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
10 10 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
11 11 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
  12 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 13 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
13 14 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
14 15 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
... ... @@ -31,7 +32,6 @@ import com.google.common.collect.Sets;
31 32 import lombok.Getter;
32 33 import lombok.Setter;
33 34 import lombok.extern.slf4j.Slf4j;
34   -import org.springframework.context.annotation.Lazy;
35 35 import org.springframework.scheduling.annotation.Scheduled;
36 36 import org.springframework.stereotype.Service;
37 37 import org.springframework.transaction.annotation.Transactional;
... ... @@ -126,106 +126,84 @@ public class PermissionServiceImpl implements PermissionService {
126 126 @Resource
127 127 private PermissionProducer permissionProducer;
128 128  
129   - @Resource
130   - @Lazy // 注入自己,所以延迟加载
131   - private PermissionService self;
132   -
133 129 @Override
134 130 @PostConstruct
135   - @TenantIgnore // 初始化缓存,无需租户过滤
136 131 public void initLocalCache() {
137   - initUserRoleLocalCache();
138   - initRoleMenuLocalCache();
139   - }
140   -
141   - /**
142   - * 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存
143   - */
144   - @VisibleForTesting
145   - void initRoleMenuLocalCache() {
146   - // 获取角色与菜单的关联列表,如果有更新
147   - List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(roleMenuMaxUpdateTime);
148   - if (CollUtil.isEmpty(roleMenuList)) {
149   - return;
150   - }
151   -
152   - // 初始化 roleMenuCache 和 menuRoleCache 缓存
153   - ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
154   - ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
155   - roleMenuList.forEach(roleMenuDO -> {
156   - roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
157   - menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
158   - });
159   - roleMenuCache = roleMenuCacheBuilder.build();
160   - menuRoleCache = menuRoleCacheBuilder.build();
161   - roleMenuMaxUpdateTime = getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime);
162   - log.info("[initRoleMenuLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
163   - }
164   -
165   - /**
166   - * 初始化 {@link #userRoleCache} 缓存
167   - */
168   - @VisibleForTesting
169   - void initUserRoleLocalCache() {
170   - // 获取用户与角色的关联列表,如果有更新
171   - List<UserRoleDO> userRoleList = loadUserRoleIfUpdate(userRoleMaxUpdateTime);
172   - if (CollUtil.isEmpty(userRoleList)) {
173   - return;
174   - }
175   -
176   - // 初始化 userRoleCache 缓存
177   - ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
178   - userRoleList.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
179   - userRoleCache = CollectionUtils.convertMultiMap2(userRoleList, UserRoleDO::getUserId, UserRoleDO::getRoleId);
180   - userRoleMaxUpdateTime = getMaxValue(userRoleList, UserRoleDO::getUpdateTime);
181   - log.info("[initUserRoleLocalCache][初始化用户与角色的关联数量为 {}]", userRoleList.size());
  132 + initLocalCacheIfUpdateForRoleMenu(null);
  133 + initLocalCacheIfUpdateForUserRole(null);
182 134 }
183 135  
184 136 @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
185 137 public void schedulePeriodicRefresh() {
186   - self.initLocalCache();
  138 + initLocalCacheIfUpdateForRoleMenu(this.roleMenuMaxUpdateTime);
  139 + initLocalCacheIfUpdateForUserRole(this.userRoleMaxUpdateTime);
187 140 }
188 141  
189 142 /**
190   - * 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。
191   - * 如果未发生变化,则返回空
  143 + * 刷新 RoleMenu 本地缓存
192 144 *
193   - * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
194   - * @return 角色与菜单的关联列表
  145 + * @param maxUpdateTime 最大更新时间
  146 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  147 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
195 148 */
196   - protected List<RoleMenuDO> loadRoleMenuIfUpdate(LocalDateTime maxUpdateTime) {
197   - // 第一步,判断是否要更新。
198   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
199   - log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
200   - } else { // 判断数据库中是否有更新的角色与菜单的关联
201   - if (roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
202   - return null;
  149 + @VisibleForTesting
  150 + void initLocalCacheIfUpdateForRoleMenu(LocalDateTime maxUpdateTime) {
  151 + // 注意:忽略自动多租户,因为要全局初始化缓存
  152 + TenantUtils.executeIgnore(() -> {
  153 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  154 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  155 + if (maxUpdateTime != null
  156 + && roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  157 + log.info("[initLocalCacheIfUpdateForRoleMenu][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
  158 + return;
203 159 }
204   - log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]");
205   - }
206   - // 第二步,如果有更新,则从数据库加载所有角色与菜单的关联
207   - return roleMenuMapper.selectList();
  160 + List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
  161 + log.info("[initLocalCacheIfUpdateForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
  162 +
  163 + // 第二步:构建缓存。
  164 + ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
  165 + ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
  166 + roleMenus.forEach(roleMenuDO -> {
  167 + roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
  168 + menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
  169 + });
  170 + roleMenuCache = roleMenuCacheBuilder.build();
  171 + menuRoleCache = menuRoleCacheBuilder.build();
  172 +
  173 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  174 + this.roleMenuMaxUpdateTime = getMaxValue(roleMenus, RoleMenuDO::getUpdateTime);
  175 + });
208 176 }
209 177  
210 178 /**
211   - * 如果用户与角色的关联发生变化,从数据库中获取最新的全量用户与角色的关联。
212   - * 如果未发生变化,则返回空
  179 + * 刷新 UserRole 本地缓存
213 180 *
214   - * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
215   - * @return 角色与菜单的关联列表
  181 + * @param maxUpdateTime 最大更新时间
  182 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  183 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
216 184 */
217   - protected List<UserRoleDO> loadUserRoleIfUpdate(LocalDateTime maxUpdateTime) {
218   - // 第一步,判断是否要更新。
219   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
220   - log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
221   - } else { // 判断数据库中是否有更新的用户与角色的关联
222   - if (userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
223   - return null;
  185 + @VisibleForTesting
  186 + void initLocalCacheIfUpdateForUserRole(LocalDateTime maxUpdateTime) {
  187 + // 注意:忽略自动多租户,因为要全局初始化缓存
  188 + TenantUtils.executeIgnore(() -> {
  189 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  190 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  191 + if (maxUpdateTime != null
  192 + && userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  193 + log.info("[initLocalCacheIfUpdateForUserRole][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
  194 + return;
224 195 }
225   - log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
226   - }
227   - // 第二步,如果有更新,则从数据库加载所有用户与角色的关联
228   - return userRoleMapper.selectList();
  196 + List<UserRoleDO> userRoles = userRoleMapper.selectList();
  197 + log.info("[initLocalCacheIfUpdateForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
  198 +
  199 + // 第二步:构建缓存。
  200 + ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
  201 + userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
  202 + userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
  203 +
  204 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  205 + this.userRoleMaxUpdateTime = getMaxValue(userRoles, UserRoleDO::getUpdateTime);
  206 + });
229 207 }
230 208  
231 209 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
... ... @@ -6,8 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
6 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 8 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
9   -import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
10   -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
  9 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
11 10 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
12 11 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
13 12 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
... ... @@ -15,13 +14,13 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUp
15 14 import cn.iocoder.yudao.module.system.convert.permission.RoleConvert;
16 15 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
17 16 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
  17 +import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
18 18 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
19 19 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
20 20 import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
21 21 import com.google.common.annotations.VisibleForTesting;
22 22 import lombok.Getter;
23 23 import lombok.extern.slf4j.Slf4j;
24   -import org.springframework.context.annotation.Lazy;
25 24 import org.springframework.lang.Nullable;
26 25 import org.springframework.scheduling.annotation.Scheduled;
27 26 import org.springframework.stereotype.Service;
... ... @@ -77,53 +76,46 @@ public class RoleServiceImpl implements RoleService {
77 76 @Resource
78 77 private RoleProducer roleProducer;
79 78  
80   - @Resource
81   - @Lazy // 注入自己,所以延迟加载
82   - private RoleService self;
83   -
84 79 /**
85 80 * 初始化 {@link #roleCache} 缓存
86 81 */
87 82 @Override
88 83 @PostConstruct
89   - @TenantIgnore // 忽略自动多租户,全局初始化缓存
90 84 public void initLocalCache() {
91   - // 获取角色列表,如果有更新
92   - List<RoleDO> roleList = loadRoleIfUpdate(maxUpdateTime);
93   - if (CollUtil.isEmpty(roleList)) {
94   - return;
95   - }
96   -
97   - // 写入缓存
98   - roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
99   - maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
100   - log.info("[initLocalCache][初始化 Role 数量为 {}]", roleList.size());
  85 + initLocalCacheIfUpdate(null);
101 86 }
102 87  
103 88 @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
104 89 public void schedulePeriodicRefresh() {
105   - self.initLocalCache();
  90 + initLocalCacheIfUpdate(this.maxUpdateTime);
106 91 }
107 92  
108 93 /**
109   - * 如果角色发生变化,从数据库中获取最新的全量角色。
110   - * 如果未发生变化,则返回空
  94 + * 刷新本地缓存
111 95 *
112   - * @param maxUpdateTime 当前角色的最大更新时间
113   - * @return 角色列表
  96 + * @param maxUpdateTime 最大更新时间
  97 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  98 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
114 99 */
115   - private List<RoleDO> loadRoleIfUpdate(LocalDateTime maxUpdateTime) {
116   - // 第一步,判断是否要更新。
117   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
118   - log.info("[loadRoleIfUpdate][首次加载全量角色]");
119   - } else { // 判断数据库中是否有更新的角色
120   - if (roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
121   - return null;
  100 + private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  101 + // 注意:忽略自动多租户,因为要全局初始化缓存
  102 + TenantUtils.executeIgnore(() -> {
  103 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  104 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  105 + if (maxUpdateTime != null
  106 + && roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  107 + log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
  108 + return;
122 109 }
123   - log.info("[loadRoleIfUpdate][增量加载全量角色]");
124   - }
125   - // 第二步,如果有更新,则从数据库加载所有角色
126   - return roleMapper.selectList();
  110 + List<RoleDO> roleList = roleMapper.selectList();
  111 + log.info("[initLocalCacheIfUpdate][缓存角色,数量为:{}]", roleList.size());
  112 +
  113 + // 第二步:构建缓存。
  114 + roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
  115 +
  116 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  117 + this.maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
  118 + });
127 119 }
128 120  
129 121 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
... ... @@ -84,21 +84,42 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
84 84 @Override
85 85 @PostConstruct
86 86 public void initLocalCache() {
87   - // 获取敏感词列表,如果有更新
88   - List<SensitiveWordDO> sensitiveWordList = loadSensitiveWordIfUpdate(maxUpdateTime);
89   - if (CollUtil.isEmpty(sensitiveWordList)) {
  87 + initLocalCacheIfUpdate(null);
  88 + }
  89 +
  90 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  91 + public void schedulePeriodicRefresh() {
  92 + initLocalCacheIfUpdate(this.maxUpdateTime);
  93 + }
  94 +
  95 + /**
  96 + * 刷新本地缓存
  97 + *
  98 + * @param maxUpdateTime 最大更新时间
  99 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  100 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
  101 + */
  102 + private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  103 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  104 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  105 + if (maxUpdateTime != null
  106 + && sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  107 + log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
90 108 return;
91 109 }
  110 + List<SensitiveWordDO> sensitiveWords = sensitiveWordMapper.selectList();
  111 + log.info("[initLocalCacheIfUpdate][缓存敏感词,数量为:{}]", sensitiveWords.size());
92 112  
  113 + // 第二步:构建缓存。
93 114 // 写入 sensitiveWordTagsCache 缓存
94 115 Set<String> tags = new HashSet<>();
95   - sensitiveWordList.forEach(word -> tags.addAll(word.getTags()));
  116 + sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
96 117 sensitiveWordTagsCache = tags;
97 118 // 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
98   - initSensitiveWordTrie(sensitiveWordList);
99   - // 写入 maxUpdateTime 最大更新时间
100   - maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWordList, SensitiveWordDO::getUpdateTime);
101   - log.info("[initLocalCache][初始化 敏感词 数量为 {}]", sensitiveWordList.size());
  119 + initSensitiveWordTrie(sensitiveWords);
  120 +
  121 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  122 + this.maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWords, SensitiveWordDO::getUpdateTime);
102 123 }
103 124  
104 125 private void initSensitiveWordTrie(List<SensitiveWordDO> wordDOs) {
... ... @@ -122,33 +143,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
122 143 this.tagSensitiveWordTries = tagSensitiveWordTries;
123 144 }
124 145  
125   - @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
126   - public void schedulePeriodicRefresh() {
127   - initLocalCache();
128   - }
129   -
130   - /**
131   - * 如果敏感词发生变化,从数据库中获取最新的全量敏感词。
132   - * 如果未发生变化,则返回空
133   - *
134   - * @param maxUpdateTime 当前敏感词的最大更新时间
135   - * @return 敏感词列表
136   - */
137   - private List<SensitiveWordDO> loadSensitiveWordIfUpdate(LocalDateTime maxUpdateTime) {
138   - // 第一步,判断是否要更新。
139   - // 如果更新时间为空,说明 DB 一定有新数据
140   - if (maxUpdateTime == null) {
141   - log.info("[loadSensitiveWordIfUpdate][首次加载全量敏感词]");
142   - } else { // 判断数据库中是否有更新的敏感词
143   - if (sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
144   - return null;
145   - }
146   - log.info("[loadSensitiveWordIfUpdate][增量加载全量敏感词]");
147   - }
148   - // 第二步,如果有更新,则从数据库加载所有敏感词
149   - return sensitiveWordMapper.selectList();
150   - }
151   -
152 146 @Override
153 147 public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
154 148 // 校验唯一性
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelService.java
... ... @@ -21,7 +21,7 @@ public interface SmsChannelService {
21 21 /**
22 22 * 初始化短信客户端
23 23 */
24   - void initSmsClients();
  24 + void initLocalCache();
25 25  
26 26 /**
27 27 * 创建短信渠道
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.sms;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 4 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
7 5 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
8 6 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
... ... @@ -23,14 +21,14 @@ import java.util.Collection;
23 21 import java.util.List;
24 22  
25 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
26 25 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
27 26 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
28 27  
29 28 /**
30   - * 短信渠道Service实现类
  29 + * 短信渠道 Service 实现类
31 30 *
32 31 * @author zzf
33   - * @date 2021/1/25 9:25
34 32 */
35 33 @Service
36 34 @Slf4j
... ... @@ -61,46 +59,39 @@ public class SmsChannelServiceImpl implements SmsChannelService {
61 59  
62 60 @Override
63 61 @PostConstruct
64   - public void initSmsClients() {
65   - // 获取短信渠道,如果有更新
66   - List<SmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
67   - if (CollUtil.isEmpty(smsChannels)) {
68   - return;
69   - }
70   -
71   - // 创建或更新短信 Client
72   - List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(smsChannels);
73   - propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
74   -
75   - // 写入缓存
76   - maxUpdateTime = CollectionUtils.getMaxValue(smsChannels, SmsChannelDO::getUpdateTime);
77   - log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
  62 + public void initLocalCache() {
  63 + initLocalCacheIfUpdate(null);
78 64 }
79 65  
80 66 @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
81 67 public void schedulePeriodicRefresh() {
82   - initSmsClients();
  68 + initLocalCacheIfUpdate(this.maxUpdateTime);
83 69 }
84 70  
85 71 /**
86   - * 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
87   - * 如果未发生变化,则返回空
  72 + * 刷新本地缓存
88 73 *
89   - * @param maxUpdateTime 当前短信渠道的最大更新时间
90   - * @return 短信渠道列表
  74 + * @param maxUpdateTime 最大更新时间
  75 + * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
  76 + * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
91 77 */
92   - private List<SmsChannelDO> loadSmsChannelIfUpdate(LocalDateTime maxUpdateTime) {
93   - // 第一步,判断是否要更新。
94   - if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
95   - log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
96   - } else { // 判断数据库中是否有更新的短信渠道
97   - if (smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
98   - return null;
99   - }
100   - log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
  78 + private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
  79 + // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
  80 + // 如果没有增量的数据变化,则不进行本地缓存的刷新
  81 + if (maxUpdateTime != null
  82 + && smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  83 + log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
  84 + return;
101 85 }
102   - // 第二步,如果有更新,则从数据库加载所有短信渠道
103   - return smsChannelMapper.selectList();
  86 + List<SmsChannelDO> channels = smsChannelMapper.selectList();
  87 + log.info("[initLocalCacheIfUpdate][缓存短信渠道,数量为:{}]", channels.size());
  88 +
  89 + // 第二步:构建缓存。创建或更新短信 Client
  90 + List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
  91 + propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
  92 +
  93 + // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
  94 + this.maxUpdateTime = getMaxValue(channels, SmsChannelDO::getUpdateTime);
104 95 }
105 96  
106 97 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
... ... @@ -71,7 +71,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
71 71 private PermissionProducer permissionProducer;
72 72  
73 73 @Test
74   - public void testInitRoleMenuLocalCache() {
  74 + public void testInitLocalCacheIfUpdateForRoleMenu() {
75 75 // mock 数据
76 76 RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
77 77 roleMenuMapper.insert(roleMenuDO01);
... ... @@ -79,7 +79,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
79 79 roleMenuMapper.insert(roleMenuDO02);
80 80  
81 81 // 调用
82   - permissionService.initRoleMenuLocalCache();
  82 + permissionService.initLocalCacheIfUpdateForRoleMenu(null);
83 83 // 断言 roleMenuCache 缓存
84 84 assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
85 85 assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
... ... @@ -93,7 +93,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
93 93 }
94 94  
95 95 @Test
96   - public void testInitUserRoleLocalCache() {
  96 + public void testInitLocalCacheIfUpdateForUserRole() {
97 97 // mock 数据
98 98 UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
99 99 userRoleMapper.insert(userRoleDO01);
... ... @@ -101,7 +101,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
101 101 userRoleMapper.insert(roleMenuDO02);
102 102  
103 103 // 调用
104   - permissionService.initUserRoleLocalCache();
  104 + permissionService.initLocalCacheIfUpdateForUserRole(null);
105 105 // 断言 roleMenuCache 缓存
106 106 assertEquals(1, permissionService.getUserRoleCache().size());
107 107 assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java
... ... @@ -57,7 +57,7 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
57 57 smsChannelMapper.insert(smsChannelDO02);
58 58  
59 59 // 调用
60   - smsChannelService.initSmsClients();
  60 + smsChannelService.initLocalCache();
61 61 // 校验 maxUpdateTime 属性
62 62 LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
63 63 assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);
... ...