Browse Source

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

YunaiV 2 years ago
parent
commit
3443aa6f5f
18 changed files with 288 additions and 341 deletions
  1. 3 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/aop/TenantIgnoreAspect.java
  2. 16 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/util/TenantUtils.java
  3. 26 39
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
  4. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelService.java
  5. 30 38
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java
  6. 0 1
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceTest.java
  7. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
  8. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsChannelRefreshConsumer.java
  9. 37 41
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
  10. 24 27
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
  11. 3 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
  12. 60 82
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
  13. 26 34
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
  14. 29 35
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
  15. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelService.java
  16. 25 34
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
  17. 4 4
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
  18. 1 1
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java

+ 3 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/aop/TenantIgnoreAspect.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.framework.tenant.core.aop;
 
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
@@ -11,6 +12,8 @@ import org.aspectj.lang.annotation.Aspect;
  * 例如说,一个定时任务,读取所有数据,进行处理。
  * 又例如说,读取所有数据,进行缓存。
  *
+ * 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致
+ *
  * @author 芋道源码
  */
 @Aspect

+ 16 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/util/TenantUtils.java

@@ -36,6 +36,22 @@ public class TenantUtils {
         }
     }
 
+    /**
+     * 忽略租户,执行对应的逻辑
+     *
+     * @param runnable 逻辑
+     */
+    public static void executeIgnore(Runnable runnable) {
+        Boolean oldIgnore = TenantContextHolder.isIgnore();
+        try {
+            TenantContextHolder.setIgnore(true);
+            // 执行逻辑
+            runnable.run();
+        } finally {
+            TenantContextHolder.setIgnore(oldIgnore);
+        }
+    }
+
     /**
      * 将多租户编号,添加到 header 中
      *

+ 26 - 39
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.infra.service.file;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.resource.ResourceUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -20,7 +19,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
 import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -79,20 +77,36 @@ public class FileConfigServiceImpl implements FileConfigService {
     @Resource
     private Validator validator;
 
-    @Resource
-    @Lazy // 注入自己,所以延迟加载
-    private FileConfigService self;
-
     @Override
     @PostConstruct
     public void initFileClients() {
-        // 获取文件配置,如果有更新
-        List<FileConfigDO> configs = loadFileConfigIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(configs)) {
+        initLocalCacheIfUpdate(null);
+    }
+
+    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+    public void schedulePeriodicRefresh() {
+        initLocalCacheIfUpdate(this.maxUpdateTime);
+    }
+
+    /**
+     * 刷新本地缓存
+     *
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
+     */
+    private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+        // 如果没有增量的数据变化,则不进行本地缓存的刷新
+        if (maxUpdateTime != null
+                && fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+            log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
             return;
         }
+        List<FileConfigDO> configs = fileConfigMapper.selectList();
+        log.info("[initLocalCacheIfUpdate][缓存文件配置,数量为:{}]", configs.size());
 
-        // 创建或更新支付 Client
+        // 第二步:构建缓存。创建或更新文件 Client
         configs.forEach(config -> {
             fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
             // 如果是 master,进行设置
@@ -101,35 +115,8 @@ public class FileConfigServiceImpl implements FileConfigService {
             }
         });
 
-        // 写入缓存
-        maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
-        log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size());
-    }
-
-    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
-    public void schedulePeriodicRefresh() {
-        self.initFileClients();
-    }
-
-    /**
-     * 如果文件配置发生变化,从数据库中获取最新的全量文件配置。
-     * 如果未发生变化,则返回空
-     *
-     * @param maxUpdateTime 当前文件配置的最大更新时间
-     * @return 文件配置列表
-     */
-    private List<FileConfigDO> loadFileConfigIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]");
-        } else { // 判断数据库中是否有更新的文件配置
-            if (fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
-            }
-            log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有文件配置
-        return fileConfigMapper.selectList();
+        // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+        this.maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
     }
 
     @Override

