增加角色的本地缓存刷新

This commit is contained in:
YunaiV 2021-01-23 18:17:48 +08:00
parent 545e9d2a0f
commit 1ecbe5aa61
12 changed files with 166 additions and 20 deletions

View File

@ -4,10 +4,13 @@ import cn.iocoder.dashboard.common.pojo.PageParam;
import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List;
/** /**
* MyBatis Plus BaseMapper 的基础上拓展提供更多的能力 * MyBatis Plus BaseMapper 的基础上拓展提供更多的能力
*/ */
@ -21,4 +24,8 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
} }
default List<T> selectList() {
return selectList(new QueryWrapper<>());
}
} }

View File

@ -1,17 +1,17 @@
package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission; package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission;
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX; import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuListReqVO; import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuListReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.Date;
import java.util.List; import java.util.List;
@Mapper @Mapper
public interface SysMenuMapper extends BaseMapper<SysMenuDO> { public interface SysMenuMapper extends BaseMapperX<SysMenuDO> {
default SysMenuDO selectByParentIdAndName(Long parentId, String name) { default SysMenuDO selectByParentIdAndName(Long parentId, String name) {
return selectOne(new QueryWrapper<SysMenuDO>().eq("parent_id", parentId) return selectOne(new QueryWrapper<SysMenuDO>().eq("parent_id", parentId)
@ -27,10 +27,6 @@ public interface SysMenuMapper extends BaseMapper<SysMenuDO> {
.eqIfPresent("status", reqVO.getStatus())); .eqIfPresent("status", reqVO.getStatus()));
} }
default List<SysMenuDO> selectList() {
return selectList(new QueryWrapper<>());
}
default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) { default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
return selectOne(new QueryWrapper<SysMenuDO>().select("id") return selectOne(new QueryWrapper<SysMenuDO>().select("id")
.gt("update_time", maxUpdateTime).last("LIMIT 1")) != null; .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null;

View File

@ -1,20 +1,22 @@
package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission; package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission;
import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX; import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.dashboard.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleExportReqVO; import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleExportReqVO;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRolePageReqVO; import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRolePageReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List; import java.util.List;
@Mapper @Mapper
public interface SysRoleMapper extends BaseMapper<SysRoleDO> { public interface SysRoleMapper extends BaseMapperX<SysRoleDO> {
default IPage<SysRoleDO> selectPage(SysRolePageReqVO reqVO) { default IPage<SysRoleDO> selectPage(SysRolePageReqVO reqVO) {
return selectPage(MyBatisUtils.buildPage(reqVO), return selectPage(MyBatisUtils.buildPage(reqVO),
@ -43,4 +45,9 @@ public interface SysRoleMapper extends BaseMapper<SysRoleDO> {
return selectList(new QueryWrapperX<SysRoleDO>().in("status", statuses)); return selectList(new QueryWrapperX<SysRoleDO>().in("status", statuses));
} }
default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
return selectOne(new QueryWrapper<SysRoleDO>().select("id")
.gt("update_time", maxUpdateTime).last("LIMIT 1")) != null;
}
} }

View File

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

View File

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

View File

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

View File

@ -88,7 +88,7 @@ public class SysDeptServiceImpl implements SysDeptService {
parentDeptCache = parentBuilder.build(); parentDeptCache = parentBuilder.build();
assert deptList.size() > 0; // 断言避免告警 assert deptList.size() > 0; // 断言避免告警
maxUpdateTime = deptList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); maxUpdateTime = deptList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
log.info("[init][初始化 Dept 数量为 {}]", deptList.size()); log.info("[initLocalCache][初始化 Dept 数量为 {}]", deptList.size());
} }
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
@ -201,7 +201,6 @@ public class SysDeptServiceImpl implements SysDeptService {
// 删除部门 // 删除部门
deptMapper.deleteById(id); deptMapper.deleteById(id);
// TODO 需要处理下与角色的数据权限关联等做数据权限一起处理下 // TODO 需要处理下与角色的数据权限关联等做数据权限一起处理下
// 发送刷新消息 // 发送刷新消息
deptProducer.sendDeptRefreshMessage(); deptProducer.sendDeptRefreshMessage();
} }

View File

@ -100,7 +100,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
valueDictDataCache = valueDictDataBuilder.build(); valueDictDataCache = valueDictDataBuilder.build();
assert dataList.size() > 0; // 断言避免告警 assert dataList.size() > 0; // 断言避免告警
maxUpdateTime = dataList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); maxUpdateTime = dataList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
log.info("[init][缓存字典数据,数量为:{}]", dataList.size()); log.info("[initLocalCache][缓存字典数据,数量为:{}]", dataList.size());
} }
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)

View File

