| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 |
- using Admin.NET.Core;
- using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
- using SqlSugar;
- namespace Admin.NET.Plugin.AiDOP.DataPlatform.S0Dim;
- /// <summary>
- /// 解析 S0 标准层自动刷新的作用域:哪些租户 × 哪些维度。
- ///
- /// <para><b>租户权威 = <c>SysTenant</c>(<c>Status = Enable</c>)</b>,与
- /// <c>MdpRebuildScopeCatalog</c> / <c>S1MdpScopeCatalog</c> 同源。
- /// ⚠️ 表名是 PascalCase 的 <c>SysTenant</c>,不是 <c>sys_tenant</c>——查错名字会得到
- /// 「表不存在」并误判成「框架表不可达」。</para>
- ///
- /// <para>🔴 <b>与 S1/MdpRebuild 的唯一差异:不排除默认租户。</b>
- /// 那两个 catalog 都写了 <c>x.Id != SqlSugarConst.DefaultTenantId</c>,
- /// 但 S0 照抄会造成实际数据缺口 —— 实测默认租户 <c>1300000000001</c> 在
- /// <c>ImageType</c> 与 6 张 <c>qms_*</c> 字典里共有 <b>28 行真实数据</b>,
- /// 却 <b>从未刷新过一次</b>(<c>mdp_transform_run_log</c> 中该租户的 S0 记录数 = 0)。
- /// 根因是现有 HTTP 入口对它<b>结构性不可达</b>:
- /// <c>AdoS0DimOpsAuthorizeAttribute</c> 只放行超管,而 <c>AdoS0TenantScope</c>
- /// 又拒绝「超管 + 目标租户 = 默认租户」的组合,两道门的交集是空集。
- /// 定时作业不经过这两道 HTTP 门,正好补上这个洞。</para>
- ///
- /// <para><b>维度权威 = <see cref="S0DimCatalog.All"/></b>,动态遍历,不维护第二份 key 清单。
- /// 但要**减去 <c>mdp_entity.status != 1</c> 的已停用对象**:
- /// <c>S0DimMaterializer.AssertEntityContractAsync</c> 对停用对象是**抛异常**的,
- /// 手工触发时这是合理的 fail-fast(人主动停用了就该报错),
- /// 但自动作业每一拍都会为它产出一条 FAILED,把真实告警淹没。
- /// 所以「停用」要在入队前就与「失败」分开。</para>
- /// </summary>
- public sealed class S0DimScopeCatalog : ITransient
- {
- private readonly ISqlSugarClient _db;
- /// <summary>注入本库客户端。</summary>
- public S0DimScopeCatalog(ISqlSugarClient db) => _db = db;
- /// <summary>自动刷新的作用域。</summary>
- /// <param name="Tenants">启用租户(含默认租户,见类型注释)。</param>
- /// <param name="Definitions">已启用的维度定义(已剔除 <c>mdp_entity.status != 1</c>)。</param>
- /// <param name="DisabledKeys">被剔除的维度 key,供日志外化,不静默跳过。</param>
- public sealed record Scope(
- IReadOnlyList<long> Tenants,
- IReadOnlyList<S0DimDefinition> Definitions,
- IReadOnlyList<string> DisabledKeys);
- /// <summary>解析当前应覆盖的租户与维度。</summary>
- public async Task<Scope> ResolveAsync(CancellationToken ct = default)
- {
- var tenants = await _db.Queryable<SysTenant>()
- .Where(x => x.Status == StatusEnum.Enable)
- .Select(x => x.Id)
- .ToListAsync(ct);
- var disabled = (await _db.Queryable<MdpEntity>()
- .Where(x => x.EntityCode.StartsWith("S0_") && x.Status != 1)
- .Select(x => x.EntityCode)
- .ToListAsync(ct))
- .ToHashSet(StringComparer.Ordinal);
- var defs = S0DimCatalog.All.Where(d => !disabled.Contains(d.EntityCode)).ToList();
- var disabledKeys = S0DimCatalog.All.Where(d => disabled.Contains(d.EntityCode))
- .Select(d => d.Key).ToList();
- return new Scope(tenants.Distinct().OrderBy(x => x).ToList(), defs, disabledKeys);
- }
- }
|