Explorar o código

岗位审核、测评完成

西格玛许 hai 2 semanas
pai
achega
d25e2151df
Modificáronse 3 ficheiros con 494 adicións e 0 borrados
  1. 126 0
      DEBUG_GUIDE.md
  2. 122 0
      PROBLEM_SUMMARY.md
  3. 246 0
      TREESELECT_EXECUTION_FLOW.md

+ 126 - 0
DEBUG_GUIDE.md

@@ -0,0 +1,126 @@
+# 商户端角色管理菜单树加载问题 - Debug 指南
+
+## 问题现象
+商户端访问"角色管理-新增"时,菜单权限树一直显示"加载中,请稍候",无法正常加载菜单数据。
+
+## 根本原因
+**数据库 `sys_menu` 表缺少 `platform_id` 字段**,导致后端查询时 SQL 报错或返回空结果。
+
+## 技术分析
+
+### 1. 接口调用链
+```
+前端: sj-merchant-web/src/views/system/role/index.vue
+  → handleAdd() 方法
+  → getMenuTreeselect() 
+  → API: GET /system/menu/treeselect
+
+后端: SysMenuController.treeselect()
+  → SysMenuServiceImpl.selectMenuList()
+  → 查询条件包含: .eq(SysMenu::getPlatformId, platformId)
+```
+
+### 2. 平台识别机制
+- 前端配置: `.env.development` 中 `VITE_APP_PLATFORM_CODE="SHANGHUDUAN"`
+- 请求头: `PLATFORM_CODE: SHANGHUDUAN`
+- 后端解析: `PlatformUtils.getId()` → 返回 1(商户端)
+- 平台枚举:
+  - `ADMIN(0, "PINGTAIDUAN", "平台端")`
+  - `MERCHANT(1, "SHANGHUDUAN", "商户端")`
+
+### 3. 问题代码位置
+**SysMenuServiceImpl.java:82-87**
+```java
+int platformId = menu.getPlatformId()==null? PlatformUtils.getId() : menu.getPlatformId();
+menuList = baseMapper.selectVoList(
+    wrapper.eq(SysMenu::getPlatformId, platformId) // ← 这里强制过滤 platform_id
+```
+
+**SysMenu.java:104-105**
+```java
+// 平台ID
+private Integer platformId;  // ← 实体类有字段
+```
+
+**ry_vue_5.X.sql:235-257**
+```sql
+create table sys_menu (
+    menu_id           bigint(20)      not null,
+    menu_name         varchar(50)     not null,
+    -- ... 其他字段
+    remark            varchar(500)    default '',
+    -- ❌ 缺少 platform_id 字段
+    primary key (menu_id)
+);
+```
+
+## 解决方案
+
+### 步骤 1: 执行数据库修复脚本
+```bash
+# 在 MySQL 中执行
+mysql -u root -p your_database < script/sql/add_platform_id_to_menu.sql
+```
+
+### 步骤 2: 为现有菜单分配平台
+根据业务需求,决定哪些菜单属于哪个平台:
+
+```sql
+-- 查看当前所有菜单
+SELECT menu_id, menu_name, parent_id FROM sys_menu ORDER BY menu_id;
+
+-- 方案 A: 所有菜单都设为平台端(管理端和商户端共享)
+UPDATE sys_menu SET platform_id = 0;
+
+-- 方案 B: 区分平台(根据实际业务调整)
+-- 平台端菜单(租户管理、系统监控等)
+UPDATE sys_menu SET platform_id = 0 WHERE menu_id IN (6, 2, 109, 113);
+
+-- 商户端菜单(用户管理、角色管理等)
+UPDATE sys_menu SET platform_id = 1 WHERE menu_id IN (1, 100, 101, 102, 103, 104, 105);
+```
+
+### 步骤 3: 验证修复
+1. 重启后端服务
+2. 清除浏览器缓存
+3. 商户端登录 → 系统管理 → 角色管理 → 新增
+4. 检查菜单树是否正常加载
+
+## Debug 断点位置
+
+如需进一步调试,可在以下位置打断点:
+
+1. **控制器层**
+   - `SysMenuController.java:87` - 查看返回的菜单列表
+
+2. **服务层**
+   - `SysMenuServiceImpl.java:83` - 查看 SQL 查询条件
+   - `SysMenuServiceImpl.java:87` - 查看 platformId 值
+
+3. **平台工具类**
+   - `PlatformUtils.java:8` - 查看请求头中的 PLATFORM_CODE
+   - `Platform.java:38` - 查看平台 ID 转换逻辑
+
+## 常见问题
+
+### Q1: 执行脚本后仍然加载不出来?
+检查后端日志,查看是否有 SQL 异常或权限问题。
+
+### Q2: 如何确认 platform_id 字段已添加?
+```sql
+DESC sys_menu;
+-- 或
+SHOW COLUMNS FROM sys_menu LIKE 'platform_id';
+```
+
+### Q3: 商户端应该看到哪些菜单?
+根据业务设计,通常商户端只能看到 `platform_id = 1` 的菜单。如果需要共享某些菜单,可以考虑:
+- 复制菜单数据,分别设置不同的 platform_id
+- 或修改后端逻辑,支持 `platform_id IN (0, 1)` 的查询
+
+## 相关文件
+- 前端接口定义: `sj-merchant-web/src/api/system/menu/index.ts`
+- 前端页面: `sj-merchant-web/src/views/system/role/index.vue`
+- 后端控制器: `ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java`
+- 后端服务: `ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java`
+- 平台工具: `yp-common-platform/src/main/java/org/dromara/common/platform/`

