| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 |
- using Admin.NET.Plugin.AiDOP.Entity.S8;
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
- namespace Admin.NET.Plugin.AiDOP.Service.S8;
- /// <summary>
- /// S8-STEP6C-CFG-NOTIFY-FIX-AND-CERT-1:通知分层「平台默认 / 工厂覆盖」的**单一** merge 契约。
- ///
- /// <para><b>覆盖粒度 = (scene_code, 归一 severity, 归一 level_code)</b>,即逐层覆盖。</para>
- ///
- /// 修正前(S8-STEP6C-CFG-NOTIFY-CLOSURE-1 及更早)覆盖粒度是整个 (scene, severity) 键:
- /// resolver 先按 (tenant,factory,scene,severity) 精确查,**仅当该键 0 行**才回落 (0,0)。
- /// 后果是工厂只要为某一层建一行(例如只配 L2_MANAGER),平台默认在该 scene+severity 下的
- /// **其余层级(L1/L3)会整体消失**——工厂本意是「补一层」,实际却是「换掉整组」,
- /// 且该副作用在页面上不可见。本类把粒度收敛到 level,使「补一层」就只影响那一层。
- ///
- /// ⚠️ <b>resolver 与配置页 ListAsync 必须都走 <see cref="Effective"/>。</b>
- /// 两处一旦各自实现,就会出现「页面显示一种生效配置、运行时按另一种派发」的静默背离
- /// (已实测过一次:resolver 用 (0,0) 基线派发通知,而配置页只查精确租户 → 页面空表、通知照发)。
- /// </summary>
- public static class S8NotificationLayerMerge
- {
- /// <summary>层级归一:去空白 + 大写。历史脏数据存在 level_code = '' 的行,归一后自成一组,不与合法层级混淆。</summary>
- public static string NormalizeLevel(string? levelCode) =>
- (levelCode ?? string.Empty).Trim().ToUpperInvariant();
- /// <summary>merge 键:(scene_code, 归一 severity, 归一 level_code)。</summary>
- public static (string Scene, string Severity, string Level) KeyOf(AdoS8NotificationLayer x) =>
- (x.SceneCode ?? string.Empty, S8SeverityCode.Normalize(x.Severity), NormalizeLevel(x.LevelCode));
- /// <summary>
- /// 从「平台默认行 ∪ 本工厂行」的候选集算出生效集合:逐 (scene, severity, level) 取工厂优先,
- /// 该层无工厂行则回落平台默认。
- ///
- /// 排序为稳定确定序:scene → severity → level → id。
- /// 末位 <c>Id</c> 是必要的——DB 侧无唯一索引(唯一性仅由 service 层 AnyAsync 前置检查保证),
- /// 同键重复行若依赖 DB 返回序,派发顺序会在不同查询计划下漂移。
- /// </summary>
- public static List<AdoS8NotificationLayer> Effective(IEnumerable<AdoS8NotificationLayer> candidates) =>
- S8ConfigScope.EffectiveRows(candidates, x => x.TenantId, x => x.FactoryId, KeyOf)
- .OrderBy(x => x.SceneCode, StringComparer.Ordinal)
- .ThenBy(x => S8SeverityCode.Normalize(x.Severity), StringComparer.Ordinal)
- .ThenBy(x => NormalizeLevel(x.LevelCode), StringComparer.Ordinal)
- .ThenBy(x => x.Id)
- .ToList();
- }
|