+ 1 - 1
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 {
     /**
      * 初始化支付客户端
      */
-    void initPayClients();
+    void initLocalCache();
 
     /**
      * 创建支付渠道

+ 30 - 38
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.pay.service.merchant;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -10,7 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
-import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO;
 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;
 import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper;
 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -66,53 +64,47 @@ public class PayChannelServiceImpl implements PayChannelService {
     @Resource
     private Validator validator;
 
-    @Resource
-    @Lazy // 注入自己,所以延迟加载
-    private PayChannelService self;
-
+    /**
+     * 初始化 {@link #payClientFactory} 缓存
+     */
     @Override
     @PostConstruct
-    @TenantIgnore // 忽略自动化租户,全局初始化本地缓存
-    public void initPayClients() {
-        // 获取支付渠道,如果有更新
-        List<PayChannelDO> payChannels = loadPayChannelIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(payChannels)) {
-            return;
-        }
-
-        // 创建或更新支付 Client
-        payChannels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
-                payChannel.getCode(), payChannel.getConfig()));
-
-        // 写入缓存
-        maxUpdateTime = CollectionUtils.getMaxValue(payChannels, PayChannelDO::getUpdateTime);
-        log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size());
+    public void initLocalCache() {
+        initLocalCacheIfUpdate(null);
     }
 
     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
     public void schedulePeriodicRefresh() {
-        self.initPayClients();
+        initLocalCacheIfUpdate(this.maxUpdateTime);
     }
 
     /**
-     * 如果支付渠道发生变化,从数据库中获取最新的全量支付渠道。
-     * 如果未发生变化,则返回空
+     * 刷新本地缓存
      *
-     * @param maxUpdateTime 当前支付渠道的最大更新时间
-     * @return 支付渠道列表
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
-    private List<PayChannelDO> loadPayChannelIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadPayChannelIfUpdate][首次加载全量支付渠道]");
-        } else { // 判断数据库中是否有更新的支付渠道
-            if (channelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
+    private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 注意:忽略自动多租户,因为要全局初始化缓存
+        TenantUtils.executeIgnore(() -> {
+            // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+            // 如果没有增量的数据变化,则不进行本地缓存的刷新
+            if (maxUpdateTime != null
+                    && channelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+                log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
+                return;
             }
-            log.info("[loadPayChannelIfUpdate][增量加载全量支付渠道]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有支付渠道
-        return channelMapper.selectList();
+            List<PayChannelDO> channels = channelMapper.selectList();
+            log.info("[initLocalCacheIfUpdate][缓存支付渠道,数量为:{}]", channels.size());
+
+            // 第二步:构建缓存。创建或更新支付 Client
+            channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
+                    payChannel.getCode(), payChannel.getConfig()));
+
+            // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+            this.maxUpdateTime = CollectionUtils.getMaxValue(channels, PayChannelDO::getUpdateTime);
+        });
     }
 
     @Override

+ 0 - 1
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 {
     @Test
     public void testCreateWechatVersion2Channel_success() {
         // 准备参数
-
         WXPayClientConfig v2Config = getV2Config();
         PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
             o.setCode(PayChannelEnum.WX_PUB.getCode());

+ 1 - 0
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql

@@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS "pay_channel" (
     "updater"     varchar(64)    NULL     DEFAULT '',
     "update_time" datetime       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     "deleted"     bit(1)         NOT NULL DEFAULT FALSE,
+    "tenant_id" bigint not null default  '0',
     PRIMARY KEY ("id")
 ) COMMENT = '支付渠道';
 

+ 1 - 1
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<Sm
     @Override
     public void onMessage(SmsChannelRefreshMessage message) {
         log.info("[onMessage][收到 SmsChannel 刷新消息]");
-        smsChannelService.initSmsClients();
+        smsChannelService.initLocalCache();
     }
 
 }

+ 37 - 41
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;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
@@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Multimap;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -73,58 +72,55 @@ public class DeptServiceImpl implements DeptService {
     @Resource
     private DeptProducer deptProducer;
 
-    @Resource
-    @Lazy // 注入自己,所以延迟加载
-    private DeptService self;
-
+    /**
+     * 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
+     */
     @Override
     @PostConstruct
