接口调整
This commit is contained in:
@@ -27,6 +27,9 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1_002_001_005, "父菜单的类型必须是目录或者菜单");
|
||||
ErrorCode MENU_COMPONENT_NAME_DUPLICATE = new ErrorCode(1_002_001_006, "已经存在该组件名的菜单");
|
||||
ErrorCode MENU_NOT_ENABLE = new ErrorCode(1_002_001_007, "名字为【{}】的菜单已被禁用");
|
||||
ErrorCode MENU_ROUTE_KIND_INVALID = new ErrorCode(1_002_001_008, "路由类型不合法");
|
||||
ErrorCode MENU_ROUTE_PROPS_JSON_INVALID = new ErrorCode(1_002_001_009, "路由 props JSON 不合法");
|
||||
ErrorCode MENU_ROUTE_IFRAME_URL_REQUIRED = new ErrorCode(1_002_001_010, "iframe 路由必须配置 props.url");
|
||||
|
||||
// ========== 角色模块 1-002-002-000 ==========
|
||||
ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在");
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.njcn.rdms.module.system.enums.permission;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 菜单路由类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MenuRouteKindEnum {
|
||||
|
||||
DIR("dir"), // 目录路由
|
||||
VIEW("view"), // 普通页面
|
||||
SINGLE("single"), // 顶级单页
|
||||
IFRAME("iframe"), // iframe 页面
|
||||
EXTERNAL("external"), // 外链页面
|
||||
REDIRECT("redirect"); // 重定向路由
|
||||
|
||||
/**
|
||||
* 路由类型值
|
||||
*/
|
||||
private final String kind;
|
||||
|
||||
public static MenuRouteKindEnum valueOfKind(String kind) {
|
||||
if (StrUtil.isBlank(kind)) {
|
||||
return null;
|
||||
}
|
||||
for (MenuRouteKindEnum value : values()) {
|
||||
if (StrUtil.equalsIgnoreCase(value.getKind(), StrUtil.trim(kind))) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,12 @@ public class MenuRespVO {
|
||||
@Schema(description = "组件名", example = "SystemUser")
|
||||
private String componentName;
|
||||
|
||||
@Schema(description = "路由类型,例如 dir/view/single/iframe", example = "view")
|
||||
private String routeKind;
|
||||
|
||||
@Schema(description = "路由 props JSON 字符串", example = "{\"url\":\"https://cn.vuejs.org/\"}")
|
||||
private String routePropsJson;
|
||||
|
||||
@Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
@@ -48,6 +48,13 @@ public class MenuSaveVO {
|
||||
@Schema(description = "组件名", example = "SystemUser")
|
||||
private String componentName;
|
||||
|
||||
@Schema(description = "路由类型,例如 dir/view/single/iframe", example = "view")
|
||||
@Size(max = 32, message = "路由类型长度不能超过32个字符")
|
||||
private String routeKind;
|
||||
|
||||
@Schema(description = "路由 props JSON 字符串", example = "{\"url\":\"https://cn.vuejs.org/\"}")
|
||||
private String routePropsJson;
|
||||
|
||||
@Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.njcn.rdms.module.system.convert.auth;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.module.system.controller.admin.auth.vo.AuthRouteMetaRespVO;
|
||||
import com.njcn.rdms.module.system.controller.admin.auth.vo.AuthRouteNodeRespVO;
|
||||
@@ -12,6 +14,7 @@ import com.njcn.rdms.module.system.controller.admin.auth.vo.AuthUserRouteRespVO;
|
||||
import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO;
|
||||
import com.njcn.rdms.module.system.dal.dataobject.permission.RoleDO;
|
||||
import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.njcn.rdms.module.system.enums.permission.MenuRouteKindEnum;
|
||||
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
@@ -208,7 +211,7 @@ public interface AuthConvert {
|
||||
Set<String> usedNames = new HashSet<>();
|
||||
menus.forEach(menu -> {
|
||||
String fullPath = fullPathCache.get(menu.getId());
|
||||
String baseName = normalizeRouteName(fullPath);
|
||||
String baseName = resolveBaseRouteName(menu, fullPath);
|
||||
if (StrUtil.isBlank(baseName)) {
|
||||
baseName = "route_" + menu.getId();
|
||||
}
|
||||
@@ -222,12 +225,21 @@ public interface AuthConvert {
|
||||
return routeNameMap;
|
||||
}
|
||||
|
||||
default String resolveBaseRouteName(MenuDO menu, String fullPath) {
|
||||
if (StrUtil.isNotBlank(menu.getComponentName())) {
|
||||
return StrUtil.trim(menu.getComponentName());
|
||||
}
|
||||
return normalizeRouteName(fullPath);
|
||||
}
|
||||
|
||||
default AuthRouteNodeRespVO buildRouteNode(MenuDO menu, String fullPath, String routeName, boolean hasChildren) {
|
||||
MenuRouteKindEnum routeKind = resolveRouteKind(menu, hasChildren);
|
||||
return AuthRouteNodeRespVO.builder()
|
||||
.id(String.valueOf(menu.getId()))
|
||||
.name(routeName)
|
||||
.path(fullPath)
|
||||
.component(resolveComponentKey(menu, routeName, hasChildren))
|
||||
.component(resolveComponentKey(menu, routeKind, routeName))
|
||||
.props(resolveRouteProps(menu.getRoutePropsJson()))
|
||||
.meta(buildRouteMeta(menu))
|
||||
.build();
|
||||
}
|
||||
@@ -238,17 +250,60 @@ public interface AuthConvert {
|
||||
.icon(resolveIcon(menu.getIcon()))
|
||||
.order(menu.getSort())
|
||||
.keepAlive(menu.getKeepAlive())
|
||||
.hideInMenu(Boolean.FALSE.equals(menu.getVisible()))
|
||||
.build();
|
||||
}
|
||||
|
||||
default String resolveComponentKey(MenuDO menu, String routeName, boolean hasChildren) {
|
||||
default MenuRouteKindEnum resolveRouteKind(MenuDO menu, boolean hasChildren) {
|
||||
if (hasChildren) {
|
||||
return "layout.base";
|
||||
return MenuRouteKindEnum.DIR;
|
||||
}
|
||||
MenuRouteKindEnum routeKind = MenuRouteKindEnum.valueOfKind(menu.getRouteKind());
|
||||
if (routeKind != null) {
|
||||
return routeKind;
|
||||
}
|
||||
if (MenuTypeEnum.DIR.getType().equals(menu.getType())) {
|
||||
return MenuRouteKindEnum.DIR;
|
||||
}
|
||||
if (isExternalPath(menu.getPath())) {
|
||||
return MenuRouteKindEnum.EXTERNAL;
|
||||
}
|
||||
if (ID_ROOT.equals(menu.getParentId())) {
|
||||
return MenuRouteKindEnum.SINGLE;
|
||||
}
|
||||
return MenuRouteKindEnum.VIEW;
|
||||
}
|
||||
|
||||
default String resolveComponentKey(MenuDO menu, MenuRouteKindEnum routeKind, String routeName) {
|
||||
if (StrUtil.isNotBlank(menu.getComponent())) {
|
||||
return StrUtil.trim(menu.getComponent());
|
||||
}
|
||||
if (MenuRouteKindEnum.DIR.equals(routeKind)) {
|
||||
return "layout.base";
|
||||
}
|
||||
if (MenuRouteKindEnum.IFRAME.equals(routeKind)) {
|
||||
return "view.iframe-page";
|
||||
}
|
||||
if (MenuRouteKindEnum.SINGLE.equals(routeKind)) {
|
||||
return "layout.base$view." + routeName;
|
||||
}
|
||||
return "view." + routeName;
|
||||
if (MenuRouteKindEnum.VIEW.equals(routeKind)) {
|
||||
return "view." + routeName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
default Object resolveRouteProps(String routePropsJson) {
|
||||
if (StrUtil.isBlank(routePropsJson)) {
|
||||
return null;
|
||||
}
|
||||
Object routeProps = JsonUtils.parseObjectQuietly(routePropsJson, new TypeReference<Object>() {
|
||||
});
|
||||
if (routeProps != null) {
|
||||
return routeProps;
|
||||
}
|
||||
LoggerFactory.getLogger(getClass()).warn("[resolveRouteProps][routePropsJson({}) 解析失败]", routePropsJson);
|
||||
return null;
|
||||
}
|
||||
|
||||
default String resolveIcon(String icon) {
|
||||
@@ -278,10 +333,15 @@ public interface AuthConvert {
|
||||
if (StrUtil.isBlank(fullPath)) {
|
||||
return "";
|
||||
}
|
||||
return fullPath.replaceAll("^/+", "")
|
||||
return fullPath
|
||||
.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2")
|
||||
.replaceAll("([a-z\\d])([A-Z])", "$1_$2")
|
||||
.replaceAll("^/+", "")
|
||||
.replaceAll("/+$", "")
|
||||
.replaceAll("[/\\-.]+", "_")
|
||||
.replaceAll("[/\\\\.\\-\\s]+", "_")
|
||||
.replaceAll("_+", "_")
|
||||
.replaceAll("^_+", "")
|
||||
.replaceAll("_+$", "")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,14 @@ public class MenuDO extends BaseDO {
|
||||
* 组件名
|
||||
*/
|
||||
private String componentName;
|
||||
/**
|
||||
* 路由类型
|
||||
*/
|
||||
private String routeKind;
|
||||
/**
|
||||
* 路由 props JSON
|
||||
*/
|
||||
private String routePropsJson;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
|
||||
@@ -3,13 +3,16 @@ package com.njcn.rdms.module.system.service.permission;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||
import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
|
||||
import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO;
|
||||
import com.njcn.rdms.module.system.dal.mysql.permission.MenuMapper;
|
||||
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.njcn.rdms.module.system.enums.permission.MenuRouteKindEnum;
|
||||
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
@@ -53,6 +56,7 @@ public class MenuServiceImpl implements MenuService {
|
||||
// 校验菜单(自己)
|
||||
validateMenuName(createReqVO.getParentId(), createReqVO.getName(), null);
|
||||
validateMenuComponentName(createReqVO.getComponentName(), null);
|
||||
validateMenuRoute(createReqVO);
|
||||
|
||||
// 插入数据库
|
||||
MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class);
|
||||
@@ -75,6 +79,7 @@ public class MenuServiceImpl implements MenuService {
|
||||
// 校验菜单(自己)
|
||||
validateMenuName(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId());
|
||||
validateMenuComponentName(updateReqVO.getComponentName(), updateReqVO.getId());
|
||||
validateMenuRoute(updateReqVO);
|
||||
|
||||
// 更新到数据库
|
||||
MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);
|
||||
@@ -296,6 +301,43 @@ public class MenuServiceImpl implements MenuService {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMenuRoute(MenuSaveVO reqVO) {
|
||||
if (reqVO == null || MenuTypeEnum.BUTTON.getType().equals(reqVO.getType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuRouteKindEnum routeKind = MenuRouteKindEnum.valueOfKind(reqVO.getRouteKind());
|
||||
if (StrUtil.isNotBlank(reqVO.getRouteKind()) && routeKind == null) {
|
||||
throw exception(MENU_ROUTE_KIND_INVALID);
|
||||
}
|
||||
|
||||
String routePropsJson = StrUtil.trim(reqVO.getRoutePropsJson());
|
||||
if (StrUtil.isBlank(routePropsJson)) {
|
||||
if (MenuRouteKindEnum.IFRAME.equals(routeKind)) {
|
||||
throw exception(MENU_ROUTE_IFRAME_URL_REQUIRED);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Object routeProps = JsonUtils.parseObjectQuietly(routePropsJson, new TypeReference<Object>() {
|
||||
});
|
||||
if (routeProps == null) {
|
||||
throw exception(MENU_ROUTE_PROPS_JSON_INVALID);
|
||||
}
|
||||
if (!MenuRouteKindEnum.IFRAME.equals(routeKind)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(routeProps instanceof Map<?, ?> routePropsMap)) {
|
||||
throw exception(MENU_ROUTE_PROPS_JSON_INVALID);
|
||||
}
|
||||
Object url = routePropsMap.get("url");
|
||||
if (!(url instanceof String urlString) || StrUtil.isBlank(urlString)) {
|
||||
throw exception(MENU_ROUTE_IFRAME_URL_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化菜单的通用属性。
|
||||
* <p>
|
||||
@@ -304,13 +346,22 @@ public class MenuServiceImpl implements MenuService {
|
||||
* @param menu 菜单
|
||||
*/
|
||||
private void initMenuProperty(MenuDO menu) {
|
||||
menu.setRouteKind(normalizeRouteKind(menu.getRouteKind()));
|
||||
menu.setRoutePropsJson(StrUtil.blankToDefault(StrUtil.trim(menu.getRoutePropsJson()), null));
|
||||
// 菜单为按钮类型时,无需 component、icon、path 属性,进行置空
|
||||
if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) {
|
||||
menu.setComponent("");
|
||||
menu.setComponentName("");
|
||||
menu.setIcon("");
|
||||
menu.setPath("");
|
||||
menu.setRouteKind(null);
|
||||
menu.setRoutePropsJson(null);
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeRouteKind(String routeKind) {
|
||||
MenuRouteKindEnum routeKindEnum = MenuRouteKindEnum.valueOfKind(routeKind);
|
||||
return routeKindEnum != null ? routeKindEnum.getKind() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -646,4 +646,60 @@ CREATE TABLE `system_users` (
|
||||
-- ----------------------------
|
||||
INSERT INTO `system_users` VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '灿能源码', '管理员', 103, 2, NULL, '[1,2]', '11aoteman@126.com', '18818260272', 2, 'http://test.rdms.iocoder.cn/20250921/avatar_1758423875594.png', 0, '192.168.2.125', '2026-03-18 16:18:02', 'admin', '2021-01-05 17:03:47', 'system', '2026-03-19 13:59:04', b'0');
|
||||
|
||||
-- ----------------------------
|
||||
-- system_menu 路由扩展字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `system_menu`
|
||||
ADD COLUMN `route_kind` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '路由类型:dir/view/single/iframe/external/redirect' AFTER `component_name`,
|
||||
ADD COLUMN `route_props_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '路由 props JSON' AFTER `route_kind`;
|
||||
|
||||
UPDATE `system_menu`
|
||||
SET `component` = 'view.iframe-page',
|
||||
`component_name` = 'document_antd',
|
||||
`route_kind` = 'iframe',
|
||||
`route_props_json` = '{\"url\":\"https://www.antdv.com/\"}'
|
||||
WHERE `id` = 900010;
|
||||
|
||||
UPDATE `system_menu`
|
||||
SET `component` = 'view.iframe-page',
|
||||
`component_name` = 'document_naive',
|
||||
`route_kind` = 'iframe',
|
||||
`route_props_json` = '{\"url\":\"https://www.naiveui.com/\"}'
|
||||
WHERE `id` = 900011;
|
||||
|
||||
UPDATE `system_menu`
|
||||
SET `component` = 'view.iframe-page',
|
||||
`component_name` = 'document_element_plus',
|
||||
`route_kind` = 'iframe',
|
||||
`route_props_json` = '{\"url\":\"https://cn.element-plus.org/\"}'
|
||||
WHERE `id` = 900012;
|
||||
|
||||
UPDATE `system_menu`
|
||||
SET `component` = 'view.iframe-page',
|
||||
`component_name` = 'document_alova',
|
||||
`route_kind` = 'iframe',
|
||||
`route_props_json` = '{\"url\":\"https://alova.js.org/\"}'
|
||||
WHERE `id` = 900013;
|
||||
|
||||
UPDATE `system_menu`
|
||||
SET `component` = 'view.iframe-page',
|
||||
`component_name` = 'document_unocss',
|
||||
`route_kind` = 'iframe',
|
||||
`route_props_json` = '{\"url\":\"https://unocss.dev/\"}'
|
||||
WHERE `id` = 900014;
|
||||
|
||||
UPDATE `system_menu`
|
||||
SET `component` = 'view.iframe-page',
|
||||
`component_name` = 'document_vite',
|
||||
`route_kind` = 'iframe',
|
||||
`route_props_json` = '{\"url\":\"https://vite.dev/\"}'
|
||||
WHERE `id` = 900015;
|
||||
|
||||
UPDATE `system_menu`
|
||||
SET `component` = 'view.iframe-page',
|
||||
`component_name` = 'document_vue',
|
||||
`route_kind` = 'iframe',
|
||||
`route_props_json` = '{\"url\":\"https://cn.vuejs.org/\"}'
|
||||
WHERE `id` = 900016;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
Reference in New Issue
Block a user