1. 部门的缓存刷新机制

This commit is contained in:
YunaiV 2021-01-23 11:29:28 +08:00
parent dc42f0f1bb
commit 7813c4019a
8 changed files with 154 additions and 21 deletions

View File

@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
import java.util.List; import java.util.List;
@Mapper @Mapper
@ -30,4 +31,9 @@ public interface SysDeptMapper extends BaseMapper<SysDeptDO> {
return selectCount(new QueryWrapper<SysDeptDO>().eq("parent_id", parentId)); return selectCount(new QueryWrapper<SysDeptDO>().eq("parent_id", parentId));
} }
default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
return selectOne(new QueryWrapper<SysDeptDO>().select("id")
.gt("update_time", maxUpdateTime).last("LIMIT 1")) != null;
}
} }

View File

@ -0,0 +1,29 @@
package cn.iocoder.dashboard.modules.system.mq.consumer.dept;
import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link SysDeptRefreshMessage} 的消费者
*
* @author 芋道源码
*/
@Component
@Slf4j
public class SysDeptRefreshConsumer extends AbstractChannelMessageListener<SysDeptRefreshMessage> {
@Resource
private SysDeptService deptService;
@Override
public void onMessage(SysDeptRefreshMessage message) {
log.info("[onMessage][收到 Dept 刷新消息]");
deptService.initLocalCache();
}
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.dashboard.modules.system.mq.message.dept;
import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
import lombok.Data;
/**
* 部门数据刷新 Message
*/
@Data
public class SysDeptRefreshMessage implements ChannelMessage {
@Override
public String getChannel() {
return "system.dept.refresh";
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.dashboard.modules.system.mq.producer.dept;
import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Dept 部门相关消息的 Producer
*/
@Component
public class SysDeptProducer {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 发送 {@link SysDeptRefreshMessage} 消息
*/
public void sendMenuRefreshMessage() {
SysDeptRefreshMessage message = new SysDeptRefreshMessage();
RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
}
}

View File

@ -20,16 +20,9 @@ import java.util.Map;
public interface SysDeptService { public interface SysDeptService {
/** /**
* 初始化 * 初始化部门的本地缓存
*/ */
void init(); void initLocalCache();
/**
* 获得所有部门列表
*
* @return 部门列表
*/
List<SysDeptDO> listDepts();
/** /**
* 获得指定编号的部门列表 * 获得指定编号的部门列表

View File

@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.service.dept.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptCreateReqVO; import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptListReqVO; import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptListReqVO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptUpdateReqVO; import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptUpdateReqVO;
@ -10,19 +11,18 @@ import cn.iocoder.dashboard.modules.system.convert.dept.SysDeptConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dept.SysDeptMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dept.SysDeptMapper;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO;
import cn.iocoder.dashboard.modules.system.enums.dept.DeptIdEnum; import cn.iocoder.dashboard.modules.system.enums.dept.DeptIdEnum;
import cn.iocoder.dashboard.modules.system.mq.producer.dept.SysDeptProducer;
import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService; import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
@ -35,6 +35,12 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
@Slf4j @Slf4j
public class SysDeptServiceImpl implements SysDeptService { public class SysDeptServiceImpl implements SysDeptService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/** /**
* 部门缓存 * 部门缓存
* key部门编号 {@link SysDeptDO#getId()} * key部门编号 {@link SysDeptDO#getId()}
@ -50,30 +56,64 @@ public class SysDeptServiceImpl implements SysDeptService {
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向 * 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/ */
private volatile Multimap<Long, SysDeptDO> parentDeptCache; private volatile Multimap<Long, SysDeptDO> parentDeptCache;
/**
* 缓存部门的最大更新时间用于后续的增量轮询判断是否有更新
*/
private volatile Date maxUpdateTime;
@Resource @Resource
private SysDeptMapper deptMapper; private SysDeptMapper deptMapper;
@Resource
private SysDeptProducer deptProducer;
@Override @Override
@PostConstruct @PostConstruct
public void init() { public synchronized void initLocalCache() {
// 从数据库中读取 // 获取部门列表如果有更新
List<SysDeptDO> sysDeptDOList = deptMapper.selectList(); List<SysDeptDO> deptList = this.loadDeptIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(deptList)) {
return;
}
// 构建缓存 // 构建缓存
ImmutableMap.Builder<Long, SysDeptDO> builder = ImmutableMap.builder(); ImmutableMap.Builder<Long, SysDeptDO> builder = ImmutableMap.builder();
ImmutableMultimap.Builder<Long, SysDeptDO> parentBuilder = ImmutableMultimap.builder(); ImmutableMultimap.Builder<Long, SysDeptDO> parentBuilder = ImmutableMultimap.builder();
sysDeptDOList.forEach(sysRoleDO -> { deptList.forEach(sysRoleDO -> {
builder.put(sysRoleDO.getId(), sysRoleDO); builder.put(sysRoleDO.getId(), sysRoleDO);
parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO); parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
}); });
// 设置缓存 // 设置缓存
deptCache = builder.build(); deptCache = builder.build();
parentDeptCache = parentBuilder.build(); parentDeptCache = parentBuilder.build();
log.info("[init][初始化 Dept 数量为 {}]", sysDeptDOList.size()); assert deptList.size() > 0; // 断言避免告警
maxUpdateTime = deptList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
log.info("[init][初始化 Dept 数量为 {}]", deptList.size());
} }
@Override @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public List<SysDeptDO> listDepts() { public void schedulePeriodicRefresh() {
initLocalCache();
}
/**
* 如果部门发生变化从数据库中获取最新的全量部门
* 如果未发生变化则返回空
*
* @param maxUpdateTime 当前部门的最大更新时间
* @return 部门列表
*/
private List<SysDeptDO> loadDeptIfUpdate(Date maxUpdateTime) {
// 第一步判断是否要更新
if (maxUpdateTime == null) { // 如果更新时间为空说明 DB 一定有新数据
log.info("[loadMenuIfUpdate][首次加载全量部门]");
} else { // 判断数据库中是否有更新的部门
if (!deptMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
return null;
}
log.info("[loadMenuIfUpdate][增量加载全量部门]");
}
// 第二步如果有更新则从数据库加载所有部门
return deptMapper.selectList(); return deptMapper.selectList();
} }
@ -134,6 +174,8 @@ public class SysDeptServiceImpl implements SysDeptService {
// 插入部门 // 插入部门
SysDeptDO dept = SysDeptConvert.INSTANCE.convert(reqVO); SysDeptDO dept = SysDeptConvert.INSTANCE.convert(reqVO);
deptMapper.insert(dept); deptMapper.insert(dept);
// 发送消息
deptProducer.sendMenuRefreshMessage();
return dept.getId(); return dept.getId();
} }
@ -144,6 +186,8 @@ public class SysDeptServiceImpl implements SysDeptService {
// 更新部门 // 更新部门
SysDeptDO updateObj = SysDeptConvert.INSTANCE.convert(reqVO); SysDeptDO updateObj = SysDeptConvert.INSTANCE.convert(reqVO);
deptMapper.updateById(updateObj); deptMapper.updateById(updateObj);
// 发送消息
deptProducer.sendMenuRefreshMessage();
} }
@Override @Override
@ -156,6 +200,8 @@ public class SysDeptServiceImpl implements SysDeptService {
} }
// 删除部门 // 删除部门
deptMapper.deleteById(id); deptMapper.deleteById(id);
// 发送消息
deptProducer.sendMenuRefreshMessage();
} }
private void checkCreateOrUpdate(Long id, Long parentId, String name) { private void checkCreateOrUpdate(Long id, Long parentId, String name) {

View File

@ -82,7 +82,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
@Override @Override
@PostConstruct @PostConstruct
public void initLocalCache() { public synchronized void initLocalCache() {
// 获取字典数据列表如果有更新 // 获取字典数据列表如果有更新
List<SysDictDataDO> dataList = this.loadDictDataIfUpdate(maxUpdateTime); List<SysDictDataDO> dataList = this.loadDictDataIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(dataList)) { if (CollUtil.isEmpty(dataList)) {

View File

@ -21,6 +21,9 @@ import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -169,6 +172,8 @@ public class SysMenuServiceImpl implements SysMenuService {
SysMenuDO menu = SysMenuConvert.INSTANCE.convert(reqVO); SysMenuDO menu = SysMenuConvert.INSTANCE.convert(reqVO);
initMenuProperty(menu); initMenuProperty(menu);
menuMapper.insert(menu); menuMapper.insert(menu);
// 发送刷新消息
menuProducer.sendMenuRefreshMessage();
// 返回 // 返回
return menu.getId(); return menu.getId();
} }
@ -196,6 +201,7 @@ public class SysMenuServiceImpl implements SysMenuService {
* *
* @param menuId 菜单编号 * @param menuId 菜单编号
*/ */
@Transactional
public void deleteMenu(Long menuId) { public void deleteMenu(Long menuId) {
// 校验更新的菜单是否存在 // 校验更新的菜单是否存在
if (menuMapper.selectById(menuId) == null) { if (menuMapper.selectById(menuId) == null) {
@ -213,6 +219,15 @@ public class SysMenuServiceImpl implements SysMenuService {
menuMapper.deleteById(menuId); menuMapper.deleteById(menuId);
// 删除授予给角色的权限 // 删除授予给角色的权限
permissionService.processMenuDeleted(menuId); permissionService.processMenuDeleted(menuId);
// 发送刷新消息. 注意需要事务提交后在进行发送消息不然 db 还未提交结果缓存先刷新了
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
menuProducer.sendMenuRefreshMessage();
}
});
} }
@Override @Override