-    @TenantIgnore // 初始化缓存,无需租户过滤
     public synchronized void initLocalCache() {
-        // 获取部门列表,如果有更新
-        List<DeptDO> deptList = loadDeptIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(deptList)) {
-            return;
-        }
-
-        // 构建缓存
-        ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
-        ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
-        deptList.forEach(sysRoleDO -> {
-            builder.put(sysRoleDO.getId(), sysRoleDO);
-            parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
-        });
-        // 设置缓存
-        deptCache = builder.build();
-        parentDeptCache = parentBuilder.build();
-        maxUpdateTime = CollectionUtils.getMaxValue(deptList, DeptDO::getUpdateTime);
-        log.info("[initLocalCache][初始化 Dept 数量为 {}]", deptList.size());
+        initLocalCacheIfUpdate(null);
     }
 
     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
     public void schedulePeriodicRefresh() {
-        self.initLocalCache();
+        initLocalCacheIfUpdate(this.maxUpdateTime);
     }
 
     /**
-     * 如果部门发生变化,从数据库中获取最新的全量部门。
-     * 如果未发生变化,则返回空
+     * 刷新本地缓存
      *
-     * @param maxUpdateTime 当前部门的最大更新时间
-     * @return 部门列表
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
-    protected List<DeptDO> loadDeptIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadDeptIfUpdate][首次加载全量部门]");
-        } else { // 判断数据库中是否有更新的部门
-            if (deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
+    private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 注意:忽略自动多租户,因为要全局初始化缓存
+        TenantUtils.executeIgnore(() -> {
+            // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+            // 如果没有增量的数据变化,则不进行本地缓存的刷新
+            if (maxUpdateTime != null
+                    && deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+                log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
+                return;
             }
-            log.info("[loadDeptIfUpdate][增量加载全量部门]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有部门
-        return deptMapper.selectList();
+            List<DeptDO> depts = deptMapper.selectList();
+            log.info("[initLocalCacheIfUpdate][缓存部门,数量为:{}]", depts.size());
+
+            // 第二步:构建缓存。创建或更新支付 Client
+            // 构建缓存
+            ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
+            ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
+            depts.forEach(sysRoleDO -> {
+                builder.put(sysRoleDO.getId(), sysRoleDO);
+                parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
+            });
+            // 设置缓存
+            deptCache = builder.build();
+            parentDeptCache = parentBuilder.build();
+
+            // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+            this.maxUpdateTime = CollectionUtils.getMaxValue(depts, DeptDO::getUpdateTime);
+        });
     }
 
     @Override

+ 24 - 27
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;
 import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -74,42 +76,37 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
     @Override
     @PostConstruct
     public void initLocalCache() {
-        // 获取客户端列表,如果有更新
-        List<OAuth2ClientDO> tenantList = loadOAuth2ClientIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(tenantList)) {
-            return;
-        }
-
-        // 写入缓存
-        clientCache = convertMap(tenantList, OAuth2ClientDO::getClientId);
-        maxUpdateTime = getMaxValue(tenantList, OAuth2ClientDO::getUpdateTime);
-        log.info("[initLocalCache][初始化 OAuth2Client 数量为 {}]", tenantList.size());
+        initLocalCacheIfUpdate(null);
     }
 
     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
     public void schedulePeriodicRefresh() {
-        initLocalCache();
+        initLocalCacheIfUpdate(this.maxUpdateTime);
     }
 
     /**
-     * 如果客户端发生变化,从数据库中获取最新的全量客户端。
-     * 如果未发生变化,则返回空
+     * 刷新本地缓存
      *
-     * @param maxUpdateTime 当前客户端的最大更新时间
-     * @return 客户端列表
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
-    private List<OAuth2ClientDO> loadOAuth2ClientIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadOAuth2ClientIfUpdate][首次加载全量客户端]");
-        } else { // 判断数据库中是否有更新的客户端
-            if (oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
-            }
-            log.info("[loadOAuth2ClientIfUpdate][增量加载全量客户端]");
+    private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+        // 如果没有增量的数据变化,则不进行本地缓存的刷新
+        if (maxUpdateTime != null
+                && oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+            log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
+            return;
         }
-        // 第二步,如果有更新,则从数据库加载所有客户端
-        return oauth2ClientMapper.selectList();
+        List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
+        log.info("[initLocalCacheIfUpdate][缓存 OAuth2 客户端,数量为:{}]", clients.size());
+
+        // 第二步:构建缓存。
+        clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
+
+        // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+        this.maxUpdateTime = getMaxValue(clients, OAuth2ClientDO::getUpdateTime);
     }
 
     @Override

+ 3 - 2
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 {
      *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
     private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
         // 如果没有增量的数据变化,则不进行本地缓存的刷新
         if (maxUpdateTime != null
             && menuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
@@ -111,7 +112,7 @@ public class MenuServiceImpl implements MenuService {
         List<MenuDO> menuList = menuMapper.selectList();
         log.info("[initLocalCacheIfUpdate][缓存菜单,数量为:{}]", menuList.size());
 
-        // 构建缓存
+        // 第二步:构建缓存
         ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
         ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
         menuList.forEach(menuDO -> {
@@ -123,7 +124,7 @@ public class MenuServiceImpl implements MenuService {
         menuCache = menuCacheBuilder.build();
         permissionMenuCache = permMenuCacheBuilder.build();
 
-        // 设置最新的 maxUpdateTime,用于下次的增量判断
+        // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断
         this.maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
     }
 

+ 60 - 82
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;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
@@ -31,7 +32,6 @@ import com.google.common.collect.Sets;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -126,106 +126,84 @@ public class PermissionServiceImpl implements PermissionService {
     @Resource
     private PermissionProducer permissionProducer;
 
-    @Resource
-    @Lazy // 注入自己,所以延迟加载
-    private PermissionService self;
-
     @Override
     @PostConstruct
-    @TenantIgnore // 初始化缓存,无需租户过滤
     public void initLocalCache() {
-        initUserRoleLocalCache();
-        initRoleMenuLocalCache();
-    }
-
-    /**
-     * 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存
-     */
-    @VisibleForTesting
-    void initRoleMenuLocalCache() {
-        // 获取角色与菜单的关联列表,如果有更新
-        List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(roleMenuMaxUpdateTime);
-        if (CollUtil.isEmpty(roleMenuList)) {
-            return;
-        }
-
-        // 初始化 roleMenuCache 和 menuRoleCache 缓存
-        ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
-        ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
-        roleMenuList.forEach(roleMenuDO -> {
-            roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
-            menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
-        });
-        roleMenuCache = roleMenuCacheBuilder.build();
-        menuRoleCache = menuRoleCacheBuilder.build();
-        roleMenuMaxUpdateTime = getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime);
-        log.info("[initRoleMenuLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
-    }
-
-    /**
-     * 初始化 {@link #userRoleCache} 缓存
-     */
-    @VisibleForTesting
-    void initUserRoleLocalCache() {
-        // 获取用户与角色的关联列表,如果有更新
-        List<UserRoleDO> userRoleList = loadUserRoleIfUpdate(userRoleMaxUpdateTime);
-        if (CollUtil.isEmpty(userRoleList)) {
-            return;
-        }
-
-        // 初始化 userRoleCache 缓存
-        ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
-        userRoleList.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
-        userRoleCache = CollectionUtils.convertMultiMap2(userRoleList, UserRoleDO::getUserId, UserRoleDO::getRoleId);
-        userRoleMaxUpdateTime = getMaxValue(userRoleList, UserRoleDO::getUpdateTime);
-        log.info("[initUserRoleLocalCache][初始化用户与角色的关联数量为 {}]", userRoleList.size());
+        initLocalCacheIfUpdateForRoleMenu(null);
+        initLocalCacheIfUpdateForUserRole(null);
     }
 
     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
     public void schedulePeriodicRefresh() {
-        self.initLocalCache();
+        initLocalCacheIfUpdateForRoleMenu(this.roleMenuMaxUpdateTime);
+        initLocalCacheIfUpdateForUserRole(this.userRoleMaxUpdateTime);
     }
 
     /**
-     * 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。
-     * 如果未发生变化,则返回空
+     * 刷新 RoleMenu 本地缓存
      *
-     * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
-     * @return 角色与菜单的关联列表
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
-    protected List<RoleMenuDO> loadRoleMenuIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
-        } else { // 判断数据库中是否有更新的角色与菜单的关联
-            if (roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
+    @VisibleForTesting
+    void initLocalCacheIfUpdateForRoleMenu(LocalDateTime maxUpdateTime) {
+        // 注意:忽略自动多租户,因为要全局初始化缓存
+        TenantUtils.executeIgnore(() -> {
+            // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+            // 如果没有增量的数据变化,则不进行本地缓存的刷新
+            if (maxUpdateTime != null
+                    && roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+                log.info("[initLocalCacheIfUpdateForRoleMenu][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
+                return;
             }
-            log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有角色与菜单的关联
-        return roleMenuMapper.selectList();
+            List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
+            log.info("[initLocalCacheIfUpdateForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
+
+            // 第二步:构建缓存。
+            ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
+            ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
+            roleMenus.forEach(roleMenuDO -> {
+                roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
+                menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
+            });
+            roleMenuCache = roleMenuCacheBuilder.build();
+            menuRoleCache = menuRoleCacheBuilder.build();
+
+            // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+            this.roleMenuMaxUpdateTime = getMaxValue(roleMenus, RoleMenuDO::getUpdateTime);
+        });
     }
 
     /**
-     * 如果用户与角色的关联发生变化,从数据库中获取最新的全量用户与角色的关联。
-     * 如果未发生变化,则返回空
+     * 刷新 UserRole 本地缓存
      *
-     * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
-     * @return 角色与菜单的关联列表
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
-    protected List<UserRoleDO> loadUserRoleIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
-        } else { // 判断数据库中是否有更新的用户与角色的关联
-            if (userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
+    @VisibleForTesting
+    void initLocalCacheIfUpdateForUserRole(LocalDateTime maxUpdateTime) {
+        // 注意:忽略自动多租户,因为要全局初始化缓存
+        TenantUtils.executeIgnore(() -> {
+            // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+            // 如果没有增量的数据变化,则不进行本地缓存的刷新
+            if (maxUpdateTime != null
+                    && userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+                log.info("[initLocalCacheIfUpdateForUserRole][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
+                return;
             }
-            log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有用户与角色的关联
-        return userRoleMapper.selectList();
+            List<UserRoleDO> userRoles = userRoleMapper.selectList();
+            log.info("[initLocalCacheIfUpdateForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
+
+            // 第二步:构建缓存。
+            ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
+            userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
+            userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
+
+            // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+            this.userRoleMaxUpdateTime = getMaxValue(userRoles, UserRoleDO::getUpdateTime);
+        });
     }
 
     @Override

+ 26 - 34
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;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
-import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
 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
 import cn.iocoder.yudao.module.system.convert.permission.RoleConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
+import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
 import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.lang.Nullable;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -77,53 +76,46 @@ public class RoleServiceImpl implements RoleService {
     @Resource
     private RoleProducer roleProducer;
 
-    @Resource
-    @Lazy // 注入自己,所以延迟加载
-    private RoleService self;
-
     /**
      * 初始化 {@link #roleCache} 缓存
      */
     @Override
     @PostConstruct
