Files
java-template/docs/dynamic_menu_management_system.md
T
BrunoGaoandClaude dfee47d3a2 feat: 完成管理员用户查询优化 - 实施admin_user_query_optimization_proposal
🚀 核心优化内容:
- ljwx-boot: 添加user_type和admin_level冗余字段到sys_user表
- ljwx-boot: 重构管理员判断方法,从复杂多表JOIN优化为单字段查询
- ljwx-boot: 实现数据同步维护机制,确保数据一致性
- ljwx-bigscreen: 移除复杂admin_helper,直接在SQL中使用user_type过滤
- 性能提升: 查询复杂度从O(N*M)优化为O(1)

📊 技术改进:
- 数据库: 新增UserType和AdminLevel枚举定义
- 迁移: 创建数据迁移脚本初始化现有用户类型
- 同步: 角色/组织变更时自动更新用户类型字段
- 查询: 消除多表JOIN,直接使用索引查询

🎯 预期效果:
- 减少数据库负载,提升查询性能
- 简化代码逻辑,降低维护成本
- 实时准确,无需复杂缓存机制

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-09-12 14:53:34 +08:00

24 KiB
Raw Blame History

动态菜单权限管理系统

系统概述

动态菜单权限管理系统是一套完整的解决方案,用于解决 ljwx-admin 和 ljwx-boot 项目中菜单管理的核心问题:

  • 自动发现问题:新增页面无法自动添加到菜单系统
  • 灵活配置问题:菜单无法自由调整名称、层级和顺序
  • 多租户支持:不同租户需要不同的菜单配置
  • 权限控制:基于角色的菜单访问控制

系统架构

整体架构图

┌─────────────────────────────────────────────────────────────────┐
│                    前端 ljwx-admin                              │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌──────────────────┐  ┌─────────────────┐ │
│  │   路由扫描界面   │  │   菜单管理界面   │  │   批量操作界面   │ │
│  └─────────────────┘  └──────────────────┘  └─────────────────┘ │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │                  useDynamicMenu Hook                       │ │
│  └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 │ HTTP API
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                    后端 ljwx-boot                               │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │              DynamicMenuController                         │ │
│  └─────────────────────────────────────────────────────────────┘ │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │              DynamicMenuServiceImpl                        │ │
│  │  • 前端文件扫描    • 菜单自动生成    • 批量操作处理      │ │
│  │  • 权限验证       • 缓存管理        • 多租户支持        │ │
│  └─────────────────────────────────────────────────────────────┘ │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │                   数据持久层                                │ │
│  │  • SysMenu 实体   • 菜单权限关系   • 租户隔离             │ │
│  └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

技术栈

前端技术栈:

  • Vue 3 + TypeScript
  • Naive UI 组件库
  • Vite 构建工具
  • Pinia 状态管理

后端技术栈:

  • Spring Boot 3
  • MyBatis Plus ORM
  • Redis 缓存
  • Spring Security 权限控制

核心功能

1. 前端路由自动扫描

功能描述

系统能够自动扫描前端项目中的 Vue 文件,发现新的页面组件和路由配置,自动生成对应的菜单项。

扫描机制

// 文件扫描逻辑
Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        // 检查文件类型(.vue, .tsx, .jsx, .ts, .js)
        // 应用包含/排除模式过滤
        // 解析文件内容,提取路由信息
        // 生成菜单建议配置
    }
});

支持的文件类型

  • .vue - Vue 单文件组件
  • .tsx - TypeScript JSX 文件
  • .jsx - JavaScript JSX 文件
  • .ts - TypeScript 文件
  • .js - JavaScript 文件

路由信息提取

// 从文件内容中提取的信息
const routeInfo = {
    path: "/system/user",           // 路由路径
    name: "SystemUser",             // 路由名称
    title: "用户管理",              // 页面标题
    component: "views/system/user", // 组件路径
    icon: "mdi:account",           // 建议图标
    level: 2                       // 菜单层级
};

