using SqlSugar; namespace Admin.NET.Plugin.AiDOP.Infrastructure; /// /// S0 租户内数据访问统一入口(S0-P0-TENANT-ID-CLOSURE)。 /// /// 铁律: /// - tenant_id 是唯一租户安全边界;Domain / DomainCode / CompanyRefId / FactoryRefId /// 只能作为业务筛选条件,永远不得替代租户条件; /// - 租户值只来自 (认证后的 JWT claim), /// 不接受前端 payload / query / DTO 传入; /// - 全局 AOP ITenantIdFilter 只作为 defense-in-depth,Controller 必须显式带租户条件; /// - 按主键取单条时,"不存在" 与 "属于其他租户" 一律返回 null,不泄露记录存在性。 /// /// qms_* 系列实体未实现 ITenantIdFilter(全局 AOP 对其不生效),故另提供按物理列 /// tenant_id 收口的重载,供 S0 Quality 控制器使用。 /// public static class AdoS0TenantQuery { /// 租户内查询(实现 ITenantIdFilter 的实体)。后续可继续追加业务筛选条件。 public static ISugarQueryable ScopedTo(this SqlSugarRepository rep, long tenantId) where T : class, ITenantIdFilter, new() => rep.AsQueryable().Where(x => x.TenantId == tenantId); /// 租户内按主键取单条;不存在或不属当前租户均返回 null。 public static Task ByIdScopedAsync(this SqlSugarRepository rep, long id, long tenantId) where T : class, ITenantIdFilter, new() => rep.AsQueryable().Where(x => x.TenantId == tenantId).In(id).FirstAsync(); /// /// 租户内查询(未实现 ITenantIdFilter、但物理表有 tenant_id 列的实体,如 qms_*)。 /// 显式按物理列收口,不依赖全局 AOP。 /// public static ISugarQueryable ScopedToByColumn(this SqlSugarRepository rep, long tenantId) where T : class, new() => rep.AsQueryable().Where("tenant_id = @__tid", new { __tid = tenantId }); /// 租户内按主键取单条(qms_* 等无 ITenantIdFilter 实体);越租户一律返回 null。 public static Task ByIdScopedByColumnAsync(this SqlSugarRepository rep, long id, long tenantId) where T : class, new() => rep.AsQueryable().Where("tenant_id = @__tid", new { __tid = tenantId }).In(id).FirstAsync(); }