数据处理功能
数据权限过滤
几乎在任何业务系统中,都离不开权限的设计。权限设计通常包含两个维度:功能权限 + 数据权限。功能权限业界普遍采用 RBAC(Role-Based Access Control,基于角色的访问控制) 方案,而数据权限则用于控制用户能够访问哪些具体的数据。
数据权限功能是应用程序中的核心访问控制机制,确保只有经过授权的用户或角色才能访问特定数据。其设计意义不仅在于保障数据的安全性与完整性,还能显著提升应用程序的可用性和性能:
- 性能优化:通过限制用户的数据访问范围,减少不必要的数据查询和操作,提高系统响应速度
- 体验提升:根据用户角色和权限动态展示数据和功能,使用户只看到与其相关的内容,避免信息过载
HMX 平台采用 轻量级、灵活可控 的数据权限设计思路,不追求"万能"的数据权限解决方案,而是将核心能力交给框架,将复杂业务逻辑交给业务系统自身实现。
核心设计理念
- 数据权限不是万能的,基础的由框架平台提供,复杂的由业务系统自己实现。
平台不提供类似"订单金额小于 100 万"这类强业务属性的自动数据过滤,这类需求应由业务系统根据自身场景自行设计与实现。
1. 静态元数据权限配置
平台将 元数据或键值对 配置为数据资源,并为角色授予这些静态元数据的访问权限:
数据资源示例:工厂数据资源
├── 工厂编码:1001(华东工厂)
├── 工厂编码:1002(华南工厂)
└── 工厂编码:1003(华北工厂)角色授权示例:张总
└── 具备 1001 工厂的数据权限2. 业务系统自主控制
业务系统获取当前角色所拥有的静态元数据权限(如工厂编码列表),然后根据实际需求 自主决定 是否使用 SQL 的 IN 表达式来过滤数据范围(使用 linq 查询,不是真的 sql in)
3. 框架能力边界
| 能力层级 | 平台提供的功能 | 业务系统负责 |
|---|---|---|
| 数据资源定义 | ✅ 支持元数据/键值对配置 | - |
| 角色权限授予 | ✅ 支持角色与数据资源关联 | - |
| 权限查询接口 | ✅ 提供获取角色数据权限的 API | - |
| SQL IN 条件拼接 | ❌ 不自动拦截拼接 | ✅ 业务系统自主实现 |
| 复杂业务逻辑过滤 | ❌ 不提供(如:金额 < 100 万) | ✅ 业务系统自行设计 |
设计优势
- 灵活可控:业务系统可以根据场景决定是否启用数据权限过滤,避免框架过度侵入
- 简单易懂:不追求复杂的数据规则引擎,降低开发人员学习成本
- 高性能:避免框架层面的自动 SQL 拦截带来的性能损耗
- 边界清晰:框架提供基础能力,业务逻辑由业务系统掌控,职责分明
HMX 平台的数据权限设计遵循 "框架提供基础,业务掌控复杂" 的原则,通过静态元数据配置 + 角色授权 + 业务系统自主拼接 SQL IN 表达式的轻量级方案,既满足了常见的数据权限隔离需求,又避免了过度设计带来的复杂度和性能开销。对于复杂的业务规则过滤(如动态条件、多维度组合等),由业务系统根据实际场景自行实现,确保平台的通用性与灵活性。
连表查询增强
在钢铁制造业务系统中,多表关联查询是日常开发中无法回避的核心需求。例如查询工单时需要关联产线信息、查询质检记录时需要关联订单和物料信息、查询库存时需要关联仓库和库位信息等。LINQ to DB 作为 .NET 生态下的数据访问增强工具,将连表查询作为一等公民,通过 LINQ 表达式即可实现类型安全、智能感知、可读性强的多表关联查询,大幅简化开发效率。HMX 平台采用 LINQ to DB 作为数据访问增强工具,在保留原生 SQL 灵活性的同时,提供强类型 LINQ 查询能力。
核心特性
- 原生连表支持:无需任何扩展即可使用
join、left join、inner join等标准 SQL 关联语法 - 类型安全:通过 Lambda 表达式访问字段,编译时检查正确性,避免运行时字段名错误
- IDE 智能感知:表名、字段名自动补全,重构时自动更新引用
- LINQ 标准语法:与操作内存集合的写法一致,降低学习成本
业务场景一:生产工单查询(订单 + 产线 + 排产计划)
生产排产时需要查询工单详情,同时显示关联的销售订单号、产线名称、排产计划量等信息。
/// <summary>
/// 分页查询生产工单列表(含订单信息、产线信息、排产计划)
/// </summary>
public async Task<PageResult<WorkOrderDto>> GetWorkOrderPage(WorkOrderQuery request)
{
var query = from workOrder in _dbContext.PpWorkOrder
join salesOrder in _dbContext.SalesOrder
on workOrder.OrderId equals salesOrder.Id
join productionLine in _dbContext.BaseProductionLine
on workOrder.LineId equals productionLine.Id
join schedule in _dbContext.PpScheduleDetail
on workOrder.Id equals schedule.WorkOrderId into scheduleGroup
from schedule in scheduleGroup.DefaultIfEmpty()
where workOrder.DelFlag == "0" && salesOrder.DelFlag == "0"
select new WorkOrderDto
{
WorkOrderId = workOrder.Id,
WorkOrderNo = workOrder.OrderNo,
SalesOrderNo = salesOrder.OrderNo,
CustomerName = salesOrder.CustomerName,
ProductName = salesOrder.ProductName,
ProductSpec = salesOrder.ProductSpec,
PlanQty = workOrder.PlanQty,
CompletedQty = workOrder.CompletedQty,
ProductionLineName = productionLine.LineName,
ScheduleQty = schedule != null ? schedule.PlanQty : 0,
PlanStartTime = workOrder.PlanStartTime,
PlanEndTime = workOrder.PlanEndTime,
WorkOrderStatus = workOrder.Status
};
return await query.ToPagedListAsync(request);
}