2. 智能菜单生成

自动生成规则

  • 名称生成:基于文件名或路径自动生成菜单名称
  • 图标建议:根据路径关键词智能建议图标
  • 层级推断:根据文件路径结构推断菜单层级
  • 排序规则:按照扫描顺序或自定义规则分配排序值

生成算法示例

public DynamicMenuConfigDTO autoGenerateMenu(String routePath, String filePath) {
    DynamicMenuConfigDTO config = new DynamicMenuConfigDTO();
    
    // 生成菜单名称
    String fileName = Paths.get(filePath).getFileName().toString();
    String menuName = fileName.replaceAll("\\.(vue|tsx|jsx|ts|js)$", "");
    config.setName(capitalizeWords(menuName.replaceAll("[-_]", " ")));
    
    // 智能图标建议
    config.setIcon(generateIcon(routePath, fileName));
    
    // 层级判断
    String[] pathParts = routePath.split("/");
    config.setType(pathParts.length <= 2 ? "1" : "2"); // 1-目录,2-菜单
    
    return config;
}

3. 灵活的菜单管理

批量操作功能

系统支持多种批量操作,提高菜单管理效率:

  • 批量启用/禁用:快速改变多个菜单的状态
  • 批量移动:将多个菜单移动到指定父级
  • 批量删除:递归删除菜单及其子菜单
  • 批量更新:同时更新多个菜单的属性

拖拽排序

前端界面支持拖拽操作,直观地调整菜单顺序和层级关系。

实时预览

提供菜单配置的实时预览功能,支持按角色预览不同用户看到的菜单结构。

4. 多租户支持

租户隔离机制

-- 菜单表结构支持多租户
CREATE TABLE sys_menu (
    id BIGINT PRIMARY KEY,
    customer_id BIGINT,      -- 租户ID,null表示全局菜单
    parent_id BIGINT,
    name VARCHAR(50),
    path VARCHAR(200),
    -- ... 其他字段
    INDEX idx_customer_id (customer_id)
);

权限控制

  • 超级管理员:可管理所有租户的菜单
  • 租户管理员:只能管理自己租户的菜单
  • 普通用户:根据角色权限查看菜单

5. 缓存优化

多级缓存策略

// Spring Cache 配置
@Cacheable(value = "dynamic_menus", key = "#customerId")
public List<DynamicMenuVO> getDynamicMenuConfig(Long customerId) {
    // 查询数据库逻辑
}

// 缓存失效策略
@CacheEvict(value = "dynamic_menus", allEntries = true)
public String updateDynamicMenuConfig(DynamicMenuConfigDTO configDTO) {
    // 更新逻辑
}

详细设计

数据库设计

核心表结构

-- 系统菜单表
CREATE TABLE sys_menu (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
    parent_id BIGINT DEFAULT 0 COMMENT '父菜单ID',
    customer_id BIGINT COMMENT '租户ID',
    type VARCHAR(1) DEFAULT '2' COMMENT '菜单类型(1目录 2菜单 3按钮)',
    name VARCHAR(50) NOT NULL COMMENT '菜单名称',
    title VARCHAR(50) COMMENT '菜单标题',
    path VARCHAR(200) COMMENT '路由地址',
    component VARCHAR(255) COMMENT '组件路径',
    icon VARCHAR(100) COMMENT '菜单图标',
    icon_type VARCHAR(1) DEFAULT '1' COMMENT '图标类型(1 Iconify 2本地)',
    status VARCHAR(1) DEFAULT '1' COMMENT '菜单状态(0停用 1启用)',
    hide_in_menu VARCHAR(1) DEFAULT '0' COMMENT '是否隐藏(0显示 1隐藏)',
    permission VARCHAR(255) COMMENT '权限标识',
    sort INT DEFAULT 0 COMMENT '显示顺序',
    is_system VARCHAR(1) DEFAULT '0' COMMENT '是否系统菜单',
    source VARCHAR(20) DEFAULT 'manual' COMMENT '来源(manual手动 scan扫描 import导入)',
    source_file VARCHAR(500) COMMENT '源文件路径',
    last_scan_time DATETIME COMMENT '最后扫描时间',
    version VARCHAR(10) COMMENT '版本号',
    remark VARCHAR(500) COMMENT '备注',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    KEY idx_parent_id (parent_id),
    KEY idx_customer_id (customer_id),
    KEY idx_status (status)
) COMMENT='菜单权限表';

