From aacc3d4c0422e900a1a0154a56bf0fd2d4421ac0 Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Tue, 4 Aug 2026 09:24:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(crm-auth):=20Ticket=2001=20=E2=80=94=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B=E6=89=A9=E5=B1=95=EF=BC=88?= =?UTF-8?q?ADR-0011=20=E7=BB=9F=E4=B8=80=E6=9D=83=E9=99=90=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E6=A0=91=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 MenuType 枚举(CATALOG/MENU/BUTTON,含 fromCode 和 isLegalChild) - SysMenu 实体加 5 字段:description/perms/denyBehavior/status/apiUrl - 新增 ResourceNode DTO(含 fromEntity/toEntity 互转,映射 menuName/name、path/route) - 95 tests 全绿,零回归 --- .../com/crm/auth/domain/dto/ResourceNode.java | 79 +++++++++++++++++++ .../com/crm/auth/domain/entity/SysMenu.java | 22 ++++++ .../com/crm/auth/domain/enums/MenuType.java | 51 ++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 crm-auth/src/main/java/com/crm/auth/domain/dto/ResourceNode.java create mode 100644 crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java diff --git a/crm-auth/src/main/java/com/crm/auth/domain/dto/ResourceNode.java b/crm-auth/src/main/java/com/crm/auth/domain/dto/ResourceNode.java new file mode 100644 index 0000000..d57f847 --- /dev/null +++ b/crm-auth/src/main/java/com/crm/auth/domain/dto/ResourceNode.java @@ -0,0 +1,79 @@ +package com.crm.auth.domain.dto; + +import com.crm.auth.domain.entity.SysMenu; +import com.crm.auth.domain.enums.MenuType; +import com.crm.base.utils.BeanCopyUtils; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 权限资源树节点 DTO(ADR-0011) + *

管理端资源树的输入输出载体;与 SysMenu 通过 BeanCopyUtils 互转

+ */ +@Data +public class ResourceNode implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "节点 ID,新增时不传、编辑时必传") + private Long id; + + @Schema(description = "父节点 ID,根节点传 null/0") + private Long parentId; + + @Schema(description = "节点名称") + private String name; + + @Schema(description = "节点类型", example = "CATALOG") + private MenuType type; + + @Schema(description = "排序", example = "1") + private Integer sort; + + @Schema(description = "描述/备注") + private String description; + + @Schema(description = "前端路由路径,menu 必填") + private String route; + + @Schema(description = "权限码(如 crm:user:list),button 必填") + private String perms; + + @Schema(description = "无权限时的前端行为:hide / disable,button 必填") + private String denyBehavior; + + @Schema(description = "接口 URL,button 必填") + private String apiUrl; + + @Schema(description = "权限点状态:enabled 启用 / disabled 停用") + private String status; + + @Schema(description = "图标 URL,catalog/menu 可选") + private String icon; + + /** SysMenu → ResourceNode(id 映射 + menuName→name + path→route) */ + public static ResourceNode fromEntity(SysMenu entity) { + ResourceNode dto = BeanCopyUtils.copy(entity, ResourceNode::new); + if (entity != null) { + dto.setName(entity.getMenuName()); + dto.setType(MenuType.fromCode(entity.getMenuType())); + dto.setRoute(entity.getPath()); + } + return dto; + } + + /** ResourceNode → SysMenu(反向映射 name→menuName + route→path) */ + public SysMenu toEntity() { + SysMenu entity = BeanCopyUtils.copy(this, SysMenu::new); + entity.setMenuName(this.name); + if (this.type != null) { + entity.setMenuType(this.type.getCode()); + } + entity.setPath(this.route); + return entity; + } +} diff --git a/crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java b/crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java index f0eaad6..3d89262 100644 --- a/crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java +++ b/crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java @@ -48,6 +48,28 @@ public class SysMenu extends BaseEntity { @Column(columnDefinition = "tinyint default 1") private Boolean visible; + // ────────────── ADR-0011 按钮/权限点扩展字段 ────────────── + + @Comment("描述/备注,仅 button 类型使用") + @Column(columnDefinition = "varchar(100)") + private String description; + + @Comment("权限码(按钮级权限标识,如 crm:user:list),仅 button 类型使用") + @Column(columnDefinition = "varchar(100)") + private String perms; + + @Comment("无权限时的前端行为:hide 隐藏,disable 禁用") + @Column(columnDefinition = "varchar(16)") + private String denyBehavior; + + @Comment("权限点状态:enabled 启用,disabled 停用(全局断路)") + @Column(columnDefinition = "varchar(16) default 'enabled'") + private String status; + + @Comment("接口 URL(按钮对应的后端 API 路径),仅 button 类型使用") + @Column(columnDefinition = "varchar(200)") + private String apiUrl; + /** 子菜单列表(非数据库字段,仅供 TreeUtils 构建树时填充) */ @TableField(exist = false) private List children; diff --git a/crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java b/crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java new file mode 100644 index 0000000..7e40a82 --- /dev/null +++ b/crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java @@ -0,0 +1,51 @@ +package com.crm.auth.domain.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 权限资源树节点类型枚举(ADR-0011) + *

DB 存储 tinyint(1/2/3),代码层用枚举;提供 fromCode 工厂方法和 toCode 映射

+ */ +@Getter +@RequiredArgsConstructor +public enum MenuType { + + /** 菜单分组:收纳容器,不承载页面,只能挂 menu 子节点 */ + CATALOG(1, "catalog"), + + /** 菜单页面:可导航的页面,只能挂 button 子节点 */ + MENU(2, "menu"), + + /** 按钮/权限点:叶子节点,不可再挂子节点,承载权限码和接口映射 */ + BUTTON(3, "button"); + + private final int code; + private final String value; + + /** 从 DB tinyint 值反查枚举,找不到返回 null */ + public static MenuType fromCode(Integer code) { + if (code == null) return null; + return Arrays.stream(values()) + .filter(t -> t.code == code) + .findFirst() + .orElse(null); + } + + /** 是否允许作为某父类型的子节点 */ + public static boolean isLegalChild(MenuType parentType, MenuType childType) { + if (childType == null) return false; + switch (parentType) { + case CATALOG: + return childType == MENU; + case MENU: + return childType == BUTTON; + case BUTTON: + return false; // 叶子节点 + default: + return false; + } + } +}