-    @TenantIgnore // 忽略自动多租户,全局初始化缓存
     public void initLocalCache() {
-        // 获取角色列表,如果有更新
-        List<RoleDO> roleList = loadRoleIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(roleList)) {
-            return;
-        }
-
-        // 写入缓存
-        roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
-        maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
-        log.info("[initLocalCache][初始化 Role 数量为 {}]", roleList.size());
+        initLocalCacheIfUpdate(null);
     }
 
     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
     public void schedulePeriodicRefresh() {
-       self.initLocalCache();
+        initLocalCacheIfUpdate(this.maxUpdateTime);
     }
 
     /**
-     * 如果角色发生变化,从数据库中获取最新的全量角色。
-     * 如果未发生变化,则返回空
+     * 刷新本地缓存
      *
-     * @param maxUpdateTime 当前角色的最大更新时间
-     * @return 角色列表
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
-    private List<RoleDO> loadRoleIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadRoleIfUpdate][首次加载全量角色]");
-        } else { // 判断数据库中是否有更新的角色
-            if (roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
+    private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 注意:忽略自动多租户,因为要全局初始化缓存
+        TenantUtils.executeIgnore(() -> {
+            // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+            // 如果没有增量的数据变化,则不进行本地缓存的刷新
+            if (maxUpdateTime != null
+                    && roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+                log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
+                return;
             }
-            log.info("[loadRoleIfUpdate][增量加载全量角色]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有角色
-        return roleMapper.selectList();
+            List<RoleDO> roleList = roleMapper.selectList();
+            log.info("[initLocalCacheIfUpdate][缓存角色,数量为:{}]", roleList.size());
+
+            // 第二步:构建缓存。
+            roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
+
+            // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+            this.maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
+        });
     }
 
     @Override

+ 29 - 35
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 {
     @Override
     @PostConstruct
     public void initLocalCache() {
-        // 获取敏感词列表,如果有更新
-        List<SensitiveWordDO> sensitiveWordList = loadSensitiveWordIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(sensitiveWordList)) {
+        initLocalCacheIfUpdate(null);
+    }
+
+    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+    public void schedulePeriodicRefresh() {
+        initLocalCacheIfUpdate(this.maxUpdateTime);
+    }
+
+    /**
+     * 刷新本地缓存
+     *
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
+     */
+    private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+        // 如果没有增量的数据变化,则不进行本地缓存的刷新
+        if (maxUpdateTime != null
+                && sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+            log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
             return;
         }