-- 角色菜单关联表
CREATE TABLE sys_role_menu (
    role_id BIGINT NOT NULL COMMENT '角色ID',
    menu_id BIGINT NOT NULL COMMENT '菜单ID',
    PRIMARY KEY (role_id, menu_id)
) COMMENT='角色和菜单关联表';

API 接口设计

RESTful API 规范

GET    /sys_menu/dynamic/config              # 获取动态菜单配置
POST   /sys_menu/dynamic/scan                # 扫描前端路由文件  
POST   /sys_menu/dynamic/auto-sync           # 自动同步菜单配置
PUT    /sys_menu/dynamic/config              # 更新动态菜单配置
PUT    /sys_menu/dynamic/batch               # 批量更新菜单
PUT    /sys_menu/dynamic/reorder             # 重新排序菜单
POST   /sys_menu/dynamic/preset              # 应用预设模板
DELETE /sys_menu/dynamic/cache               # 清除菜单缓存
GET    /sys_menu/dynamic/preview             # 预览菜单配置
POST   /sys_menu/dynamic/import              # 导入菜单配置
GET    /sys_menu/dynamic/export              # 导出菜单配置

接口详细说明

1. 扫描前端路由文件
POST /sys_menu/dynamic/scan
Content-Type: application/json

{
  "frontendPath": "/Users/project/ljwx-admin/src/views",
  "scanMode": "auto",
  "includePatterns": ["**/*.vue", "**/*.tsx"],
  "excludePatterns": ["**/components/**", "**/temp/**"],
  "recursive": true,
  "autoCreate": false,
  "overwriteExisting": false,
  "customerId": 1
}

// 响应
{
  "code": 200,
  "message": "扫描完成",
  "data": {
    "scanTime": "2024-01-15 14:30:00",
    "foundFiles": [
      "src/views/system/user/index.vue",
      "src/views/system/role/index.vue"
    ],
    "newRoutes": [
      {
        "path": "/system/user", 
        "title": "用户管理",
        "component": "views/system/user",
        "suggestedIcon": "mdi:account",
        "level": 2
      }
    ],
    "scanStats": {
      "totalFiles": 45,
      "newRoutes": 3,
      "changedFiles": 1,
      "scanDuration": 1250
    }
  }
}
2. 批量更新菜单
PUT /sys_menu/dynamic/batch
Content-Type: application/json

{
  "menuIds": [1, 2, 3],
  "operation": "update",
  "updateFields": {
    "status": "1",
    "icon": "mdi:menu"
  },
  "reason": "批量启用系统菜单"
}

// 响应
{
  "code": 200,
  "message": "操作成功", 
  "data": "成功批量更新 3 个菜单的字段"
}

前端组件设计

主界面组件结构

DynamicMenuManagement/
├── index.vue                    # 主界面
├── modules/
│   ├── scan-result-modal.vue   # 扫描结果展示
│   ├── menu-edit-modal.vue     # 菜单编辑弹窗
│   ├── batch-update-modal.vue  # 批量操作弹窗
│   └── icon-select-modal.vue   # 图标选择器
└── hooks/
    └── use-dynamic-menu.ts     # 业务逻辑钩子

组件功能特性

