9.0 KiB
9.0 KiB
基于手机号的数据权限过滤功能实现说明
功能概述
实现了基于登录用户手机号的数据权限过滤功能,保护用户隐私,确保普通用户只能查看与自己相关的装车订单和运送清单。
核心需求
-
角色识别:系统识别四个角色的手机号
- 司机(Driver)
- 供应商(Supplier,可能有多个)
- 资金方(Fund)
- 采购商(Buyer)
-
权限规则:
- 超级管理员(roleId=1):可以查看所有订单和运送清单
- 普通用户:只能查看与自己手机号相关的数据(四个角色中任意一个匹配即可)
技术实现
1. 后端修改
1.1 实体类修改
文件: tradeCattle/aiotagro-cattle-trade/src/main/java/com/aiotagro/cattletrade/business/entity/Delivery.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
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 超级管理员判断
// 位置: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 手机号匹配逻辑
普通用户的数据访问权限判断:
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查询对应的手机号:
// 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:超级管理员登录
步骤:
- 使用 roleId=1 的超级管理员账号登录
- 访问装车订单列表页
- 访问运送清单列表页
预期结果:
- 可以看到系统中所有的订单和运送清单
- 控制台输出:
=== 超级管理员,不执行数据权限过滤 ===
测试场景2:司机登录
步骤:
- 使用司机账号登录(手机号:13800138001)
- 访问装车订单列表页
- 访问运送清单列表页
预期结果:
- 只能看到 driverMobile = '13800138001' 的订单
- 其他订单不可见
- 控制台输出匹配日志
测试场景3:供应商登录
步骤:
- 使用供应商账号登录(手机号:13900139001)
- 访问装车订单列表页
- 访问运送清单列表页
预期结果:
- 只能看到 supplierMobile 包含 '13900139001' 的订单
- 其他订单不可见
测试场景4:资金方/采购商登录
步骤:
- 使用资金方或采购商账号登录
- 访问装车订单列表页
- 访问运送清单列表页
预期结果:
- 只能看到与自己手机号匹配的订单
- 其他订单不可见
测试场景5:多角色用户
步骤:
- 创建一个订单,将同一个用户设置为多个角色(如既是司机又是供应商)
- 使用该用户登录
- 访问列表页
预期结果:
- 该用户可以看到此订单(任意角色匹配即可)
调试信息
系统在控制台输出详细的调试日志,方便问题排查:
=== 装车订单列表查询 - 当前登录用户手机号: 13800138001 ===
=== 非超级管理员,执行数据权限过滤 ===
当前用户手机号: 13800138001
订单 ZC20251016142501 - 匹配司机手机号
订单 ZC20251016142502 - 无权限,过滤掉
过滤后的订单数量: 1
数据库表结构
delivery 表
相关字段:
supplier_idVARCHAR - 供应商ID(逗号分隔)fund_idINT - 资金方IDdriver_idINT - 司机IDbuyer_idINT - 采购商ID
member 表
相关字段:
idINT - 会员IDmobileVARCHAR - 手机号typeINT - 会员类型
member_driver 表
相关字段:
idINT - 司机IDmember_idINT - 关联 member.idusernameVARCHAR - 司机姓名mobileVARCHAR - 司机手机号
性能优化建议
-
批量查询优化:当前实现对每个订单单独查询角色手机号,如果订单数量较多,建议改为批量查询:
// 收集所有角色ID List<Integer> allMemberIds = new ArrayList<>(); // ... 添加所有 fundId, buyerId 等 // 批量查询 List<Member> members = memberMapper.selectBatchIds(allMemberIds); Map<Integer, String> idToMobileMap = ...; -
缓存优化:对于频繁访问的会员信息,可以考虑添加 Redis 缓存。
-
分页问题:当前实现在内存中过滤数据后更新分页信息,这可能导致分页不准确。建议:
- 方案A:在数据库层面进行过滤(通过 SQL JOIN 查询)
- 方案B:在应用层正确处理分页逻辑
注意事项
- 手机号格式:确保系统中所有手机号格式一致(如:都是11位数字,无空格)
- 空值处理:代码中已处理 null 值情况,避免空指针异常
- 供应商多值处理:supplierId 可能是逗号分隔的多个值,需要分别处理
- 调试日志:生产环境建议关闭或降低日志级别
版本信息
- 实现日期:2025-10-16
- 修改文件:
Delivery.javaDeliveryServiceImpl.java
- 影响接口:
/delivery/pageDeliveryOrderList(装车订单列表)/delivery/pageQueryList(运送清单列表)