+        List<SensitiveWordDO> sensitiveWords = sensitiveWordMapper.selectList();
+        log.info("[initLocalCacheIfUpdate][缓存敏感词,数量为:{}]", sensitiveWords.size());
 
+        // 第二步:构建缓存。
         // 写入 sensitiveWordTagsCache 缓存
         Set<String> tags = new HashSet<>();
-        sensitiveWordList.forEach(word -> tags.addAll(word.getTags()));
+        sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
         sensitiveWordTagsCache = tags;
         // 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
-        initSensitiveWordTrie(sensitiveWordList);
-        // 写入 maxUpdateTime 最大更新时间
-        maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWordList, SensitiveWordDO::getUpdateTime);
-        log.info("[initLocalCache][初始化 敏感词 数量为 {}]", sensitiveWordList.size());
+        initSensitiveWordTrie(sensitiveWords);
+
+        // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+        this.maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWords, SensitiveWordDO::getUpdateTime);
     }
 
     private void initSensitiveWordTrie(List<SensitiveWordDO> wordDOs) {
@@ -122,33 +143,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
         this.tagSensitiveWordTries = tagSensitiveWordTries;
     }
 
-    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
-    public void schedulePeriodicRefresh() {
-        initLocalCache();
-    }
-
-    /**
-     * 如果敏感词发生变化,从数据库中获取最新的全量敏感词。
-     * 如果未发生变化,则返回空
-     *
-     * @param maxUpdateTime 当前敏感词的最大更新时间
-     * @return 敏感词列表
-     */
-    private List<SensitiveWordDO> loadSensitiveWordIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        // 如果更新时间为空,说明 DB 一定有新数据
-        if (maxUpdateTime == null) {
-            log.info("[loadSensitiveWordIfUpdate][首次加载全量敏感词]");
-        } else { // 判断数据库中是否有更新的敏感词
-            if (sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
-            }
-            log.info("[loadSensitiveWordIfUpdate][增量加载全量敏感词]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有敏感词
-        return sensitiveWordMapper.selectList();
-    }
-
     @Override
     public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
         // 校验唯一性

+ 1 - 1
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 {
     /**
      * 初始化短信客户端
      */
-    void initSmsClients();
+    void initLocalCache();
 
     /**
      * 创建短信渠道

+ 25 - 34
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java

@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.system.service.sms;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
 import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
@@ -23,14 +21,14 @@ import java.util.Collection;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
 
 /**
- * 短信渠道Service实现类
+ * 短信渠道 Service 实现类
  *
  * @author zzf
- * @date 2021/1/25 9:25
  */
 @Service
 @Slf4j
@@ -61,46 +59,39 @@ public class SmsChannelServiceImpl implements SmsChannelService {
 
     @Override
     @PostConstruct
-    public void initSmsClients() {
-        // 获取短信渠道,如果有更新
-        List<SmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(smsChannels)) {
-            return;
-        }
-
-        // 创建或更新短信 Client
-        List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(smsChannels);
-        propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
-
-        // 写入缓存
-        maxUpdateTime = CollectionUtils.getMaxValue(smsChannels, SmsChannelDO::getUpdateTime);
-        log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
+    public void initLocalCache() {
+        initLocalCacheIfUpdate(null);
     }
 
     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
     public void schedulePeriodicRefresh() {
-        initSmsClients();
+        initLocalCacheIfUpdate(this.maxUpdateTime);
     }
 
     /**
-     * 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
-     * 如果未发生变化,则返回空
+     * 刷新本地缓存
      *
-     * @param maxUpdateTime 当前短信渠道的最大更新时间
-     * @return 短信渠道列表
+     * @param maxUpdateTime 最大更新时间
+     *                      1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
+     *                      2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
      */
-    private List<SmsChannelDO> loadSmsChannelIfUpdate(LocalDateTime maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
-        } else { // 判断数据库中是否有更新的短信渠道
-            if (smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
-            }
-            log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
+    private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
+        // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
+        // 如果没有增量的数据变化,则不进行本地缓存的刷新
+        if (maxUpdateTime != null
+                && smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
+            log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
+            return;
         }
-        // 第二步,如果有更新,则从数据库加载所有短信渠道
-        return smsChannelMapper.selectList();
+        List<SmsChannelDO> channels = smsChannelMapper.selectList();
+        log.info("[initLocalCacheIfUpdate][缓存短信渠道,数量为:{}]", channels.size());
+
+        // 第二步:构建缓存。创建或更新短信 Client
+        List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
+        propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
+
+        // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
+        this.maxUpdateTime = getMaxValue(channels, SmsChannelDO::getUpdateTime);
     }
 
     @Override

+ 4 - 4
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 {
     private PermissionProducer permissionProducer;
 
     @Test
-    public void testInitRoleMenuLocalCache() {
+    public void testInitLocalCacheIfUpdateForRoleMenu() {
         // mock 数据
         RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
         roleMenuMapper.insert(roleMenuDO01);
@@ -79,7 +79,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
         roleMenuMapper.insert(roleMenuDO02);
 
         // 调用
-        permissionService.initRoleMenuLocalCache();
+        permissionService.initLocalCacheIfUpdateForRoleMenu(null);
         // 断言 roleMenuCache 缓存
         assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
         assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
@@ -93,7 +93,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testInitUserRoleLocalCache() {
+    public void testInitLocalCacheIfUpdateForUserRole() {
         // mock 数据
         UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
         userRoleMapper.insert(userRoleDO01);
@@ -101,7 +101,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
         userRoleMapper.insert(roleMenuDO02);
 
         // 调用
-        permissionService.initUserRoleLocalCache();
+        permissionService.initLocalCacheIfUpdateForUserRole(null);
         // 断言 roleMenuCache 缓存
         assertEquals(1, permissionService.getUserRoleCache().size());
         assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));

+ 1 - 1
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 {
         smsChannelMapper.insert(smsChannelDO02);
 
         // 调用
-        smsChannelService.initSmsClients();
+        smsChannelService.initLocalCache();
         // 校验 maxUpdateTime 属性
         LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
         assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);