diff --git a/ruoyi-quartz/pom.xml b/ruoyi-quartz/pom.xml
index a6068a62b..b91b68cff 100644
--- a/ruoyi-quartz/pom.xml
+++ b/ruoyi-quartz/pom.xml
@@ -28,13 +28,6 @@
-
-
-
- com.ruoyi
- ruoyi-common
-
-
-
\ No newline at end of file
+
diff --git a/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/ConfigFrameworkDAO.java b/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/ConfigFrameworkDAO.java
index 52bd0a4f0..17561434b 100644
--- a/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/ConfigFrameworkDAO.java
+++ b/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/ConfigFrameworkDAO.java
@@ -23,6 +23,6 @@ public interface ConfigFrameworkDAO {
*
* @return 配置列表
*/
- List getSysConfigList();
+ List selectList();
}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/DBConfigRepository.java b/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/DBConfigRepository.java
index 3e2da5209..92431c620 100644
--- a/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/DBConfigRepository.java
+++ b/src/main/java/cn/iocoder/dashboard/framework/apollo/internals/DBConfigRepository.java
@@ -73,7 +73,6 @@ public class DBConfigRepository extends AbstractConfigRepository {
if (CollUtil.isEmpty(configs)) { // 如果没有更新,则返回
return;
}
- log.info("[sync][同步到新配置,配置数量为:{}]", configs.size());
// 第二步,构建新的 Properties
Properties newProperties = this.buildProperties(configs);
@@ -83,6 +82,7 @@ public class DBConfigRepository extends AbstractConfigRepository {
this.maxUpdateTime = configs.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
// 第四部,触发配置刷新!重要!!!!
super.fireRepositoryChange(m_namespace, newProperties);
+ log.info("[sync][缓存配置,数量为:{}]", configs.size());
}
@Override
@@ -150,7 +150,7 @@ public class DBConfigRepository extends AbstractConfigRepository {
log.info("[loadConfigIfUpdate][增量加载全量配置]");
}
// 第二步,如果有更新,则从数据库加载所有配置
- return configFrameworkDAO.getSysConfigList();
+ return configFrameworkDAO.selectList();
}
}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/quartz/config/QuartzConfig.java b/src/main/java/cn/iocoder/dashboard/framework/quartz/config/QuartzConfig.java
new file mode 100644
index 000000000..24abfa9b7
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/quartz/config/QuartzConfig.java
@@ -0,0 +1,9 @@
+package cn.iocoder.dashboard.framework.quartz.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@Configuration
+@EnableScheduling // 开启 Spring 自带的定时任务
+public class QuartzConfig {
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/quartz/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/quartz/package-info.java
new file mode 100644
index 000000000..79d9eebe7
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/quartz/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * 定时任务,采用 Quartz 实现进程内的任务执行。
+ * 考虑到高可用,使用 Quartz 自带的 MySQL 集群方案。
+ */
+package cn.iocoder.dashboard.framework.quartz;
diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/dal/mysql/dao/config/InfConfigDAOImpl.java b/src/main/java/cn/iocoder/dashboard/modules/infra/dal/mysql/dao/config/InfConfigDAOImpl.java
index 99070764f..b746c95ad 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/infra/dal/mysql/dao/config/InfConfigDAOImpl.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/infra/dal/mysql/dao/config/InfConfigDAOImpl.java
@@ -32,7 +32,7 @@ public class InfConfigDAOImpl implements ConfigFrameworkDAO {
}
@Override
- public List getSysConfigList() {
+ public List selectList() {
return jdbcTemplate.query("SELECT `key`, `value`, update_time, deleted FROM inf_config", new BeanPropertyRowMapper<>(InfConfigDO.class));
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysMenuMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysMenuMapper.java
index 03db0e69e..59dbe287a 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysMenuMapper.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysMenuMapper.java
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
+import java.util.Date;
import java.util.List;
@Mapper
@@ -30,4 +31,9 @@ public interface SysMenuMapper extends BaseMapper {
return selectList(new QueryWrapper<>());
}
+ default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
+ return selectOne(new QueryWrapper().select("id")
+ .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null;
+ }
+
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
index 450fd3b64..ba611836e 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
@@ -1,6 +1,8 @@
package cn.iocoder.dashboard.modules.system.service.permission.impl;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuListReqVO;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuUpdateReqVO;
@@ -16,14 +18,12 @@ 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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
@@ -37,6 +37,12 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
@Slf4j
public class SysMenuServiceImpl implements SysMenuService {
+ /**
+ * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
+ * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
+ */
+ private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
+
/**
* 菜单缓存
* key:菜单编号
@@ -52,6 +58,10 @@ public class SysMenuServiceImpl implements SysMenuService {
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
private volatile Multimap permMenuCache;
+ /**
+ * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
+ */
+ private volatile Date maxUpdateTime;
@Resource
private SysMenuMapper menuMapper;
@@ -63,8 +73,14 @@ public class SysMenuServiceImpl implements SysMenuService {
*/
@Override
@PostConstruct
- public void init() {
- List menuList = menuMapper.selectList();
+ public synchronized void init() {
+ // 获取
+ List menuList = this.loadMenuIfUpdate(maxUpdateTime);
+ if (CollUtil.isEmpty(menuList)) {
+ return;
+ }
+
+ // 构建缓存
ImmutableMap.Builder menuCacheBuilder = ImmutableMap.builder();
ImmutableMultimap.Builder permMenuCacheBuilder = ImmutableMultimap.builder();
menuList.forEach(menuDO -> {
@@ -73,7 +89,35 @@ public class SysMenuServiceImpl implements SysMenuService {
});
menuCache = menuCacheBuilder.build();
permMenuCache = permMenuCacheBuilder.build();
- log.info("[init][初始化菜单数量为 {}]", menuList.size());
+ assert menuList.size() > 0; // 断言,避免告警
+ maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+ log.info("[init][缓存菜单,数量为:{}]", menuList.size());
+ }
+
+ @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+ public void schedulePeriodicRefresh() {
+ init();
+ }
+
+ /**
+ * 如果菜单发生变化,从数据库中获取最新的全量菜单。
+ * 如果未发生变化,则返回空
+ *
+ * @param maxUpdateTime 当前菜单的最大更新时间
+ * @return 菜单列表
+ */
+ private List loadMenuIfUpdate(Date maxUpdateTime) {
+ // 第一步,判断是否要更新。
+ if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
+ log.info("[loadMenuIfUpdate][首次加载全量菜单]");
+ } else { // 判断数据库中是否有更新的菜单
+ if (!menuMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
+ return null;
+ }
+ log.info("[loadMenuIfUpdate][增量加载全量菜单]");
+ }
+ // 第二步,如果有更新,则从数据库加载所有菜单
+ return menuMapper.selectList();
}
@Override