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