feat(menu): 优化菜单服务性能并新增医生排班功能

- 添加菜单缓存注解以提升查询性能
- 实现菜单完整路径计算优化,解决 N+1 查询问题
- 新增 selectAllMenus 方法供路径计算使用
- 添加今日医生排班查询功能
- 重构前端图标显示逻辑,使用 SVG 图标替代 Element 图标
- 添加前端菜单数据本地缓存机制
- 更新菜单管理界面的表单组件绑定方式
- 新增预约管理、门诊管理和药房管理路由配置
This commit is contained in:
2026-02-02 08:46:33 +08:00
parent 669d669422
commit 5534a71c7d
20 changed files with 1156 additions and 228 deletions

View File

@@ -140,4 +140,11 @@ public interface SysMenuMapper {
* @return 结果
*/
public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId);
/**
* 查询所有菜单信息
*
* @return 菜单列表
*/
public List<SysMenu> selectAllMenus();
}

View File

@@ -59,6 +59,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
* @return 菜单列表
*/
@Override
@org.springframework.cache.annotation.Cacheable(value = "menu", key = "'menuList:' + #userId + ':' + (#menu == null ? 'all' : #menu.menuName)")
public List<SysMenu> selectMenuList(SysMenu menu, Long userId) {
List<SysMenu> menuList = null;
// 管理员显示所有菜单信息
@@ -224,23 +225,133 @@ public class SysMenuServiceImpl implements ISysMenuService {
@Override
public List<SysMenu> buildMenuTreeWithFullPath(List<SysMenu> menus) {
List<SysMenu> menuTree = buildMenuTree(menus);
// 为每个菜单项添加完整路径
addFullPathToMenuTree(menuTree);
// 一次性获取所有菜单信息避免N+1查询问题
List<SysMenu> allMenus = menuMapper.selectAllMenus();
Map<Long, SysMenu> menuMap = allMenus.stream()
.collect(Collectors.toMap(SysMenu::getMenuId, menu -> menu));
// 为每个菜单项添加完整路径优化版本避免N+1查询问题
addFullPathsToMenuTreeOptimized(menuTree, menuMap);
return menuTree;
}
/**
* 为菜单树添加完整路径
* 为菜单树添加完整路径(优化版本)
*
* @param menus 菜单树
* @param menuMap 菜单映射
*/
private void addFullPathToMenuTree(List<SysMenu> menus) {
private void addFullPathsToMenuTreeOptimized(List<SysMenu> menus, Map<Long, SysMenu> menuMap) {
for (SysMenu menu : menus) {
// 计算当前菜单的完整路径
menu.setFullPath(getMenuFullPath(menu.getMenuId()));
// 使用优化的路径计算方法
menu.setFullPath(computeMenuFullPathOptimized(menu, menuMap));
// 递归处理子菜单
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
addFullPathToMenuTree(menu.getChildren());
addFullPathsToMenuTreeOptimized(menu.getChildren(), menuMap);
}
}
}
/**
* 优化的计算菜单完整路径方法
*
* @param menu 菜单
* @param menuMap 菜单映射
* @return 完整路径
*/
private String computeMenuFullPathOptimized(SysMenu menu, Map<Long, SysMenu> menuMap) {
if (menu == null || menu.getMenuId() == null) {
return "";
}
StringBuilder fullPath = new StringBuilder();
buildMenuPathOptimized(menu, fullPath, menuMap);
return normalizePath(fullPath.toString());
}
/**
* 优化的递归构建菜单路径
*
* @param menu 菜单信息
* @param path 路径构建器
* @param menuMap 菜单映射
*/
private void buildMenuPathOptimized(SysMenu menu, StringBuilder path, Map<Long, SysMenu> menuMap) {
if (menu == null || menu.getMenuId() == null) {
return;
}
// 如果不是根节点,则递归查找父节点
if (menu.getParentId() != null && menu.getParentId() > 0) {
SysMenu parentMenu = menuMap.get(menu.getParentId());
if (parentMenu != null) {
buildMenuPathOptimized(parentMenu, path, menuMap);
}
}
// 添加当前菜单的路径,避免双斜杠
String currentPath = normalizePathSegment(menu.getPath());
if (currentPath != null && !currentPath.isEmpty()) {
if (path.length() > 0) {
// 确保路径之间只有一个斜杠分隔符
if (path.charAt(path.length() - 1) != '/') {
path.append("/").append(currentPath);
} else {
path.append(currentPath);
}
} else {
// 对于第一个路径,直接追加
path.append(currentPath);
}
}
}
/**
* 递归收集菜单树中的所有菜单ID
*
* @param menus 菜单树
* @return 菜单ID列表
*/
private List<Long> collectMenuIds(List<SysMenu> menus) {
List<Long> menuIds = new ArrayList<>();
for (SysMenu menu : menus) {
menuIds.add(menu.getMenuId());
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
menuIds.addAll(collectMenuIds(menu.getChildren()));
}
}
return menuIds;
}
/**
* 批量获取菜单完整路径
*
* @param menuIds 菜单ID列表
* @return 菜单ID到完整路径的映射
*/
private Map<Long, String> batchGetMenuFullPaths(List<Long> menuIds) {
Map<Long, String> fullPathMap = new HashMap<>();
for (Long menuId : menuIds) {
// 使用缓存的getMenuFullPath方法
fullPathMap.put(menuId, getMenuFullPath(menuId));
}
return fullPathMap;
}
/**
* 为菜单树设置完整路径
*
* @param menus 菜单树
* @param fullPathMap 完整路径映射
*/
private void setFullPathsToMenuTree(List<SysMenu> menus, Map<Long, String> fullPathMap) {
for (SysMenu menu : menus) {
// 设置当前菜单的完整路径
menu.setFullPath(fullPathMap.get(menu.getMenuId()));
// 递归处理子菜单
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
setFullPathsToMenuTree(menu.getChildren(), fullPathMap);
}
}
}
@@ -299,6 +410,9 @@ public class SysMenuServiceImpl implements ISysMenuService {
* @return 结果
*/
@Override
@org.springframework.cache.annotation.Caching(evict = {
@org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true)
})
public int insertMenu(SysMenu menu) {
//路径Path唯一性判断
SysMenu sysMenu = menuMapper.selectMenuByPath(menu.getPath());
@@ -315,13 +429,17 @@ public class SysMenuServiceImpl implements ISysMenuService {
* @return 结果
*/
@Override
@org.springframework.cache.annotation.Caching(evict = {
@org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true),
@org.springframework.cache.annotation.CacheEvict(value = "menu", key = "'fullPath:' + #menu.menuId")
})
public int updateMenu(SysMenu menu) {
//路径Path唯一性判断排除当前菜单本身
String path = menu.getPath();
if (StringUtils.isNotBlank(path)) {
SysMenu sysMenu = menuMapper.selectMenuByPathExcludeId(menu.getPath(), menu.getMenuId());
if (sysMenu != null) {
log.warn("路由地址已存在 - menuId: {}, path: {}, 存在的menuId: {}",
log.warn("路由地址已存在 - menuId: {}, path: {}, 存在的menuId: {}",
menu.getMenuId(), menu.getPath(), sysMenu.getMenuId());
return -1; // 路由地址已存在
}
@@ -337,6 +455,10 @@ public class SysMenuServiceImpl implements ISysMenuService {
* @return 结果
*/
@Override
@org.springframework.cache.annotation.Caching(evict = {
@org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true),
@org.springframework.cache.annotation.CacheEvict(value = "menu", key = "'fullPath:' + #menuId")
})
public int deleteMenuById(Long menuId) {
return menuMapper.deleteMenuById(menuId);
}
@@ -533,6 +655,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
* @return 完整路径
*/
@Override
@org.springframework.cache.annotation.Cacheable(value = "menu", key = "'fullPath:' + #menuId", unless = "#result == null || #result.isEmpty()")
public String getMenuFullPath(Long menuId) {
SysMenu menu = menuMapper.selectMenuById(menuId);
if (menu == null) {

View File

@@ -274,4 +274,27 @@
where menu_id = #{menuId}
</delete>
<select id="selectAllMenus" resultMap="SysMenuResult">
select menu_id,
parent_id,
menu_name,
path,
component,
"query",
route_name,
is_frame,
is_cache,
menu_type,
visible,
status,
perms,
icon,
order_num,
create_time,
update_time,
remark
from sys_menu
order by parent_id, order_num
</select>
</mapper>