初步实现数据权限过滤

This commit is contained in:
xuqiuyun
2025-10-16 17:31:44 +08:00
parent 804b83b369
commit 7c7d2487ea

View File

@@ -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<Delivery> pageQueryListLog(DeliverListDto dto) {
// 1. 获取当前登录用户手机号
String currentUserMobile = SecurityUtil.getUserMobile();
// 2. 查询所有数据
List<Delivery> 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<String> 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<String, Object> 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<Integer> allMemberIds = new ArrayList<>();
// ... 添加所有 fundId, buyerId 等
// 批量查询
List<Member> members = memberMapper.selectBatchIds(allMemberIds);
Map<Integer, String> 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` (运送清单列表)