package com.iailab.module.system.service.app; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.iailab.framework.common.enums.CommonStatusEnum; import com.iailab.framework.common.util.object.BeanUtils; import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import com.iailab.module.system.controller.admin.permission.vo.menu.MenuSaveVO; import com.iailab.module.system.dal.dataobject.app.AppMenuDO; import com.iailab.module.system.dal.mysql.app.AppMenuMapper; import com.iailab.module.system.dal.redis.RedisKeyConstants; import com.iailab.module.system.enums.permission.MenuTypeEnum; import com.iailab.module.system.service.tenant.TenantService; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap; import static com.iailab.module.system.dal.dataobject.app.AppMenuDO.ID_ROOT; import static com.iailab.module.system.enums.ErrorCodeConstants.*; /** * èœå• Service 实现 * * @author iailab */ @Service @Slf4j public class AppMenuServiceImpl implements AppMenuService { @Resource private AppMenuMapper appMenuMapper; @Resource @Lazy // 延迟,é¿å…循环ä¾èµ–报错 private TenantService tenantService; @Override @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission", condition = "#createReqVO.permission != null") public Long createMenu(MenuSaveVO createReqVO) { // æ ¡éªŒçˆ¶èœå•å˜åœ¨ validateParentMenu(createReqVO.getParentId(), null); // æ ¡éªŒèœå•ï¼ˆè‡ªå·±ï¼‰ validateMenu(createReqVO.getParentId(), createReqVO.getName(), null); // æ’入数æ®åº“ AppMenuDO menu = BeanUtils.toBean(createReqVO, AppMenuDO.class); initMenuProperty(menu); appMenuMapper.insert(menu); // 返回 return menu.getId(); } @Override @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, allEntries = true) // allEntries 清空所有缓å˜ï¼Œå› 为 permission 如果å˜æ›´ï¼Œæ¶‰åŠåˆ°æ–°è€ä¸¤ä¸ª permission。直接清ç†ï¼Œç®€å•æœ‰æ•ˆ public void updateMenu(MenuSaveVO updateReqVO) { // æ ¡éªŒæ›´æ–°çš„èœå•æ˜¯å¦å˜åœ¨ if (appMenuMapper.selectById(updateReqVO.getId()) == null) { throw exception(MENU_NOT_EXISTS); } // æ ¡éªŒçˆ¶èœå•å˜åœ¨ validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId()); // æ ¡éªŒèœå•ï¼ˆè‡ªå·±ï¼‰ validateMenu(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId()); // 更新到数æ®åº“ AppMenuDO updateObj = BeanUtils.toBean(updateReqVO, AppMenuDO.class); initMenuProperty(updateObj); appMenuMapper.updateById(updateObj); } @Override @Transactional(rollbackFor = Exception.class) @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, allEntries = true) // allEntries 清空所有缓å˜ï¼Œå› 为æ¤æ—¶ä¸çŸ¥é“ id 对应的 permission 是多少。直接清ç†ï¼Œç®€å•æœ‰æ•ˆ public void deleteMenu(Long id) { // æ ¡éªŒæ˜¯å¦è¿˜æœ‰åèœå• if (appMenuMapper.selectCountByParentId(id) > 0) { throw exception(MENU_EXISTS_CHILDREN); } // æ ¡éªŒåˆ é™¤çš„èœå•æ˜¯å¦å˜åœ¨ if (appMenuMapper.selectById(id) == null) { throw exception(MENU_NOT_EXISTS); } // æ ‡è®°åˆ é™¤ appMenuMapper.deleteById(id); } @Override public List<AppMenuDO> getMenuList() { return appMenuMapper.selectList(); } @Override public List<AppMenuDO> getMenuList(Integer type) { LambdaQueryWrapper<AppMenuDO> queryWrapper = new LambdaQueryWrapper<>(); if (type == 1) { queryWrapper.eq(AppMenuDO::getType, type); } return appMenuMapper.selectList(queryWrapper); } @Override public List<AppMenuDO> getMenuListByTenant(Integer type) { // 查询所有èœå•ï¼Œå¹¶è¿‡æ»¤æŽ‰å…³é—的节点 List<AppMenuDO> menus = getMenuList(type); // å¼€å¯å¤šç§Ÿæˆ·çš„情况下,需è¦è¿‡æ»¤æŽ‰æœªå¼€é€šçš„èœå• tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId()))); return menus; } @Override public List<AppMenuDO> selectListByParentId(Collection<Long> parentIds) { return appMenuMapper.selectListByParentId(parentIds); } @Override public List<AppMenuDO> getMenuListByTenant(MenuListReqVO reqVO) { // 查询所有èœå•ï¼Œå¹¶è¿‡æ»¤æŽ‰å…³é—的节点 List<AppMenuDO> menus = getMenuList(reqVO); // å¼€å¯å¤šç§Ÿæˆ·çš„情况下,需è¦è¿‡æ»¤æŽ‰æœªå¼€é€šçš„èœå• tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId()))); return menus; } @Override public List<AppMenuDO> filterDisableMenus(List<AppMenuDO> menuList) { if (CollUtil.isEmpty(menuList)){ return Collections.emptyList(); } Map<Long, AppMenuDO> menuMap = convertMap(menuList, AppMenuDO::getId); // é历 menu èœå•ï¼ŒæŸ¥æ‰¾ä¸æ˜¯ç¦ç”¨çš„èœå•ï¼Œæ·»åŠ 到 enabledMenus 结果 List<AppMenuDO> enabledMenus = new ArrayList<>(); Set<Long> disabledMenuCache = new HashSet<>(); // å˜ä¸‹é€’å½’æœç´¢è¿‡è¢«ç¦ç”¨çš„èœå•ï¼Œé˜²æ¢é‡å¤çš„æœç´¢ for (AppMenuDO menu : menuList) { if (isMenuDisabled(menu, menuMap, disabledMenuCache)) { continue; } enabledMenus.add(menu); } return enabledMenus; } private boolean isMenuDisabled(AppMenuDO node, Map<Long, AppMenuDO> menuMap, Set<Long> disabledMenuCache) { // 如果已ç»åˆ¤å®šæ˜¯ç¦ç”¨çš„èŠ‚ç‚¹ï¼Œç›´æŽ¥ç»“æŸ if (disabledMenuCache.contains(node.getId())) { return true; } // 1. é历到 parentId ä¸ºæ ¹èŠ‚ç‚¹ï¼Œåˆ™æ— éœ€åˆ¤æ– Long parentId = node.getParentId(); if (ObjUtil.equal(parentId, ID_ROOT)) { if (CommonStatusEnum.isDisable(node.getStatus())) { disabledMenuCache.add(node.getId()); return true; } return false; } // 2. 继ç»é历 parent 节点 AppMenuDO parent = menuMap.get(parentId); if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) { disabledMenuCache.add(node.getId()); return true; } return false; } @Override public List<AppMenuDO> getMenuList(MenuListReqVO reqVO) { return appMenuMapper.selectList(reqVO); } @Override @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission") public List<Long> getMenuIdListByPermissionFromCache(String permission) { List<AppMenuDO> menus = appMenuMapper.selectListByPermission(permission); return convertList(menus, AppMenuDO::getId); } @Override public AppMenuDO getMenu(Long id) { return appMenuMapper.selectById(id); } @Override public List<AppMenuDO> getMenuList(Collection<Long> ids) { // 当 ids 为空时,返回一个空的实例对象 if (CollUtil.isEmpty(ids)) { return Lists.newArrayList(); } return appMenuMapper.selectBatchIds(ids); } /** * æ ¡éªŒçˆ¶èœå•æ˜¯å¦åˆæ³• * <p> * 1. ä¸èƒ½è®¾ç½®è‡ªå·±ä¸ºçˆ¶èœå• * 2. 父èœå•ä¸å˜åœ¨ * 3. 父èœå•å¿…须是 {@link MenuTypeEnum#MENU} èœå•ç±»åž‹ * * @param parentId 父èœå•ç¼–å· * @param childId 当å‰èœå•ç¼–å· */ @VisibleForTesting void validateParentMenu(Long parentId, Long childId) { if (parentId == null || ID_ROOT.equals(parentId)) { return; } // ä¸èƒ½è®¾ç½®è‡ªå·±ä¸ºçˆ¶èœå• if (parentId.equals(childId)) { throw exception(MENU_PARENT_ERROR); } AppMenuDO menu = appMenuMapper.selectById(parentId); // 父èœå•ä¸å˜åœ¨ if (menu == null) { throw exception(MENU_PARENT_NOT_EXISTS); } // 父èœå•å¿…须是目录或者èœå•ç±»åž‹ if (!MenuTypeEnum.DIR.getType().equals(menu.getType()) && !MenuTypeEnum.MENU.getType().equals(menu.getType())) { throw exception(MENU_PARENT_NOT_DIR_OR_MENU); } } /** * æ ¡éªŒèœå•æ˜¯å¦åˆæ³• * <p> * 1. æ ¡éªŒç›¸åŒçˆ¶èœå•ç¼–å·ä¸‹ï¼Œæ˜¯å¦å˜åœ¨ç›¸åŒçš„èœå•å * * @param name èœå•åå— * @param parentId 父èœå•ç¼–å· * @param id èœå•ç¼–å· */ @VisibleForTesting void validateMenu(Long parentId, String name, Long id) { AppMenuDO menu = appMenuMapper.selectByParentIdAndName(parentId, name); if (menu == null) { return; } // 如果 id 为空,说明ä¸ç”¨æ¯”较是å¦ä¸ºç›¸åŒ id çš„èœå• if (id == null) { throw exception(MENU_NAME_DUPLICATE); } if (!menu.getId().equals(id)) { throw exception(MENU_NAME_DUPLICATE); } } /** * åˆå§‹åŒ–èœå•çš„通用属性。 * <p> * 例如说,åªæœ‰ç›®å½•æˆ–者èœå•ç±»åž‹çš„èœå•ï¼Œæ‰è®¾ç½® icon * * @param menu èœå• */ private void initMenuProperty(AppMenuDO menu) { // èœå•ä¸ºæŒ‰é’®ç±»åž‹æ—¶ï¼Œæ— 需 componentã€iconã€path 属性,进行置空 if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) { menu.setComponent(""); menu.setComponentName(""); menu.setIcon(""); menu.setPath(""); } } }