@ -20,9 +20,9 @@ import java.util.Set;
public interface SysRoleService { public interface SysRoleService {
/** /**
* 初始化 * 初始化角色的本地缓存
*/ */
void init(); void initLocalCache();
/** /**
* 获得角色从缓存中 * 获得角色从缓存中

View File

@ -98,7 +98,7 @@ public class SysMenuServiceImpl implements SysMenuService {
permMenuCache = permMenuCacheBuilder.build(); permMenuCache = permMenuCacheBuilder.build();
assert menuList.size() > 0; // 断言避免告警 assert menuList.size() > 0; // 断言避免告警
maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
log.info("[init][缓存菜单,数量为:{}]", menuList.size()); log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
} }
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)

View File

@ -78,7 +78,7 @@ public class SysPermissionServiceImpl implements SysPermissionService {
}); });
roleMenuCache = roleMenuCacheBuilder.build(); roleMenuCache = roleMenuCacheBuilder.build();
menuRoleCache = menuRoleCacheBuilder.build(); menuRoleCache = menuRoleCacheBuilder.build();
log.info("[init][初始化角色与菜单的关联数量为 {}]", roleMenuList.size()); log.info("[initLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
} }
@Override @Override

View File

@ -1,9 +1,11 @@
package cn.iocoder.dashboard.modules.system.service.permission.impl; package cn.iocoder.dashboard.modules.system.service.permission.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
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.common.pojo.PageResult; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleCreateReqVO; import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleExportReqVO; import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRoleExportReqVO;
import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRolePageReqVO; import cn.iocoder.dashboard.modules.system.controller.permission.vo.role.SysRolePageReqVO;
@ -13,14 +15,18 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysRoleMappe
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
import cn.iocoder.dashboard.modules.system.enums.permission.RoleCodeEnum; import cn.iocoder.dashboard.modules.system.enums.permission.RoleCodeEnum;
import cn.iocoder.dashboard.modules.system.enums.permission.SysRoleTypeEnum; import cn.iocoder.dashboard.modules.system.enums.permission.SysRoleTypeEnum;
import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysRoleProducer;
import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService; import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
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.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -39,6 +45,12 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
@Slf4j @Slf4j
public class SysRoleServiceImpl implements SysRoleService { public class SysRoleServiceImpl implements SysRoleService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/** /**
* 角色缓存 * 角色缓存
* key角色编号 {@link SysRoleDO#getId()} * key角色编号 {@link SysRoleDO#getId()}
@ -46,6 +58,10 @@ public class SysRoleServiceImpl implements SysRoleService {
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向 * 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/ */
private volatile Map<Long, SysRoleDO> roleCache; private volatile Map<Long, SysRoleDO> roleCache;
/**
* 缓存菜单的最大更新时间用于后续的增量轮询判断是否有更新
*/
private volatile Date maxUpdateTime;
@Resource @Resource
private SysPermissionService permissionService; private SysPermissionService permissionService;
@ -53,19 +69,54 @@ public class SysRoleServiceImpl implements SysRoleService {
@Resource @Resource
private SysRoleMapper roleMapper; private SysRoleMapper roleMapper;
@Resource
private SysRoleProducer roleProducer;
/** /**
* 初始化 {@link #roleCache} 缓存 * 初始化 {@link #roleCache} 缓存
*/ */
@Override @Override
@PostConstruct @PostConstruct
public void init() { public void initLocalCache() {
// 从数据库中读取 // 获取菜单列表如果有更新
List<SysRoleDO> roleDOList = roleMapper.selectList(null); List<SysRoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(roleList)) {
return;
}
// 写入缓存 // 写入缓存
ImmutableMap.Builder<Long, SysRoleDO> builder = ImmutableMap.builder(); ImmutableMap.Builder<Long, SysRoleDO> builder = ImmutableMap.builder();
roleDOList.forEach(sysRoleDO -> builder.put(sysRoleDO.getId(), sysRoleDO)); roleList.forEach(sysRoleDO -> builder.put(sysRoleDO.getId(), sysRoleDO));
roleCache = builder.build(); roleCache = builder.build();
log.info("[init][初始化 Role 数量为 {}]", roleDOList.size()); assert roleList.size() > 0; // 断言避免告警
maxUpdateTime = roleList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
log.info("[initLocalCache][初始化 Role 数量为 {}]", roleList.size());
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initLocalCache();
}
/**
* 如果菜单发生变化从数据库中获取最新的全量菜单
* 如果未发生变化则返回空
*
* @param maxUpdateTime 当前菜单的最大更新时间
* @return 菜单列表
*/
private List<SysRoleDO> loadRoleIfUpdate(Date maxUpdateTime) {
// 第一步判断是否要更新
if (maxUpdateTime == null) { // 如果更新时间为空说明 DB 一定有新数据
log.info("[loadRoleIfUpdate][首次加载全量菜单]");
} else { // 判断数据库中是否有更新的菜单
if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
return null;
}
log.info("[loadRoleIfUpdate][增量加载全量菜单]");
}
// 第二步如果有更新则从数据库加载所有菜单
return roleMapper.selectList();
} }
@Override @Override
@ -104,6 +155,8 @@ public class SysRoleServiceImpl implements SysRoleService {
role.setType(SysRoleTypeEnum.CUSTOM.getType()); role.setType(SysRoleTypeEnum.CUSTOM.getType());
role.setStatus(CommonStatusEnum.ENABLE.getStatus()); role.setStatus(CommonStatusEnum.ENABLE.getStatus());
roleMapper.insert(role); roleMapper.insert(role);
// 发送刷新消息
roleProducer.sendRoleRefreshMessage();
// 返回 // 返回
return role.getId(); return role.getId();
} }
@ -117,6 +170,8 @@ public class SysRoleServiceImpl implements SysRoleService {
// 更新到数据库 // 更新到数据库
SysRoleDO updateObject = SysRoleConvert.INSTANCE.convert(reqVO); SysRoleDO updateObject = SysRoleConvert.INSTANCE.convert(reqVO);
roleMapper.updateById(updateObject); roleMapper.updateById(updateObject);
// 发送刷新消息
roleProducer.sendRoleRefreshMessage();
} }
@Override @Override
@ -128,6 +183,15 @@ public class SysRoleServiceImpl implements SysRoleService {
roleMapper.deleteById(id); roleMapper.deleteById(id);
// 删除相关数据 // 删除相关数据
permissionService.processRoleDeleted(id); permissionService.processRoleDeleted(id);
// 发送刷新消息. 注意需要事务提交后在进行发送刷新消息不然 db 还未提交结果缓存先刷新了
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
roleProducer.sendRoleRefreshMessage();
}
});
} }
@Override @Override