Files
cattleTransportation/基于手机号的数据权限过滤功能说明.md
2025-10-16 17:31:44 +08:00

9.0 KiB
Raw Blame History

基于手机号的数据权限过滤功能实现说明

功能概述

实现了基于登录用户手机号的数据权限过滤功能,保护用户隐私,确保普通用户只能查看与自己相关的装车订单和运送清单。

核心需求

  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

添加了三个新的手机号字段(司机手机号已存在):

/**
 * 供应商手机号(逗号分隔)
 */
@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超级管理员登录

步骤

  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. 批量查询优化:当前实现对每个订单单独查询角色手机号,如果订单数量较多,建议改为批量查询:

    // 收集所有角色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 (运送清单列表)