Commit 7813c4019adc8fd85d2c61af8e6da8a7235da359

Authored by YunaiV
1 parent dc42f0f1

1. 部门的缓存刷新机制

src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/dept/SysDeptMapper.java
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
7 import com.baomidou.mybatisplus.core.mapper.BaseMapper; 7 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 9
  10 +import java.util.Date;
10 import java.util.List; 11 import java.util.List;
11 12
12 @Mapper 13 @Mapper
@@ -30,4 +31,9 @@ public interface SysDeptMapper extends BaseMapper<SysDeptDO> { @@ -30,4 +31,9 @@ public interface SysDeptMapper extends BaseMapper<SysDeptDO> {
30 return selectCount(new QueryWrapper<SysDeptDO>().eq("parent_id", parentId)); 31 return selectCount(new QueryWrapper<SysDeptDO>().eq("parent_id", parentId));
31 } 32 }
32 33
  34 + default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
  35 + return selectOne(new QueryWrapper<SysDeptDO>().select("id")
  36 + .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null;
  37 + }
  38 +
33 } 39 }
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/dept/SysDeptRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.consumer.dept;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
  5 +import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link SysDeptRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class SysDeptRefreshConsumer extends AbstractChannelMessageListener<SysDeptRefreshMessage> {
  19 +
  20 + @Resource
  21 + private SysDeptService deptService;
  22 +
  23 + @Override
  24 + public void onMessage(SysDeptRefreshMessage message) {
  25 + log.info("[onMessage][收到 Dept 刷新消息]");
  26 + deptService.initLocalCache();
  27 + }
  28 +
  29 +}
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/dept/SysDeptRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.message.dept;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 部门数据刷新 Message
  8 + */
  9 +@Data
  10 +public class SysDeptRefreshMessage implements ChannelMessage {
  11 +
  12 + @Override
  13 + public String getChannel() {
  14 + return "system.dept.refresh";
  15 + }
  16 +
  17 +}
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/dept/SysDeptProducer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.producer.dept;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
  5 +import org.springframework.data.redis.core.StringRedisTemplate;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +/**
  11 + * Dept 部门相关消息的 Producer
  12 + */
  13 +@Component
  14 +public class SysDeptProducer {
  15 +
  16 + @Resource
  17 + private StringRedisTemplate stringRedisTemplate;
  18 +
  19 + /**
  20 + * 发送 {@link SysDeptRefreshMessage} 消息
  21 + */
  22 + public void sendMenuRefreshMessage() {
  23 + SysDeptRefreshMessage message = new SysDeptRefreshMessage();
  24 + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  25 + }
  26 +
  27 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/dept/SysDeptService.java
@@ -20,16 +20,9 @@ import java.util.Map; @@ -20,16 +20,9 @@ import java.util.Map;
20 public interface SysDeptService { 20 public interface SysDeptService {
21 21
22 /** 22 /**
23 - * 初始化 23 + * 初始化部门的本地缓存
24 */ 24 */
25 - void init();  
26 -  
27 - /**  
28 - * 获得所有部门列表  
29 - *  
30 - * @return 部门列表  
31 - */  
32 - List<SysDeptDO> listDepts(); 25 + void initLocalCache();
33 26
34 /** 27 /**
35 * 获得指定编号的部门列表 28 * 获得指定编号的部门列表
src/main/java/cn/iocoder/dashboard/modules/system/service/dept/impl/SysDeptServiceImpl.java
@@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.service.dept.impl; @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.service.dept.impl;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.dashboard.common.enums.CommonStatusEnum; 4 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
5 import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; 5 import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
  6 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
6 import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptCreateReqVO; 7 import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptCreateReqVO;
7 import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptListReqVO; 8 import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptListReqVO;
8 import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptUpdateReqVO; 9 import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptUpdateReqVO;
@@ -10,19 +11,18 @@ import cn.iocoder.dashboard.modules.system.convert.dept.SysDeptConvert; @@ -10,19 +11,18 @@ import cn.iocoder.dashboard.modules.system.convert.dept.SysDeptConvert;
10 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dept.SysDeptMapper; 11 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dept.SysDeptMapper;
11 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO; 12 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO;
12 import cn.iocoder.dashboard.modules.system.enums.dept.DeptIdEnum; 13 import cn.iocoder.dashboard.modules.system.enums.dept.DeptIdEnum;
  14 +import cn.iocoder.dashboard.modules.system.mq.producer.dept.SysDeptProducer;
13 import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService; 15 import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
14 import com.google.common.collect.ImmutableMap; 16 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.ImmutableMultimap; 17 import com.google.common.collect.ImmutableMultimap;
16 import com.google.common.collect.Multimap; 18 import com.google.common.collect.Multimap;
17 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.scheduling.annotation.Scheduled;
18 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
19 22
20 import javax.annotation.PostConstruct; 23 import javax.annotation.PostConstruct;
21 import javax.annotation.Resource; 24 import javax.annotation.Resource;
22 -import java.util.ArrayList;  
23 -import java.util.Collection;  
24 -import java.util.List;  
25 -import java.util.Map; 25 +import java.util.*;
26 26
27 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; 27 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
28 28
@@ -36,6 +36,12 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; @@ -36,6 +36,12 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
36 public class SysDeptServiceImpl implements SysDeptService { 36 public class SysDeptServiceImpl implements SysDeptService {
37 37
38 /** 38 /**
  39 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  40 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  41 + */
  42 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  43 +
  44 + /**
39 * 部门缓存 45 * 部门缓存
40 * key:部门编号 {@link SysDeptDO#getId()} 46 * key:部门编号 {@link SysDeptDO#getId()}
41 * 47 *
@@ -50,30 +56,64 @@ public class SysDeptServiceImpl implements SysDeptService { @@ -50,30 +56,64 @@ public class SysDeptServiceImpl implements SysDeptService {
50 * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 56 * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
51 */ 57 */
52 private volatile Multimap<Long, SysDeptDO> parentDeptCache; 58 private volatile Multimap<Long, SysDeptDO> parentDeptCache;
  59 + /**
  60 + * 缓存部门的最大更新时间,用于后续的增量轮询,判断是否有更新
  61 + */
  62 + private volatile Date maxUpdateTime;
53 63
54 @Resource 64 @Resource
55 private SysDeptMapper deptMapper; 65 private SysDeptMapper deptMapper;
56 66
  67 + @Resource
  68 + private SysDeptProducer deptProducer;
  69 +
57 @Override 70 @Override
58 @PostConstruct 71 @PostConstruct
59 - public void init() {  
60 - // 从数据库中读取  
61 - List<SysDeptDO> sysDeptDOList = deptMapper.selectList(); 72 + public synchronized void initLocalCache() {
  73 + // 获取部门列表,如果有更新
  74 + List<SysDeptDO> deptList = this.loadDeptIfUpdate(maxUpdateTime);
  75 + if (CollUtil.isEmpty(deptList)) {
  76 + return;
  77 + }
  78 +
62 // 构建缓存 79 // 构建缓存
63 ImmutableMap.Builder<Long, SysDeptDO> builder = ImmutableMap.builder(); 80 ImmutableMap.Builder<Long, SysDeptDO> builder = ImmutableMap.builder();
64 ImmutableMultimap.Builder<Long, SysDeptDO> parentBuilder = ImmutableMultimap.builder(); 81 ImmutableMultimap.Builder<Long, SysDeptDO> parentBuilder = ImmutableMultimap.builder();
65 - sysDeptDOList.forEach(sysRoleDO -> { 82 + deptList.forEach(sysRoleDO -> {
66 builder.put(sysRoleDO.getId(), sysRoleDO); 83 builder.put(sysRoleDO.getId(), sysRoleDO);
67 parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO); 84 parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
68 }); 85 });
69 // 设置缓存 86 // 设置缓存
70 deptCache = builder.build(); 87 deptCache = builder.build();
71 parentDeptCache = parentBuilder.build(); 88 parentDeptCache = parentBuilder.build();
72 - log.info("[init][初始化 Dept 数量为 {}]", sysDeptDOList.size()); 89 + assert deptList.size() > 0; // 断言,避免告警
  90 + maxUpdateTime = deptList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
  91 + log.info("[init][初始化 Dept 数量为 {}]", deptList.size());
73 } 92 }
74 93
75 - @Override  
76 - public List<SysDeptDO> listDepts() { 94 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  95 + public void schedulePeriodicRefresh() {
  96 + initLocalCache();
  97 + }
  98 +
  99 + /**
  100 + * 如果部门发生变化,从数据库中获取最新的全量部门。
  101 + * 如果未发生变化,则返回空
  102 + *
  103 + * @param maxUpdateTime 当前部门的最大更新时间
  104 + * @return 部门列表
  105 + */
  106 + private List<SysDeptDO> loadDeptIfUpdate(Date maxUpdateTime) {
  107 + // 第一步,判断是否要更新。
  108 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  109 + log.info("[loadMenuIfUpdate][首次加载全量部门]");
  110 + } else { // 判断数据库中是否有更新的部门
  111 + if (!deptMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
  112 + return null;
  113 + }
  114 + log.info("[loadMenuIfUpdate][增量加载全量部门]");
  115 + }
  116 + // 第二步,如果有更新,则从数据库加载所有部门
77 return deptMapper.selectList(); 117 return deptMapper.selectList();
78 } 118 }
79 119
@@ -134,6 +174,8 @@ public class SysDeptServiceImpl implements SysDeptService { @@ -134,6 +174,8 @@ public class SysDeptServiceImpl implements SysDeptService {
134 // 插入部门 174 // 插入部门
135 SysDeptDO dept = SysDeptConvert.INSTANCE.convert(reqVO); 175 SysDeptDO dept = SysDeptConvert.INSTANCE.convert(reqVO);
136 deptMapper.insert(dept); 176 deptMapper.insert(dept);
  177 + // 发送消息
  178 + deptProducer.sendMenuRefreshMessage();
137 return dept.getId(); 179 return dept.getId();
138 } 180 }
139 181
@@ -144,6 +186,8 @@ public class SysDeptServiceImpl implements SysDeptService { @@ -144,6 +186,8 @@ public class SysDeptServiceImpl implements SysDeptService {
144 // 更新部门 186 // 更新部门
145 SysDeptDO updateObj = SysDeptConvert.INSTANCE.convert(reqVO); 187 SysDeptDO updateObj = SysDeptConvert.INSTANCE.convert(reqVO);
146 deptMapper.updateById(updateObj); 188 deptMapper.updateById(updateObj);
  189 + // 发送消息
  190 + deptProducer.sendMenuRefreshMessage();
147 } 191 }
148 192
149 @Override 193 @Override
@@ -156,6 +200,8 @@ public class SysDeptServiceImpl implements SysDeptService { @@ -156,6 +200,8 @@ public class SysDeptServiceImpl implements SysDeptService {
156 } 200 }
157 // 删除部门 201 // 删除部门
158 deptMapper.deleteById(id); 202 deptMapper.deleteById(id);
  203 + // 发送消息
  204 + deptProducer.sendMenuRefreshMessage();
159 } 205 }
160 206
161 private void checkCreateOrUpdate(Long id, Long parentId, String name) { 207 private void checkCreateOrUpdate(Long id, Long parentId, String name) {
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java
@@ -82,7 +82,7 @@ public class SysDictDataServiceImpl implements SysDictDataService { @@ -82,7 +82,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
82 82
83 @Override 83 @Override
84 @PostConstruct 84 @PostConstruct
85 - public void initLocalCache() { 85 + public synchronized void initLocalCache() {
86 // 获取字典数据列表,如果有更新 86 // 获取字典数据列表,如果有更新
87 List<SysDictDataDO> dataList = this.loadDictDataIfUpdate(maxUpdateTime); 87 List<SysDictDataDO> dataList = this.loadDictDataIfUpdate(maxUpdateTime);
88 if (CollUtil.isEmpty(dataList)) { 88 if (CollUtil.isEmpty(dataList)) {
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
@@ -21,6 +21,9 @@ import com.google.common.collect.Multimap; @@ -21,6 +21,9 @@ import com.google.common.collect.Multimap;
21 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
22 import org.springframework.scheduling.annotation.Scheduled; 22 import org.springframework.scheduling.annotation.Scheduled;
23 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
  24 +import org.springframework.transaction.annotation.Transactional;
  25 +import org.springframework.transaction.support.TransactionSynchronization;
  26 +import org.springframework.transaction.support.TransactionSynchronizationManager;
24 27
25 import javax.annotation.PostConstruct; 28 import javax.annotation.PostConstruct;
26 import javax.annotation.Resource; 29 import javax.annotation.Resource;
@@ -169,6 +172,8 @@ public class SysMenuServiceImpl implements SysMenuService { @@ -169,6 +172,8 @@ public class SysMenuServiceImpl implements SysMenuService {
169 SysMenuDO menu = SysMenuConvert.INSTANCE.convert(reqVO); 172 SysMenuDO menu = SysMenuConvert.INSTANCE.convert(reqVO);
170 initMenuProperty(menu); 173 initMenuProperty(menu);
171 menuMapper.insert(menu); 174 menuMapper.insert(menu);
  175 + // 发送刷新消息
  176 + menuProducer.sendMenuRefreshMessage();
172 // 返回 177 // 返回
173 return menu.getId(); 178 return menu.getId();
174 } 179 }
@@ -196,6 +201,7 @@ public class SysMenuServiceImpl implements SysMenuService { @@ -196,6 +201,7 @@ public class SysMenuServiceImpl implements SysMenuService {
196 * 201 *
197 * @param menuId 菜单编号 202 * @param menuId 菜单编号
198 */ 203 */
  204 + @Transactional
199 public void deleteMenu(Long menuId) { 205 public void deleteMenu(Long menuId) {
200 // 校验更新的菜单是否存在 206 // 校验更新的菜单是否存在
201 if (menuMapper.selectById(menuId) == null) { 207 if (menuMapper.selectById(menuId) == null) {
@@ -213,6 +219,15 @@ public class SysMenuServiceImpl implements SysMenuService { @@ -213,6 +219,15 @@ public class SysMenuServiceImpl implements SysMenuService {
213 menuMapper.deleteById(menuId); 219 menuMapper.deleteById(menuId);
214 // 删除授予给角色的权限 220 // 删除授予给角色的权限
215 permissionService.processMenuDeleted(menuId); 221 permissionService.processMenuDeleted(menuId);
  222 + // 发送刷新消息. 注意,需要事务提交后,在进行发送消息。不然 db 还未提交,结果缓存先刷新了
  223 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
  224 +
  225 + @Override
  226 + public void afterCommit() {
  227 + menuProducer.sendMenuRefreshMessage();
  228 + }
  229 +
  230 + });
216 } 231 }
217 232
218 @Override 233 @Override