diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java index 451cd61b3..83191914a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java @@ -1,6 +1,8 @@ package com.cf.imes.module.system.service.permission; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; import com.cf.imes.framework.common.util.object.BeanUtils; import com.cf.imes.framework.organ.core.aop.OrganIgnore; import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO; @@ -8,13 +10,18 @@ import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSaveVO; import com.cf.imes.module.system.dal.dataobject.permission.MenuDO; import com.cf.imes.module.system.dal.mysql.permission.MenuMapper; import com.cf.imes.module.system.dal.redis.RedisKeyConstants; +import com.cf.imes.module.system.dal.redis.RedisRefreshChannelTopicConstants; import com.cf.imes.module.system.enums.permission.MenuTypeEnum; import com.cf.imes.module.system.service.organ.OrganService; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +29,7 @@ import javax.annotation.Resource; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.CompletableFuture; import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList; @@ -44,6 +52,9 @@ public class MenuServiceImpl implements MenuService { @Lazy // 延迟,避免循环依赖报错 private OrganService organService; + @Autowired + private RedisTemplate redisTemplate; + @Override @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission", condition = "#createReqVO.permission != null") @@ -57,8 +68,13 @@ public class MenuServiceImpl implements MenuService { MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class); initMenuProperty(menu); menuMapper.insert(menu); + + Long menuId = menu.getId(); + // 清理缓存 + flushCacheWhenOperateMenu(menuId, createReqVO.getPermission(), null); + // 返回 - return menu.getId(); + return menuId; } @Override @@ -66,7 +82,8 @@ public class MenuServiceImpl implements MenuService { allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效 public void updateMenu(MenuSaveVO updateReqVO) { // 校验更新的菜单是否存在 - if (menuMapper.selectById(updateReqVO.getId()) == null) { + MenuDO menuDO = menuMapper.selectById(updateReqVO.getId()); + if (menuDO == null) { throw exception(MENU_NOT_EXISTS); } // 校验父菜单存在 @@ -78,6 +95,13 @@ public class MenuServiceImpl implements MenuService { MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class); initMenuProperty(updateObj); menuMapper.updateById(updateObj); + + // 权限串发生改变清空缓存 + String oldPermission = menuDO.getPermission(); + String newPermission = updateReqVO.getPermission(); + if (ObjectUtil.notEqual(oldPermission, newPermission)) { + flushCacheWhenOperateMenu(menuDO.getId(), newPermission, oldPermission); + } } @Override @@ -90,13 +114,76 @@ public class MenuServiceImpl implements MenuService { throw exception(MENU_EXISTS_CHILDREN); } // 校验删除的菜单是否存在 - if (menuMapper.selectById(id) == null) { + MenuDO menuDO = menuMapper.selectById(id); + if (menuDO == null) { throw exception(MENU_NOT_EXISTS); } // 标记删除 menuMapper.deleteById(id); // 删除授予给角色的权限 permissionService.processMenuDeleted(id); + + // 清理缓存 + flushCacheWhenOperateMenu(id, null, menuDO.getPermission()); + } + + /** + * 菜单增删改时清理缓存 + * + * @param menuId 菜单id + * @param newPermission 新权限串 + * @param oldPermission 旧权限串 + */ + private void flushCacheWhenOperateMenu(Long menuId, String newPermission, String oldPermission) { + CompletableFuture.runAsync(() -> { + if (ObjectUtil.isNotNull(oldPermission)) { + // 移除 USER_ROLE_ID_LIST:*:旧的permission + String oldMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(":*:%s")), oldPermission); + Long oldBatchDelPerNum = scanAndDelKeys(oldMenuPerPattern); + log.info("[updateMenu] 批量删除redis[{}]数量:{}", oldMenuPerPattern, oldBatchDelPerNum); + } + + if (ObjectUtil.isNotNull(newPermission)) { + // 移除 USER_ROLE_ID_LIST:*:新的permission + String newMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(":*:%s")), newPermission); + Long newBatchDelPerNum = scanAndDelKeys(newMenuPerPattern); + log.info("[updateMenu] 批量删除redis[{}]数量:{}", newMenuPerPattern, newBatchDelPerNum); + } + + if (ObjectUtil.isNotNull(menuId)) { + // 移除 MENU_ROLE_ID_LIST:*:menuId + String menuRolePattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.MENU_ROLE_ID_LIST).append(":*:%s")), menuId); + Long batchDelRoleNum = scanAndDelKeys(menuRolePattern); + log.info("[deleteMenu] 批量删除redis[{}]数量:{}", menuRolePattern, batchDelRoleNum); + } + + // 清空本地缓存 + redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); + }).exceptionally(e -> { + log.error("[deleteMenu] redis 清空PERMISSION_MENU_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); + return null; + }); + } + + + /** + * scan所有匹配的redis key并删除 + * + * @param keyPattern 模糊匹配的表达式 + */ + private Long scanAndDelKeys(String keyPattern) { + // 根据keyPattern scan匹配的redis key + List delKeys = new ArrayList<>(); + Cursor cursor = redisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build()); + while (cursor.hasNext()) { + delKeys.add(cursor.next()); + } + cursor.close(); + // 删除匹配到的key + if (CollectionUtil.isNotEmpty(delKeys)) { + return redisTemplate.delete(delKeys); + } + return 0L; } @Override diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java index 3fcfe7e05..804c3d7fe 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java @@ -219,7 +219,7 @@ public class PermissionServiceImpl implements PermissionService { } redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); }).exceptionally(e -> { - log.error("[assignRoleMenu] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); + log.error("[assignRoleMenu] redis 清空MENU_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); return null; }); } @@ -240,7 +240,7 @@ public class PermissionServiceImpl implements PermissionService { cursor.close(); // 删除匹配到的key if (CollectionUtil.isNotEmpty(delKeys)) { - redisTemplate.delete(delKeys); + return redisTemplate.delete(delKeys); } return 0L; } @@ -397,7 +397,7 @@ public class PermissionServiceImpl implements PermissionService { } redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); }).exceptionally(e -> { - log.error("[batchAssignRoleUsers] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); + log.error("[batchAssignRoleUsers] redis 清空USER_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); return null; }); } @@ -479,7 +479,7 @@ public class PermissionServiceImpl implements PermissionService { redisTemplate.delete(String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":%s:%s")), organId, userId)); redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); }).exceptionally(e -> { - log.error("[assignUserRole] redis 清空USER_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); + log.error("[assignUserRole] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); return null; }); }