SmartOpsTrustedScopeResolver.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. using Admin.NET.Core;
  2. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  3. public sealed record SmartOpsTrustedScope(long TenantId, long FactoryId, long UserId);
  4. /// <summary>
  5. /// 智慧诊断与改善闭环的可信作用域:只信登录上下文,工厂由租户关系解析。
  6. /// </summary>
  7. public sealed class SmartOpsTrustedScopeResolver : ITransient
  8. {
  9. public const long PlatformTenantId = SqlSugarConst.DefaultTenantId;
  10. private readonly ISqlSugarClient _db;
  11. private readonly UserManager _userManager;
  12. public SmartOpsTrustedScopeResolver(ISqlSugarClient db, UserManager userManager)
  13. {
  14. _db = db;
  15. _userManager = userManager;
  16. }
  17. public async Task<SmartOpsTrustedScope> ResolveAsync(long? candidateFactoryId = null)
  18. {
  19. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  20. SmartOpsTrustedScopeRules.EnsureBusinessTenant(tenantId);
  21. var orgId = await _db.Queryable<SysTenant>()
  22. .ClearFilter<ITenantIdFilter>()
  23. .Where(x => x.Id == tenantId)
  24. .Select(x => x.OrgId)
  25. .FirstAsync();
  26. var factoryId = SmartOpsTrustedScopeRules.ResolveFactory(tenantId, orgId, candidateFactoryId);
  27. var userId = _userManager.UserId;
  28. if (userId <= 0)
  29. throw Oops.Oh("无法确定当前用户,请重新登录");
  30. return new SmartOpsTrustedScope(tenantId, factoryId, userId);
  31. }
  32. }
  33. public static class SmartOpsTrustedScopeRules
  34. {
  35. public const long PlatformTenantId = SqlSugarConst.DefaultTenantId;
  36. public static void EnsureBusinessTenant(long tenantId)
  37. {
  38. if (tenantId <= 0 || tenantId == 1 || tenantId == PlatformTenantId)
  39. throw new InvalidOperationException("平台租户不得作为业务事实作用域");
  40. }
  41. /// <summary>
  42. /// 客户端工厂只是筛选建议。未授权或不存在时回落到租户 OrgId,不得使用外租户工厂。
  43. /// </summary>
  44. /// <remarks>
  45. /// 只判 <paramref name="orgId"/> 是否为空。
  46. /// 不得再用 <c>orgId == tenantId</c> 判「未配置」:Admin.NET 种子/SQL 脚本建的租户
  47. /// 其根组织 Id 与租户 Id 同号是官方约定(见 SysTenantSeedData「Id=OrgId=DefaultTenantId」
  48. /// 与 SysOrgSeedData 根组织),只有界面 AddTenant 建的租户才另发雪花号。
  49. /// 旧判据会把系统默认 / Ai-DOP 演示公司等种子租户全部误判为「未配置有效工厂」而拒绝服务。
  50. /// </remarks>
  51. public static long ResolveFactory(long tenantId, long orgId, long? candidateFactoryId)
  52. {
  53. // 文案面向使用者:说清「是什么状态、该找谁、去哪里配」,不暴露表名/列名。
  54. if (orgId <= 0)
  55. throw new InvalidOperationException("当前租户尚未配置所属机构,请联系管理员在「租户管理」中完成配置");
  56. if (candidateFactoryId is > 0
  57. && candidateFactoryId.Value != tenantId
  58. && candidateFactoryId.Value == orgId)
  59. return candidateFactoryId.Value;
  60. return orgId;
  61. }
  62. public static bool IsAuthorizedFactory(long tenantId, long orgId, long factoryId) =>
  63. factoryId > 0 && factoryId != tenantId && factoryId == orgId;
  64. }
  65. public static class SmartOpsImprovementPlanNo
  66. {
  67. public static string BuildPrefix(DateTime now) => $"SOP-{now:yyyyMMdd}-";
  68. public static string Next(string prefix, IEnumerable<string> existing)
  69. {
  70. var max = 0;
  71. foreach (var planNo in existing)
  72. {
  73. if (string.IsNullOrWhiteSpace(planNo) || !planNo.StartsWith(prefix, StringComparison.Ordinal))
  74. continue;
  75. if (int.TryParse(planNo.AsSpan(prefix.Length), out var n))
  76. max = Math.Max(max, n);
  77. }
  78. return $"{prefix}{max + 1:000}";
  79. }
  80. }