+ 122 - 0
PROBLEM_SUMMARY.md

@@ -0,0 +1,122 @@
+# 商户端角色管理菜单树加载问题 - 完整分析报告
+
+## 问题现象
+商户端访问"系统管理 → 角色管理 → 新增"时,菜单权限树一直显示"加载中,请稍候",无法加载菜单数据。
+而平台端相同功能正常。
+
+## 问题根源
+
+### 核心原因
+**商户端用户的角色分配的菜单,其 `platform_id` 不是 1(商户端),导致查询时被过滤掉。**
+
+### 技术分析
+
+1. **查询逻辑**(`SysMenuServiceImpl.selectMenuList`):
+   ```java
+   // 第一步:权限过滤(非超管用户)
+   if (!LoginHelper.isSuperAdmin(userId)) {
+       wrapper.inSql(SysMenu::getMenuId, baseMapper.buildMenuByUserSql(userId));
+   }
+   
+   // 第二步:平台过滤
+   int platformId = PlatformUtils.getId(); // 商户端返回 1
+   wrapper.eq(SysMenu::getPlatformId, platformId);
+   ```
+
+2. **问题链路**:
+   ```
+   商户端用户登录
+   → 请求头携带 PLATFORM_CODE: "SHANGHUDUAN"
+   → PlatformUtils.getId() 返回 1
+   → 查询条件:
+      - 用户角色分配的菜单(通过 sys_role_menu)
+      - AND platform_id = 1
+   → 但角色分配的菜单都是 platform_id = 0 或 NULL
+   → 结果为空
+   ```
+
+3. **数据验证结果**:
+   - 数据库中 `platform_id = 0` 有 134 条菜单(平台端)
+   - 数据库中 `platform_id = 1` 有 6 条菜单(商户端)
+   - 商户端用户角色分配的菜单:`platform_id = NULL` 或 `0`,没有 `platform_id = 1` 的
+
+## 解决方案
+
+### 方案 1:为商户端角色重新分配菜单(推荐)
+
+```sql
+-- 1. 确认商户端用户ID和角色ID
+SET @merchant_user_id = 2038434195944886273;
+
+SELECT sur.role_id, sr.role_name
+FROM sys_user_role sur
+LEFT JOIN sys_role sr ON sur.role_id = sr.role_id
+WHERE sur.user_id = @merchant_user_id;
+
+-- 2. 删除旧的菜单权限
+SET @merchant_role_id = 100; -- 替换为实际的角色ID
+DELETE FROM sys_role_menu WHERE role_id = @merchant_role_id;
+
+-- 3. 为角色分配所有 platform_id=1 的菜单
+INSERT INTO sys_role_menu (role_id, menu_id)
+SELECT @merchant_role_id, menu_id
+FROM sys_menu
+WHERE platform_id = 1;
+```
+
+### 方案 2:将现有菜单改为商户端可用
+
+如果商户端需要的菜单功能和平台端一致,可以将这些菜单的 `platform_id` 改为 1:
+
+```sql
+-- 将系统管理相关菜单设为商户端
+UPDATE sys_menu SET platform_id = 1 
+WHERE menu_id IN (
+    1,    -- 系统管理
+    100,  -- 用户管理
+    101,  -- 角色管理
+    102,  -- 菜单管理
+    103,  -- 部门管理
+    -- ... 其他需要的菜单ID
+);
+```
+
+### 方案 3:同时支持多平台(修改代码)
+
+如果希望某些菜单在两个平台都可见,需要修改查询逻辑,支持 `platform_id IN (0, 1)` 或引入菜单平台关联表。
+
+## 验证步骤
+
+1. **执行修复SQL**
+2. **验证数据**:
+   ```sql
+   -- 查看角色菜单的平台分布
+   SELECT sm.platform_id, COUNT(*) as count
+   FROM sys_role_menu srm
+   LEFT JOIN sys_menu sm ON srm.menu_id = sm.menu_id
+   WHERE srm.role_id = @merchant_role_id
+   GROUP BY sm.platform_id;
+   
+   -- 应该显示 platform_id = 1 有数据
+   ```
+
+3. **重启后端服务**
+4. **清除浏览器缓存**
+5. **商户端登录测试**:系统管理 → 角色管理 → 新增
+
+## 相关文件
+
+- 前端页面:`sj-merchant-web/src/views/system/role/index.vue`
+- 前端API:`sj-merchant-web/src/api/system/menu/index.ts`
+- 后端控制器:`ruoyi-system/.../SysMenuController.java:84-89`
+- 后端服务:`ruoyi-system/.../SysMenuServiceImpl.java:73-93`
+- 平台工具:`yp-common-platform/.../PlatformUtils.java`
+- 修复脚本:`script/sql/fix_merchant_role_permission.sql`
+
+## 总结
+
+问题不在于代码逻辑或 `platform_id` 字段缺失,而是**数据配置问题**:
+- 商户端用户的角色分配的菜单权限(`sys_role_menu`)指向的菜单,其 `platform_id` 不是商户端的值(1)
+- 查询时先通过角色过滤,再通过平台过滤,两个条件的交集为空
+
+修复方式:确保商户端角色分配的菜单,其 `platform_id = 1`。

