# 菜单管理模块
> 本文档描述菜单管理模块的数据库设计、后端实现、API 接口、前端管理页面及动态路由加载机制。
> 菜单管理是权限控制体系的基础,通过动态菜单实现角色级的页面可见性控制。
---
## 1. 模块概述
菜单管理模块实现运营平台侧边栏菜单的**动态配置与角色权限绑定**。核心能力:
| 能力 | 说明 |
|------|------|
| 增删改查 | 对 t_menu 表进行 CRUD 操作,支持按菜单名称模糊搜索 |
| 级联删除 | 删除父菜单时,递归逻辑删除所有子孙菜单 |
| 菜单树构建 | 将扁平菜单列表按 parent_id 关系构建为树形结构(前端 asyncRoutes 格式) |
| 角色菜单绑定 | 支持为每个角色分配可见菜单,保存到 t_role_menu 表 |
| 动态路由下发 | 前端请求 /menu/routes 获取当前用户可见的菜单树,动态注册路由 |
模块在系统中的位置:
```
系统设置
└── 菜单管理 (/system/menu) ← 本模块前端管理页
└── 角色管理 (/system/role) ← 角色-菜单权限分配
```
---
## 2. 数据库设计
### 2.1 t_menu(菜单表)
**建表 SQL**:[create_menu.sql](file://docs/database/create_menu.sql#L1-L50)
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| id | BIGINT | — | 主键(雪花算法) |
| parent_id | BIGINT | 0 | 父菜单ID,0表示顶级菜单 |
| menu_type | INT | 0 | 菜单类型: 0=菜单 1=iframe 2=外链 3=按钮 |
| title | VARCHAR(100) | — | 菜单名称 |
| name | VARCHAR(100) | '' | 路由名称(须与前端 .ts 路由文件中的 name 一致) |
| path | VARCHAR(200) | '' | 路由路径 |
| component | VARCHAR(200) | '' | 组件路径(后端仅存储,由前端 addAsyncRoutes 解析匹配) |
| `rank` | INT | 99 | 菜单排序(越小越靠前) |
| `redirect` | VARCHAR(200) | '' | 路由重定向 |
| icon | VARCHAR(100) | '' | 菜单图标(ri: / ep: 前缀的 iconify 图标名) |
| extra_icon | VARCHAR(100) | '' | 右侧图标 |
| enter_transition | VARCHAR(50) | '' | 进场动画 |
| leave_transition | VARCHAR(50) | '' | 离场动画 |
| active_path | VARCHAR(200) | '' | 菜单激活路径 |
| auths | VARCHAR(200) | '' | 权限标识(按钮级别,逗号分隔) |
| frame_src | VARCHAR(500) | '' | iframe 链接地址 |
| frame_loading | TINYINT | 1 | iframe 是否开启加载动画 |
| keep_alive | TINYINT | 0 | 是否缓存页面 |
| hidden_tag | TINYINT | 0 | 是否禁止添加到标签页 |
| fixed_tag | TINYINT | 0 | 是否固定显示在标签页 |
| show_link | TINYINT | 1 | 是否显示该菜单 |
| show_parent | TINYINT | 0 | 是否显示父级菜单 |
| create_time | DATETIME | CURRENT_TIMESTAMP | 创建时间 |
| update_time | DATETIME | CURRENT_TIMESTAMP | 更新时间(自动更新) |
| deleted | TINYINT | 0 | 逻辑删除: 0=正常 1=已删除 |
**索引**:
```
KEY idx_parent_id (parent_id),
KEY idx_menu_type (menu_type),
KEY idx_rank (`rank`)
```
**MySQL 保留字处理**:`rank` 和 `redirect` 是 MySQL 保留字,实体类中使用 `@TableField("`rank`")` 和 `@TableField("`redirect`")` 进行转义。
### 2.2 t_role_menu(角色菜单关联表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键(雪花算法) |
| role_id | BIGINT | 角色ID |
| menu_id | BIGINT | 菜单ID |
| create_time | DATETIME | 创建时间 |
**唯一约束**:`UNIQUE KEY uk_role_menu (role_id, menu_id)`
---
## 3. 实体类
### 3.1 Menu
**文件**:[Menu.java](file://haha-entity/src/main/java/com/haha/entity/Menu.java)
```java
@Data
@TableName("t_menu")
public class Menu implements Serializable {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long parentId; // 父菜单ID,0表示顶级
private Integer menuType; // 0=菜单 1=iframe 2=外链 3=按钮
private String title; // 菜单名称
private String name; // 路由名称
private String path; // 路由路径
private String component; // 组件路径
@TableField("`rank`")
private Integer rank; // 排序(MySQL保留字转义)
@TableField("`redirect`")
private String redirect; // 重定向(MySQL保留字转义)
private String icon; // 菜单图标
private String extraIcon; // 右侧图标
private String enterTransition; // 进场动画
private String leaveTransition; // 离场动画
private String activePath; // 激活路径
private String auths; // 权限标识
private String frameSrc; // iframe链接
private Boolean frameLoading; // iframe加载动画
private Boolean keepAlive; // 是否缓存
private Boolean hiddenTag; // 禁止标签页
private Boolean fixedTag; // 固定标签页
private Boolean showLink; // 是否显示
private Boolean showParent; // 是否显示父级
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime updateTime;
private Integer deleted; // 逻辑删除标记
}
```
### 3.2 RoleMenu
**文件**:[RoleMenu.java](file://haha-entity/src/main/java/com/haha/entity/RoleMenu.java)
```java
@Data
@TableName("t_role_menu")
public class RoleMenu implements Serializable {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long roleId;
@JsonSerialize(using = ToStringSerializer.class)
private Long menuId;
private LocalDateTime createTime;
}
```
---
## 4. Mapper 层
### 4.1 MenuMapper
**文件**:[MenuMapper.java](file://haha-mapper/src/main/java/com/haha/mapper/MenuMapper.java)
```java
public interface MenuMapper extends BaseMapper