缓存改造:Menu 使用 Redis 作为缓存

This commit is contained in:
YunaiV 2023-02-25 22:55:26 +08:00
parent 2db6a4510c
commit dfb1bdb9fb
8 changed files with 18 additions and 204 deletions

View File

@ -25,4 +25,7 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus())); .eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
} }
default List<MenuDO> selectListByPermission(String permission) {
return selectList(MenuDO::getPermission, permission);
}
} }

View File

@ -62,4 +62,12 @@ public interface RedisKeyConstants {
*/ */
String DEPT_CHILDREN_ID_LIST_EXPIRE = "30s"; String DEPT_CHILDREN_ID_LIST_EXPIRE = "30s";
/**
* 拥有权限对应的菜单编号数组的缓存
*
* KEY 格式permission_menu_ids::{permission}
* 数据类型String 菜单编号数组
*/
String PERMISSION_MENU_ID_LIST = "permission_menu_ids";
} }

View File

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

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.system.mq.message.permission;
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 菜单数据刷新 Message
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class MenuRefreshMessage extends AbstractChannelMessage {
@Override
public String getChannel() {
return "system.menu.refresh";
}
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.system.mq.producer.permission;
import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Menu 菜单相关消息的 Producer
*/
@Component
public class MenuProducer {
@Resource
private RedisMQTemplate redisMQTemplate;
/**
* 发送 {@link MenuRefreshMessage} 消息
*/
public void sendMenuRefreshMessage() {
MenuRefreshMessage message = new MenuRefreshMessage();
redisMQTemplate.send(message);
}
}

View File

@ -15,11 +15,6 @@ import java.util.List;
*/ */
public interface MenuService { public interface MenuService {
/**
* 初始化菜单的本地缓存
*/
void initLocalCache();
/** /**
* 创建菜单 * 创建菜单
* *
@ -66,30 +61,6 @@ public interface MenuService {
*/ */
List<MenuDO> getMenuList(MenuListReqVO reqVO); List<MenuDO> getMenuList(MenuListReqVO reqVO);
/**
* 获得所有菜单从缓存中
*
* 任一参数为空时则返回为空
*
* @param menuTypes 菜单类型数组
* @param menusStatuses 菜单状态数组
* @return 菜单列表
*/
List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses);
/**
* 获得指定编号的菜单数组从缓存中
*
* 任一参数为空时则返回为空
*
* @param menuIds 菜单编号数组
* @param menuTypes 菜单类型数组
* @param menusStatuses 菜单状态数组
* @return 菜单数组
*/
List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
Collection<Integer> menusStatuses);
/** /**
* 获得权限对应的菜单数组 * 获得权限对应的菜单数组
* *

View File

@ -1,34 +1,25 @@
package cn.iocoder.yudao.module.system.service.permission; package cn.iocoder.yudao.module.system.service.permission;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUpdateReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUpdateReqVO;
import cn.iocoder.yudao.module.system.convert.permission.MenuConvert; import cn.iocoder.yudao.module.system.convert.permission.MenuConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum; import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
import cn.iocoder.yudao.module.system.service.tenant.TenantService; import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
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 javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.*; import java.util.Collection;
import java.util.stream.Collectors; import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT; import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
@ -43,26 +34,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j @Slf4j
public class MenuServiceImpl implements MenuService { public class MenuServiceImpl implements MenuService {
/**
* 菜单缓存
* key菜单编号
*
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/
@Getter
@Setter
private volatile Map<Long, MenuDO> menuCache;
/**
* 权限与菜单缓存
* key权限 {@link MenuDO#getPermission()}
* valueMenuDO 数组因为一个权限可能对应多个 MenuDO 对象
*
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/
@Getter
@Setter
private volatile Multimap<String, MenuDO> permissionMenuCache;
@Resource @Resource
private MenuMapper menuMapper; private MenuMapper menuMapper;
@Resource @Resource
@ -71,32 +42,6 @@ public class MenuServiceImpl implements MenuService {
@Lazy // 延迟避免循环依赖报错 @Lazy // 延迟避免循环依赖报错
private TenantService tenantService; private TenantService tenantService;
@Resource
private MenuProducer menuProducer;
/**
* 初始化 {@link #menuCache} {@link #permissionMenuCache} 缓存
*/
@Override
@PostConstruct
public synchronized void initLocalCache() {
// 第一步查询数据
List<MenuDO> menuList = menuMapper.selectList();
log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
// 第二步构建缓存
ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
menuList.forEach(menuDO -> {
menuCacheBuilder.put(menuDO.getId(), menuDO);
if (StrUtil.isNotEmpty(menuDO.getPermission())) { // 会存在 permission null 的情况导致 put NPE 异常
permMenuCacheBuilder.put(menuDO.getPermission(), menuDO);
}
});
menuCache = menuCacheBuilder.build();
permissionMenuCache = permMenuCacheBuilder.build();
}
@Override @Override
public Long createMenu(MenuCreateReqVO reqVO) { public Long createMenu(MenuCreateReqVO reqVO) {
// 校验父菜单存在 // 校验父菜单存在
@ -108,8 +53,6 @@ public class MenuServiceImpl implements MenuService {
MenuDO menu = MenuConvert.INSTANCE.convert(reqVO); MenuDO menu = MenuConvert.INSTANCE.convert(reqVO);
initMenuProperty(menu); initMenuProperty(menu);
menuMapper.insert(menu); menuMapper.insert(menu);
// 发送刷新消息
menuProducer.sendMenuRefreshMessage();
// 返回 // 返回
return menu.getId(); return menu.getId();
} }
@ -129,8 +72,6 @@ public class MenuServiceImpl implements MenuService {
MenuDO updateObject = MenuConvert.INSTANCE.convert(reqVO); MenuDO updateObject = MenuConvert.INSTANCE.convert(reqVO);
initMenuProperty(updateObject); initMenuProperty(updateObject);
menuMapper.updateById(updateObject); menuMapper.updateById(updateObject);
// 发送刷新消息
menuProducer.sendMenuRefreshMessage();
} }
@Override @Override
@ -148,15 +89,6 @@ public class MenuServiceImpl implements MenuService {
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
@ -178,33 +110,9 @@ public class MenuServiceImpl implements MenuService {
} }
@Override @Override
public List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) { @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission")
// 任一一个参数为空则返回空
if (CollectionUtils.isAnyEmpty(menuTypes, menusStatuses)) {
return Collections.emptyList();
}
// 创建新数组避免缓存被修改
return menuCache.values().stream().filter(menu -> menuTypes.contains(menu.getType())
&& menusStatuses.contains(menu.getStatus()))
.collect(Collectors.toList());
}
@Override
public List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
Collection<Integer> menusStatuses) {
// 任一一个参数为空则返回空
if (CollectionUtils.isAnyEmpty(menuIds, menuTypes, menusStatuses)) {
return Collections.emptyList();
}
return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getId())
&& menuTypes.contains(menu.getType())
&& menusStatuses.contains(menu.getStatus()))
.collect(Collectors.toList());
}
@Override
public List<MenuDO> getMenuListByPermissionFromCache(String permission) { public List<MenuDO> getMenuListByPermissionFromCache(String permission) {
return new ArrayList<>(permissionMenuCache.get(permission)); return menuMapper.selectListByPermission(permission);
} }
@Override @Override

View File

@ -190,7 +190,7 @@ yudao:
- tmp_report_data_1 - tmp_report_data_1
- tmp_report_data_income - tmp_report_data_income
ignore-caches: ignore-caches:
- demo - permission_menu_ids
sms-code: # 短信验证码相关的配置项 sms-code: # 短信验证码相关的配置项
expire-times: 10m expire-times: 10m
send-frequency: 1m send-frequency: 1m