# 基于手机号的数据权限过滤功能实现说明 ## 功能概述 实现了基于登录用户手机号的数据权限过滤功能,保护用户隐私,确保普通用户只能查看与自己相关的装车订单和运送清单。 ## 核心需求 1. **角色识别**:系统识别四个角色的手机号 - 司机(Driver) - 供应商(Supplier,可能有多个) - 资金方(Fund) - 采购商(Buyer) 2. **权限规则**: - **超级管理员(roleId=1)**:可以查看所有订单和运送清单 - **普通用户**:只能查看与自己手机号相关的数据(四个角色中任意一个匹配即可) ## 技术实现 ### 1. 后端修改 #### 1.1 实体类修改 **文件**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Delivery.java` 添加了三个新的手机号字段(司机手机号已存在): ```java /** * 供应商手机号(逗号分隔) */ @TableField(exist = false) private String supplierMobile; /** * 资金方手机号 */ @TableField(exist = false) private String fundMobile; /** * 采购商手机号 */ @TableField(exist = false) private String buyerMobile; ``` #### 1.2 服务层修改 **文件**: `tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/service/impl/DeliveryServiceImpl.java` 修改了两个关键方法: ##### (1)装车订单列表查询 - `pageQueryListLog` ```java public PageResultResponse pageQueryListLog(DeliverListDto dto) { // 1. 获取当前登录用户手机号 String currentUserMobile = SecurityUtil.getUserMobile(); // 2. 查询所有数据 List resList = list(deliveryLambdaQueryWrapper); // 3. 关联查询四个角色的手机号 resList.forEach(deliveryLogVo -> { // 查询供应商手机号(supplierId是逗号分隔的) // 查询资金方手机号 // 查询采购商手机号 // 查询司机手机号 }); // 4. 数据权限过滤(非超级管理员) if (!SecurityUtil.isSuperAdmin()) { resList = resList.stream().filter(delivery -> { // 匹配司机手机号 // 匹配供应商手机号 // 匹配资金方手机号 // 匹配采购商手机号 return hasPermission; }).collect(Collectors.toList()); } return new PageResultResponse(filteredTotal, resList); } ``` ##### (2)运送清单列表查询 - `pageQuery` 实现逻辑与 `pageQueryListLog` 相同,确保两个页面的权限过滤一致。 ### 2. 权限判断逻辑 #### 2.1 超级管理员判断 ```java // 位置:SecurityUtil.java public static boolean isSuperAdmin() { Integer roleId = getRoleId(); return roleId != null && roleId.equals(RoleConstants.SUPER_ADMIN_ROLE_ID); } // RoleConstants.SUPER_ADMIN_ROLE_ID = 1 ``` #### 2.2 手机号匹配逻辑 普通用户的数据访问权限判断: ```java boolean hasPermission = false; // 1. 检查是否是司机 if (currentUserMobile.equals(delivery.getDriverMobile())) { hasPermission = true; } // 2. 检查是否是供应商(可能有多个) if (!hasPermission && delivery.getSupplierMobile() != null) { String[] supplierMobiles = delivery.getSupplierMobile().split(","); for (String mobile : supplierMobiles) { if (currentUserMobile.equals(mobile.trim())) { hasPermission = true; break; } } } // 3. 检查是否是资金方 if (!hasPermission && currentUserMobile.equals(delivery.getFundMobile())) { hasPermission = true; } // 4. 检查是否是采购商 if (!hasPermission && currentUserMobile.equals(delivery.getBuyerMobile())) { hasPermission = true; } return hasPermission; ``` ### 3. 角色手机号关联查询 系统在返回列表数据前,会根据角色ID查询对应的手机号: ```java // 1. 供应商手机号查询(supplierId可能是逗号分隔的多个ID) if (delivery.getSupplierId() != null) { String[] supplierIds = delivery.getSupplierId().split(","); List supplierMobiles = new ArrayList<>(); for (String supplierId : supplierIds) { Member supplier = memberMapper.selectById(Integer.parseInt(supplierId)); if (supplier != null) { supplierMobiles.add(supplier.getMobile()); } } delivery.setSupplierMobile(String.join(",", supplierMobiles)); } // 2. 资金方手机号查询 if (delivery.getFundId() != null) { Member fund = memberMapper.selectById(delivery.getFundId()); if (fund != null) { delivery.setFundMobile(fund.getMobile()); } } // 3. 采购商手机号查询 if (delivery.getBuyerId() != null) { Member buyer = memberMapper.selectById(delivery.getBuyerId()); if (buyer != null) { delivery.setBuyerMobile(buyer.getMobile()); } } // 4. 司机手机号查询 if (delivery.getDriverId() != null) { Map driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId()); if (driverInfo != null) { delivery.setDriverMobile((String) driverInfo.get("mobile")); } } ``` ## 影响范围 ### 1. 受影响的页面 - **装车订单列表页** (`/shipping/loadingOrder`) - 接口:`/delivery/pageDeliveryOrderList` - **运送清单列表页** (`/entry/attestation`) - 接口:`/delivery/pageQueryList` ### 2. 不受影响的功能 - 订单详情查询 - 订单创建/编辑 - 设备分配 - 其他管理功能 ## 测试建议 ### 测试场景1:超级管理员登录 **步骤**: 1. 使用 roleId=1 的超级管理员账号登录 2. 访问装车订单列表页 3. 访问运送清单列表页 **预期结果**: - 可以看到系统中所有的订单和运送清单 - 控制台输出:`=== 超级管理员,不执行数据权限过滤 ===` ### 测试场景2:司机登录 **步骤**: 1. 使用司机账号登录(手机号:13800138001) 2. 访问装车订单列表页 3. 访问运送清单列表页 **预期结果**: - 只能看到 driverMobile = '13800138001' 的订单 - 其他订单不可见 - 控制台输出匹配日志 ### 测试场景3:供应商登录 **步骤**: 1. 使用供应商账号登录(手机号:13900139001) 2. 访问装车订单列表页 3. 访问运送清单列表页 **预期结果**: - 只能看到 supplierMobile 包含 '13900139001' 的订单 - 其他订单不可见 ### 测试场景4:资金方/采购商登录 **步骤**: 1. 使用资金方或采购商账号登录 2. 访问装车订单列表页 3. 访问运送清单列表页 **预期结果**: - 只能看到与自己手机号匹配的订单 - 其他订单不可见 ### 测试场景5:多角色用户 **步骤**: 1. 创建一个订单,将同一个用户设置为多个角色(如既是司机又是供应商) 2. 使用该用户登录 3. 访问列表页 **预期结果**: - 该用户可以看到此订单(任意角色匹配即可) ## 调试信息 系统在控制台输出详细的调试日志,方便问题排查: ``` === 装车订单列表查询 - 当前登录用户手机号: 13800138001 === === 非超级管理员,执行数据权限过滤 === 当前用户手机号: 13800138001 订单 ZC20251016142501 - 匹配司机手机号 订单 ZC20251016142502 - 无权限,过滤掉 过滤后的订单数量: 1 ``` ## 数据库表结构 ### delivery 表 相关字段: - `supplier_id` VARCHAR - 供应商ID(逗号分隔) - `fund_id` INT - 资金方ID - `driver_id` INT - 司机ID - `buyer_id` INT - 采购商ID ### member 表 相关字段: - `id` INT - 会员ID - `mobile` VARCHAR - 手机号 - `type` INT - 会员类型 ### member_driver 表 相关字段: - `id` INT - 司机ID - `member_id` INT - 关联 member.id - `username` VARCHAR - 司机姓名 - `mobile` VARCHAR - 司机手机号 ## 性能优化建议 1. **批量查询优化**:当前实现对每个订单单独查询角色手机号,如果订单数量较多,建议改为批量查询: ```java // 收集所有角色ID List allMemberIds = new ArrayList<>(); // ... 添加所有 fundId, buyerId 等 // 批量查询 List members = memberMapper.selectBatchIds(allMemberIds); Map idToMobileMap = ...; ``` 2. **缓存优化**:对于频繁访问的会员信息,可以考虑添加 Redis 缓存。 3. **分页问题**:当前实现在内存中过滤数据后更新分页信息,这可能导致分页不准确。建议: - 方案A:在数据库层面进行过滤(通过 SQL JOIN 查询) - 方案B:在应用层正确处理分页逻辑 ## 注意事项 1. **手机号格式**:确保系统中所有手机号格式一致(如:都是11位数字,无空格) 2. **空值处理**:代码中已处理 null 值情况,避免空指针异常 3. **供应商多值处理**:supplierId 可能是逗号分隔的多个值,需要分别处理 4. **调试日志**:生产环境建议关闭或降低日志级别 ## 版本信息 - 实现日期:2025-10-16 - 修改文件: - `Delivery.java` - `DeliveryServiceImpl.java` - 影响接口: - `/delivery/pageDeliveryOrderList` (装车订单列表) - `/delivery/pageQueryList` (运送清单列表)