1. 扫描结果展示
<template>
  <ScanResultModal
    v-model:visible="scanResultVisible"
    :scan-result="scanResult"
    @sync-menus="handleSyncMenus"
  >
    <!-- 扫描统计卡片 -->
    <div class="grid grid-cols-4 gap-12px">
      <NCard>{{ scanStats.totalFiles }} 总文件数</NCard>
      <NCard>{{ scanStats.newRoutes }} 新路由</NCard>
      <!-- ... -->
    </div>
    
    <!-- 新路由列表 -->
    <NDataTable
      :data="newRoutes"
      :columns="routeColumns" 
      v-model:checked-row-keys="selectedRoutes"
    />
  </ScanResultModal>
</template>
2. 菜单树形表格
<template>
  <NDataTable
    :data="menuData"
    :columns="columns"
    :row-key="(row) => row.id"
    :children-key="'children'"
    :indent="24"
    default-expand-all
  />
</template>

<script setup lang="ts">
// 构建菜单树
function buildMenuTree(menus: MenuRecord[]): MenuRecord[] {
  const map = new Map();
  const roots = [];
  
  menus.forEach(menu => {
    map.set(menu.id, { ...menu, children: [] });
  });
  
  menus.forEach(menu => {
    const currentMenu = map.get(menu.id);
    if (!menu.parentId) {
      roots.push(currentMenu);
    } else {
      const parent = map.get(menu.parentId);
      if (parent) {
        parent.children.push(currentMenu);
      }
    }
  });
  
  return sortMenus(roots);
}
</script>

使用指南

快速开始

1. 系统部署

# 后端部署
cd ljwx-boot
./mvn clean package
java -jar target/ljwx-boot.jar

# 前端部署  
cd ljwx-admin
npm install
npm run dev

2. 访问管理界面

打开浏览器访问:http://localhost:3000/#/system/dynamic-menu

3. 首次使用流程

  1. 权限检查:确保当前用户具有 sys:menu:dynamic:* 权限
  2. 路由扫描:点击"扫描路由"按钮,选择前端项目路径
  3. 查看结果:查看扫描发现的新路由和文件变化
  4. 选择同步:选择需要同步的路由,点击"同步选中的路由"
  5. 菜单调整:在菜单管理界面调整菜单属性、顺序等

高级功能使用

1. 批量操作

// 选择多个菜单后,点击批量操作
const batchData = {
  operation: "update",
  updateFields: {
    status: "1",        // 启用状态
    icon: "mdi:menu"    // 统一图标
  },
  reason: "系统优化"
};

await batchUpdateMenus({
  menuIds: [1, 2, 3],
  ...batchData
});

2. 预设模板应用

// 应用系统管理模板
await applyPresetTemplate("system-management", customerId);

// 应用监控大屏模板  
await applyPresetTemplate("monitoring-dashboard", customerId);

3. 菜单导入导出

// 导出当前租户的菜单配置
const menuConfig = await exportMenuConfig(customerId);

// 导入菜单配置到其他租户
await importMenuConfig(menuConfig);

配置说明

1. 扫描配置

interface DynamicMenuScanDTO {
  frontendPath: string;           // 前端项目路径
  scanMode: 'auto' | 'manual';    // 扫描模式
  includePatterns: string[];      // 包含模式
  excludePatterns: string[];      // 排除模式
  recursive: boolean;             // 递归扫描
  autoCreate: boolean;            // 自动创建菜单
  nameGenerationRule: string;     // 名称生成规则
  iconGenerationRule: string;     // 图标生成规则
}

2. 权限配置

# Spring Security 权限配置
security:
  permissions:
    - "sys:menu:dynamic:scan"      # 路由扫描权限
    - "sys:menu:dynamic:sync"      # 菜单同步权限
    - "sys:menu:dynamic:config"    # 菜单配置权限
    - "sys:menu:dynamic:batch"     # 批量操作权限

最佳实践

1. 扫描配置建议

路径配置

