diff --git a/基于手机号的数据权限过滤功能说明.md b/基于手机号的数据权限过滤功能说明.md new file mode 100644 index 0000000..c599230 --- /dev/null +++ b/基于手机号的数据权限过滤功能说明.md @@ -0,0 +1,332 @@ +# 基于手机号的数据权限过滤功能实现说明 + +## 功能概述 + +实现了基于登录用户手机号的数据权限过滤功能,保护用户隐私,确保普通用户只能查看与自己相关的装车订单和运送清单。 + +## 核心需求 + +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` (运送清单列表) +