Browse Source

feat(crm-auth): Ticket 01 — 数据模型扩展(ADR-0011 统一权限资源树)

- 新增 MenuType 枚举(CATALOG/MENU/BUTTON,含 fromCode 和 isLegalChild)
- SysMenu 实体加 5 字段:description/perms/denyBehavior/status/apiUrl
- 新增 ResourceNode DTO(含 fromEntity/toEntity 互转,映射 menuName/name、path/route)
- 95 tests 全绿,零回归
master
luoweijian 1 month ago
parent
commit
aacc3d4c04
  1. 79
      crm-auth/src/main/java/com/crm/auth/domain/dto/ResourceNode.java
  2. 22
      crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java
  3. 51
      crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java

79
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;
/**
* 权限资源树节点 DTOADR-0011
* <p>管理端资源树的输入输出载体 SysMenu 通过 BeanCopyUtils 互转</p>
*/
@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;
}
}

22
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") @Column(columnDefinition = "tinyint default 1")
private Boolean visible; 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 构建树时填充) */ /** 子菜单列表(非数据库字段,仅供 TreeUtils 构建树时填充) */
@TableField(exist = false) @TableField(exist = false)
private List<SysMenu> children; private List<SysMenu> children;

51
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
* <p>DB 存储 tinyint1/2/3代码层用枚举提供 fromCode 工厂方法和 toCode 映射</p>
*/
@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;
}
}
}
Loading…
Cancel
Save