const scanConfig = {
  frontendPath: "/src/views",
  includePatterns: [
    "**/*.vue",           // Vue组件
    "**/pages/**/*.tsx"   // React页面
  ],
  excludePatterns: [
    "**/components/**",   // 排除通用组件
    "**/temp/**",         // 排除临时文件
    "**/__tests__/**"     // 排除测试文件
  ]
};

命名规范

  • 文件名:使用 PascalCase,如 UserManagement.vue
  • 路由路径:使用 kebab-case,如 /user-management
  • 菜单名称:使用中文描述,如 "用户管理"

2. 菜单结构设计

推荐层级结构

系统管理 (level 1, type 1 - 目录)
├── 用户管理 (level 2, type 2 - 菜单)
│   ├── 查看用户 (level 3, type 3 - 按钮)
│   ├── 新增用户 (level 3, type 3 - 按钮) 
│   └── 编辑用户 (level 3, type 3 - 按钮)
├── 角色管理 (level 2, type 2 - 菜单)
└── 权限管理 (level 2, type 2 - 菜单)

图标使用规范

  • 系统类:mdi:cog, mdi:settings
  • 用户类:mdi:account, mdi:account-group
  • 数据类:mdi:database, mdi:table
  • 监控类:mdi:monitor-dashboard, mdi:chart-line

3. 性能优化

缓存策略

// 1. 启用Spring Cache
@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager.Builder builder = RedisCacheManager
            .RedisCacheManagerBuilder
            .fromConnectionFactory(redisConnectionFactory)
            .cacheDefaults(cacheConfiguration());
        return builder.build();
    }
}

// 2. 合理设置缓存TTL
@Cacheable(value = "dynamic_menus", key = "#customerId")
@CachePut(value = "dynamic_menus", key = "#customerId")  
@CacheEvict(value = "dynamic_menus", allEntries = true)

批量操作优化

// 使用 MyBatis Plus 批量操作
@Transactional
public String batchUpdateMenus(MenuBatchUpdateDTO batchDTO) {
    List<SysMenu> menus = listByIds(batchDTO.getMenuIds());
    
    // 批量更新而非逐个更新
    updateBatchById(menus);
    
    return "批量操作完成";
}

4. 安全考虑

权限校验

@SaCheckPermission("sys:menu:dynamic:scan")
@PostMapping("/scan") 
public Result<MenuScanResultVO> scanRoutes(@Valid @RequestBody DynamicMenuScanDTO scanDTO) {
    // 验证前端路径安全性
    if (!isPathSafe(scanDTO.getFrontendPath())) {
        throw new APIException("路径不安全");
    }
    
    // 限制扫描文件数量
    if (scanDTO.getMaxFiles() > 1000) {
        scanDTO.setMaxFiles(1000);
    }
    
    return Result.data(dynamicMenuService.scanFrontendRoutes(scanDTO));
}

数据校验

public List<String> validateMenuConfig(DynamicMenuConfigDTO config) {
    List<String> errors = new ArrayList<>();
    
    // SQL注入防护
    if (containsSqlKeywords(config.getName())) {
        errors.add("菜单名称包含非法字符");
    }
    
    // XSS防护
    if (containsScriptTags(config.getDescription())) {
        errors.add("菜单描述包含脚本标签");
    }
    
    return errors;
}

故障排除

常见问题

1. 扫描不到文件

问题:扫描结果为空或文件数量不正确

解决步骤:

  1. 检查 frontendPath 路径是否正确
  2. 验证 includePatterns 和 excludePatterns 配置
  3. 确认服务器有文件访问权限
  4. 查看日志中的详细错误信息
# 检查路径是否存在
ls -la /path/to/frontend/src/views

# 检查文件权限
find /path/to/frontend -name "*.vue" -type f

2. 菜单同步失败

问题:扫描成功但菜单同步时报错