+ 246 - 0
TREESELECT_EXECUTION_FLOW.md

@@ -0,0 +1,246 @@
+# /system/menu/treeselect 接口执行流程详解
+
+## 完整执行顺序
+
+### 1. 控制器层 - SysMenuController.treeselect()
+**位置**: `SysMenuController.java:84-89`
+
+```java
+@SaCheckPermission("system:menu:query")  // ← 步骤1: 权限校验
+@GetMapping("/treeselect")
+public R<List<Tree<Long>>> treeselect(SysMenuBo menu) {
+    // 步骤2: 查询菜单列表
+    List<SysMenuVo> menus = menuService.selectMenuList(menu, LoginHelper.getUserId());
+    
+    // 步骤3: 构建树形结构
+    return R.ok(menuService.buildMenuTreeSelect(menus));
+}
+```
+
+**执行步骤**:
+1. **权限校验**: `@SaCheckPermission("system:menu:query")` - 检查用户是否有 `system:menu:query` 权限
+2. **获取用户ID**: `LoginHelper.getUserId()` - 从当前登录上下文获取用户ID
+3. **调用服务层**: `menuService.selectMenuList(menu, userId)`
+4. **构建树结构**: `menuService.buildMenuTreeSelect(menus)`
+5. **返回结果**: 包装成 `R<List<Tree<Long>>>` 返回
+
+---
+
+### 2. 服务层 - SysMenuServiceImpl.selectMenuList()
+**位置**: `SysMenuServiceImpl.java:73-93`
+
+```java
+public List<SysMenuVo> selectMenuList(SysMenuBo menu, Long userId) {
+    List<SysMenuVo> menuList;
+    LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
+    
+    // ========== 步骤A: 权限过滤 ==========
+    if (!LoginHelper.isSuperAdmin(userId)) {
+        // 非超管用户,需要通过角色过滤菜单
+        wrapper.inSql(SysMenu::getMenuId, baseMapper.buildMenuByUserSql(userId));
+    }
+    
+    // ========== 步骤B: 平台过滤 ==========
+    int platformId = menu.getPlatformId()==null? PlatformUtils.getId() : menu.getPlatformId();
+    
+    // ========== 步骤C: 构建查询条件并执行 ==========
+    menuList = baseMapper.selectVoList(
+        wrapper.like(StringUtils.isNotBlank(menu.getMenuName()), SysMenu::getMenuName, menu.getMenuName())
+            .eq(StringUtils.isNotBlank(menu.getVisible()), SysMenu::getVisible, menu.getVisible())
+            .eq(StringUtils.isNotBlank(menu.getStatus()), SysMenu::getStatus, menu.getStatus())
+            .eq(SysMenu::getPlatformId, platformId)  // ← 关键:平台过滤
+            .eq(StringUtils.isNotBlank(menu.getMenuType()), SysMenu::getMenuType, menu.getMenuType())
+            .eq(ObjectUtil.isNotNull(menu.getParentId()), SysMenu::getParentId, menu.getParentId())
+            .orderByAsc(SysMenu::getParentId)
+            .orderByAsc(SysMenu::getOrderNum)
+    );
+    
+    return menuList;
+}
+```
+
+**详细执行流程**:
+
+#### 步骤A: 权限过滤(非超管用户)
+```java
+if (!LoginHelper.isSuperAdmin(userId)) {
+    wrapper.inSql(SysMenu::getMenuId, baseMapper.buildMenuByUserSql(userId));
+}
+```
+
+**生成的SQL子查询** (`buildMenuByUserSql`):
+```sql
+SELECT menu_id 
+FROM sys_role_menu 
+WHERE role_id IN (
+    SELECT sur.role_id 
+    FROM sys_user_role sur
+    LEFT JOIN sys_role sr ON sr.role_id = sur.role_id
+    WHERE sur.user_id = ? 
+      AND sr.status = '0'
+)
+```
+
+**作用**: 获取该用户所有角色分配的菜单ID列表
+
+#### 步骤B: 平台过滤
+```java
+int platformId = PlatformUtils.getId();
+```
+
+**执行流程**:
+1. `PlatformUtils.getId()` 调用
+2. 从请求头获取 `PLATFORM_CODE`
+3. 根据 `PLATFORM_CODE` 转换为平台ID:
+   - `"PINGTAIDUAN"` → 0 (平台端)
+   - `"SHANGHUDUAN"` → 1 (商户端)
+
+#### 步骤C: 最终SQL查询
+
+**商户端用户的完整SQL**:
+```sql
+SELECT * 
+FROM sys_menu
+WHERE menu_id IN (
+    -- 用户角色分配的菜单ID列表
+    SELECT menu_id FROM sys_role_menu WHERE role_id IN (...)
+)
+AND platform_id = 1  -- 商户端
+ORDER BY parent_id, order_num
+```
+
+**关键点**: 这是一个 **AND 条件**,必须同时满足:
+- 菜单ID在用户角色分配的列表中
+- 菜单的 platform_id = 1
+
+---
+
+### 3. 数据层 - SysMenuMapper
+**位置**: `SysMenuMapper.java`
+
+使用 MyBatis-Plus 的 `LambdaQueryWrapper` 自动生成SQL并执行。
+
+---
+
+### 4. 树结构构建 - buildMenuTreeSelect()
+**位置**: `SysMenuServiceImpl.java:250-265`
+
+```java
+public List<Tree<Long>> buildMenuTreeSelect(List<SysMenuVo> menus) {
+    if (CollUtil.isEmpty(menus)) {
+        return CollUtil.newArrayList();  // ← 如果查询结果为空,返回空列表
+    }
+    return TreeBuildUtils.build(menus, (menu, tree) -> {
+        tree.setId(menu.getMenuId())
+            .setParentId(menu.getParentId())
+            .setName(menu.getMenuName())
+            .setWeight(menu.getOrderNum());
+        // ... 设置其他属性
+    });
+}
+```
+
+---
+
+## 为什么商户端查不到数据?
+
+### 问题定位
+
+根据执行流程,商户端查询的SQL实际上是:
+
+```sql
+SELECT * 
+FROM sys_menu
+WHERE menu_id IN (
+    -- 步骤1: 获取用户角色分配的菜单
+    SELECT menu_id FROM sys_role_menu 
+    WHERE role_id IN (
+        SELECT role_id FROM sys_user_role WHERE user_id = 2038434195944886273
+    )
+)
+AND platform_id = 1  -- 步骤2: 过滤商户端菜单
+```
+
+### 问题原因
+
+**两个条件的交集为空**:
+
+1. **步骤1结果**: 用户角色分配的菜单(假设有 100 条)
+   - 这些菜单的 `platform_id` 可能是 0、NULL 或其他值
+
+2. **步骤2过滤**: `platform_id = 1`
+   - 只保留商户端菜单
+
+3. **交集**: 如果步骤1的100条菜单中,没有一条的 `platform_id = 1`
+   - 最终结果为空
+
+### 验证方法
+
+执行以下SQL验证:
+
+```sql
+-- 查看用户角色分配的菜单的平台分布
+SELECT sm.platform_id, COUNT(*) as count
+FROM sys_user_role sur
+LEFT JOIN sys_role_menu srm ON sur.role_id = srm.role_id
+LEFT JOIN sys_menu sm ON srm.menu_id = sm.menu_id
+WHERE sur.user_id = 2038434195944886273
+GROUP BY sm.platform_id;
+```
+
+**如果结果显示**:
+- `platform_id = 0`: 100 条
+- `platform_id = 1`: 0 条
+
+**说明**: 用户角色分配的菜单都是平台端的,没有商户端的,所以查询结果为空。
+
+---
+
+## 解决方案
+
+### 方案1: 修改菜单数据(推荐)
+
+将用户角色需要的菜单改为 `platform_id = 1`:
+
+```sql
+UPDATE sys_menu SET platform_id = 1 
+WHERE menu_id IN (
+    SELECT menu_id FROM sys_role_menu 
+    WHERE role_id IN (
+        SELECT role_id FROM sys_user_role 
+        WHERE user_id = 2038434195944886273
+    )
+);
+```
+
+### 方案2: 重新分配角色菜单
+
+删除旧的菜单权限,分配新的 `platform_id = 1` 的菜单:
+
+```sql
+-- 1. 获取角色ID
+SELECT role_id FROM sys_user_role WHERE user_id = 2038434195944886273;
+
+-- 2. 删除旧权限
+DELETE FROM sys_role_menu WHERE role_id = ?;
+
+-- 3. 分配新权限
+INSERT INTO sys_role_menu (role_id, menu_id)
+SELECT ?, menu_id FROM sys_menu WHERE platform_id = 1;
+```
+
+---
+
+## 调试建议
+
+在以下位置添加日志:
+
+1. **SysMenuServiceImpl.java:82** - 查看 `platformId` 的值
+2. **SysMenuServiceImpl.java:92** - 查看 `menuList.size()`
+3. **PlatformUtils.java:8** - 查看请求头中的 `PLATFORM_CODE`
+
+```java
+log.info("查询菜单 - userId={}, platformId={}, isSuperAdmin={}", 
+    userId, platformId, LoginHelper.isSuperAdmin(userId));
+log.info("查询结果 - menuList.size()={}", menuList.size());
+```