From 62f6362d61bf67b0e73a4d31e584b4ca65d220ca Mon Sep 17 00:00:00 2001 From: Joshi <3040996759@qq.com> Date: Wed, 1 Jul 2026 13:23:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(menu):=20=E6=B7=BB=E5=8A=A0=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=94=AF=E4=B8=80=E6=A0=87=E8=AF=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 menuKey 字段到 SysMenu 实体类 - 在菜单服务接口和服务实现中添加 checkMenuKeyUnique、selectMenuByMenuKey 和 generateMenuKeys 方法 - 在控制器中增加 getByKey 和 generateMenuKeys 接口用于通过唯一标识获取菜单和批量生成菜单标识 - 在数据库映射中添加对 menuKey 字段的支持 - 在新增和修改菜单时验证 menuKey 唯一性 - 实现自动生成菜单唯一标识的功能,支持基于路径或菜单名称生成 --- .../controller/system/SysMenuController.java | 29 +++++++ .../common/core/domain/entity/SysMenu.java | 6 ++ .../com/klp/system/mapper/SysMenuMapper.java | 12 +++ .../klp/system/service/ISysMenuService.java | 23 +++++ .../service/impl/SysMenuServiceImpl.java | 86 +++++++++++++++++++ .../resources/mapper/system/SysMenuMapper.xml | 4 +- 6 files changed, 159 insertions(+), 1 deletion(-) diff --git a/klp-admin/src/main/java/com/klp/web/controller/system/SysMenuController.java b/klp-admin/src/main/java/com/klp/web/controller/system/SysMenuController.java index 10a2dd9a2..7f3cffe50 100644 --- a/klp-admin/src/main/java/com/klp/web/controller/system/SysMenuController.java +++ b/klp-admin/src/main/java/com/klp/web/controller/system/SysMenuController.java @@ -84,6 +84,8 @@ public class SysMenuController extends BaseController { public R add(@Validated @RequestBody SysMenu menu) { if (!menuService.checkMenuNameUnique(menu)) { return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (StringUtils.isNotBlank(menu.getMenuKey()) && !menuService.checkMenuKeyUnique(menu)) { + return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单唯一标识'" + menu.getMenuKey() + "'已存在"); } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); } @@ -99,6 +101,8 @@ public class SysMenuController extends BaseController { public R edit(@Validated @RequestBody SysMenu menu) { if (!menuService.checkMenuNameUnique(menu)) { return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (StringUtils.isNotBlank(menu.getMenuKey()) && !menuService.checkMenuKeyUnique(menu)) { + return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单唯一标识'" + menu.getMenuKey() + "'已存在"); } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); } else if (menu.getMenuId().equals(menu.getParentId())) { @@ -124,4 +128,29 @@ public class SysMenuController extends BaseController { } return toAjax(menuService.deleteMenuById(menuId)); } + + /** + * 根据 menuKey 获取菜单信息(供流程图等外部固定引用跳转) + * + * @param menuKey 菜单唯一标识 + */ + @GetMapping("/getByKey") + public R getByKey(@RequestParam String menuKey) { + SysMenu menu = menuService.selectMenuByMenuKey(menuKey); + if (menu == null) { + return R.fail("菜单唯一标识 '" + menuKey + "' 不存在"); + } + return R.ok(menu); + } + + /** + * 为现有菜单自动生成 menu_key(仅处理 menu_key 为空且类型为 C/M 的菜单) + */ + @SaCheckPermission("system:menu:edit") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PostMapping("/generateMenuKeys") + public R generateMenuKeys() { + int count = menuService.generateMenuKeys(); + return R.ok("成功为 " + count + " 个菜单生成 menu_key"); + } } diff --git a/klp-common/src/main/java/com/klp/common/core/domain/entity/SysMenu.java b/klp-common/src/main/java/com/klp/common/core/domain/entity/SysMenu.java index c214dc446..aa5ab2cae 100644 --- a/klp-common/src/main/java/com/klp/common/core/domain/entity/SysMenu.java +++ b/klp-common/src/main/java/com/klp/common/core/domain/entity/SysMenu.java @@ -35,6 +35,12 @@ public class SysMenu extends TreeEntity { @Size(min = 0, max = 50, message = "菜单名称长度不能超过{max}个字符") private String menuName; + /** + * 菜单唯一标识(外部固定引用,不受目录变动影响) + */ + @Size(min = 0, max = 64, message = "菜单唯一标识长度不能超过{max}个字符") + private String menuKey; + /** * 显示顺序 */ diff --git a/klp-system/src/main/java/com/klp/system/mapper/SysMenuMapper.java b/klp-system/src/main/java/com/klp/system/mapper/SysMenuMapper.java index 6eb020063..3359a1922 100644 --- a/klp-system/src/main/java/com/klp/system/mapper/SysMenuMapper.java +++ b/klp-system/src/main/java/com/klp/system/mapper/SysMenuMapper.java @@ -79,4 +79,16 @@ public interface SysMenuMapper extends BaseMapperPlus selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + /** + * 根据 menuKey 查询菜单 + * + * @param menuKey 菜单唯一标识 + * @return 菜单信息 + */ + default SysMenu selectMenuByMenuKey(String menuKey) { + LambdaQueryWrapper lqw = new LambdaQueryWrapper() + .eq(SysMenu::getMenuKey, menuKey); + return this.selectOne(lqw); + } + } diff --git a/klp-system/src/main/java/com/klp/system/service/ISysMenuService.java b/klp-system/src/main/java/com/klp/system/service/ISysMenuService.java index 73dbc49d3..4744a786f 100644 --- a/klp-system/src/main/java/com/klp/system/service/ISysMenuService.java +++ b/klp-system/src/main/java/com/klp/system/service/ISysMenuService.java @@ -134,4 +134,27 @@ public interface ISysMenuService { * @return 结果 */ boolean checkMenuNameUnique(SysMenu menu); + + /** + * 校验 menu_key 是否唯一 + * + * @param menu 菜单信息 + * @return 结果 true=唯一 false=不唯一 + */ + boolean checkMenuKeyUnique(SysMenu menu); + + /** + * 根据 menuKey 查询菜单 + * + * @param menuKey 菜单唯一标识 + * @return 菜单信息 + */ + SysMenu selectMenuByMenuKey(String menuKey); + + /** + * 为现有菜单自动生成 menu_key(仅处理 menu_key 为空且 menu_type 为 C/M 的记录) + * + * @return 更新条数 + */ + int generateMenuKeys(); } diff --git a/klp-system/src/main/java/com/klp/system/service/impl/SysMenuServiceImpl.java b/klp-system/src/main/java/com/klp/system/service/impl/SysMenuServiceImpl.java index acc1e8480..62b09f9fc 100644 --- a/klp-system/src/main/java/com/klp/system/service/impl/SysMenuServiceImpl.java +++ b/klp-system/src/main/java/com/klp/system/service/impl/SysMenuServiceImpl.java @@ -303,6 +303,92 @@ public class SysMenuServiceImpl implements ISysMenuService { return !exist; } + /** + * 校验 menu_key 是否唯一 + */ + @Override + public boolean checkMenuKeyUnique(SysMenu menu) { + if (StringUtils.isBlank(menu.getMenuKey())) { + return true; + } + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysMenu::getMenuKey, menu.getMenuKey()) + .ne(ObjectUtil.isNotNull(menu.getMenuId()), SysMenu::getMenuId, menu.getMenuId())); + return !exist; + } + + /** + * 根据 menuKey 查询菜单 + */ + @Override + public SysMenu selectMenuByMenuKey(String menuKey) { + return baseMapper.selectMenuByMenuKey(menuKey); + } + + /** + * 为现有菜单自动生成 menu_key + * 规则: + * C(菜单):去掉 path 的首尾 /,剩余 / 替换为 _,如 /mes/qc/tensile → mes_qc_tensile + * M(目录):同上,若 path 为空则用 menu_name 转拼音首字母 + * F(按钮):不处理 + */ + @Override + public int generateMenuKeys() { + List allMenus = baseMapper.selectList(new LambdaQueryWrapper() + .isNull(SysMenu::getMenuKey) + .in(SysMenu::getMenuType, UserConstants.TYPE_DIR, UserConstants.TYPE_MENU) + .eq(SysMenu::getStatus, UserConstants.MENU_NORMAL)); + + int count = 0; + for (SysMenu menu : allMenus) { + String key = buildMenuKey(menu); + if (StringUtils.isBlank(key)) { + continue; + } + // 确保唯一:如果已有同 key 的菜单,追加数字后缀 + String uniqueKey = key; + int suffix = 1; + while (baseMapper.exists(new LambdaQueryWrapper() + .eq(SysMenu::getMenuKey, uniqueKey) + .ne(SysMenu::getMenuId, menu.getMenuId()))) { + uniqueKey = key + "_" + suffix; + suffix++; + } + menu.setMenuKey(uniqueKey); + baseMapper.updateById(menu); + count++; + } + return count; + } + + /** + * 根据菜单信息构建 menu_key + */ + private String buildMenuKey(SysMenu menu) { + // 优先使用 path + if (StringUtils.isNotBlank(menu.getPath())) { + String path = menu.getPath().trim(); + // 去掉首尾 / + if (path.startsWith("/")) { + path = path.substring(1); + } + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + // 剩余 / 替换为 _,非字母数字替换为 _ + return path.replaceAll("[^a-zA-Z0-9_\\u4e00-\\u9fa5]", "_") + .replaceAll("_+", "_") + .replaceAll("(^_|_$)", ""); + } + // path 为空时,使用 menuName 作为 key + if (StringUtils.isNotBlank(menu.getMenuName())) { + return menu.getMenuName().trim().replaceAll("[^a-zA-Z0-9_\\u4e00-\\u9fa5]", "_") + .replaceAll("_+", "_") + .replaceAll("(^_|_$)", ""); + } + return null; + } + /** * 获取路由名称 * diff --git a/klp-system/src/main/resources/mapper/system/SysMenuMapper.xml b/klp-system/src/main/resources/mapper/system/SysMenuMapper.xml index 9bebaee34..e9b49f6cd 100644 --- a/klp-system/src/main/resources/mapper/system/SysMenuMapper.xml +++ b/klp-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -7,6 +7,7 @@ + @@ -29,7 +30,7 @@