解决步骤:

  1. 检查数据库连接是否正常
  2. 验证菜单数据的合法性
  3. 确认当前用户有菜单创建权限
  4. 查看数据库中是否有重复的菜单路径
-- 检查菜单重复
SELECT path, COUNT(*) as count 
FROM sys_menu 
WHERE customer_id = ? 
GROUP BY path 
HAVING count > 1;

3. 权限问题

问题:用户无法访问动态菜单管理功能

解决步骤:

  1. 检查用户角色和权限分配
  2. 验证 Spring Security 配置
  3. 确认菜单权限表中的数据
-- 检查用户权限
SELECT r.role_name, p.permission 
FROM sys_user_role ur
JOIN sys_role r ON ur.role_id = r.id
JOIN sys_role_menu rm ON r.id = rm.role_id  
JOIN sys_menu m ON rm.menu_id = m.id
WHERE ur.user_id = ?;

日志分析

开启调试日志

logging:
  level:
    com.ljwx.modules.system.service.impl.DynamicMenuServiceImpl: DEBUG
    com.ljwx.admin.controller.system.DynamicMenuController: DEBUG

关键日志示例

2024-01-15 14:30:00 [INFO ] 开始扫描前端路由文件,路径: /src/views
2024-01-15 14:30:01 [DEBUG] 发现文件: src/views/system/user/index.vue
2024-01-15 14:30:01 [DEBUG] 解析路由信息: {path: "/system/user", component: "views/system/user"}
2024-01-15 14:30:02 [INFO ] 扫描完成,发现文件: 45, 变更文件: 3, 新路由: 2

系统集成

与现有系统集成

1. 权限系统集成

动态菜单系统无缝集成现有的 Spring Security 权限框架:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/sys_menu/dynamic/**").hasAnyRole("ADMIN", "MENU_MANAGER")
            .anyRequest().authenticated()
        );
        return http.build();
    }
}

2. 多租户系统集成

与现有多租户架构完全兼容:

// 自动注入租户ID
@Component
public class TenantMenuInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        Long customerId = getCurrentCustomerId();
        request.setAttribute("customerId", customerId);
        return true;
    }
}

扩展开发

1. 自定义扫描规则

@Component
public class CustomRouteScanStrategy implements RouteScanStrategy {
    
    @Override
    public List<RouteInfo> scanRoutes(Path basePath, DynamicMenuScanDTO config) {
        // 实现自定义扫描逻辑
        return customScanLogic(basePath, config);
    }
}

2. 菜单模板扩展

@Service
public class MenuTemplateService {
    
    public List<DynamicMenuConfigDTO> loadTemplate(String templateName) {
        switch (templateName) {
            case "e-commerce":
                return createECommerceTemplate();
            case "crm":
                return createCrmTemplate();
            default:
                return createDefaultTemplate();
        }
    }
}

总结

动态菜单权限管理系统完美解决了 ljwx-admin 和 ljwx-boot 项目中的菜单管理痛点:

核心价值

  1. 自动化:前端页面自动发现和菜单生成,减少90%的手动配置工作
  2. 灵活性:支持拖拽排序、批量操作、实时预览等丰富的管理功能
  3. 多租户:完整的租户隔离和权限控制机制
  4. 易用性:直观的用户界面和完整的操作引导
  5. 安全性:全面的权限校验和数据安全保护

技术优势

  1. 高性能:多级缓存和批量操作优化
  2. 可扩展:模块化设计,支持自定义扩展
  3. 可维护:完整的日志记录和错误处理机制
  4. 标准化:遵循RESTful API设计规范

部署建议

  1. 生产环境:启用Redis缓存,配置数据库连接池
  2. 开发环境:开启调试日志,使用本地文件缓存
  3. 测试环境:配置自动化测试用例,验证各项功能

通过这套完整的动态菜单权限管理系统,开发团队可以大幅提升菜单管理效率,为用户提供更好的系统体验。