S8TrustedScopeResolver.cs 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. using Admin.NET.Core;
  2. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  3. /// <summary>
  4. /// S8 可信作用域:租户与工厂一律由服务端从认证身份解析,客户端传入的
  5. /// tenantId / factoryId / body.TenantId / body.FactoryId 不承担任何安全边界作用。
  6. /// </summary>
  7. public sealed record S8TrustedScope(long TenantId, long FactoryId);
  8. /// <summary>
  9. /// S8 租户 / 工厂可信作用域解析器(S8-TENANT-FACTORY-P0-CLOSURE-1)。
  10. ///
  11. /// 租户:只信认证后的 JWT,复用 <see cref="AidopTenantScope.ResolveOrThrow"/>。
  12. ///
  13. /// 工厂:沿用 S8 订单链路(S8OrderFlowService)已在生产路径上实际工作的口径——
  14. /// 在当前可信租户范围内解析业务工厂组织,而不是取租户根组织 <c>SysTenant.OrgId</c>:
  15. /// 1. 当前登录用户的组织,且该组织确实属于当前可信租户 → 直接采用;
  16. /// 2. 否则在当前租户内查 <see cref="FactoryOrgType"/> 工厂类型组织:
  17. /// 有且仅有 1 个 → 采用;0 个 → 明确业务错误;多于 1 个 → 明确要求消歧,不做任意 First()。
  18. ///
  19. /// 不做以下回落(会把归属判错或悄悄放大可见范围):
  20. /// 租户根组织 / <c>SysTenant.OrgId</c> / 常量 1 / 客户端传入值。
  21. /// </summary>
  22. public sealed class S8TrustedScopeResolver : ITransient
  23. {
  24. /// <summary>SysOrg.Type 中代表「工厂」的机构类型编码。</summary>
  25. public const string FactoryOrgType = "501";
  26. private readonly SqlSugarRepository<SysOrg> _orgRep;
  27. private readonly UserManager _userManager;
  28. public S8TrustedScopeResolver(SqlSugarRepository<SysOrg> orgRep, UserManager userManager)
  29. {
  30. _orgRep = orgRep;
  31. _userManager = userManager;
  32. }
  33. /// <summary>解析当前登录身份的 S8 可信作用域;无法确定时抛业务异常,绝不回落默认值。</summary>
  34. public async Task<S8TrustedScope> ResolveAsync()
  35. {
  36. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  37. var factoryId = await ResolveFactoryIdAsync(tenantId);
  38. return new S8TrustedScope(tenantId, factoryId);
  39. }
  40. private async Task<long> ResolveFactoryIdAsync(long tenantId)
  41. {
  42. // 1) 登录用户自身组织:必须确实隶属当前可信租户,避免跨租户组织被当作工厂。
  43. var orgId = _userManager.OrgId;
  44. if (orgId > 0)
  45. {
  46. var belongsToTenant = await _orgRep.AsQueryable()
  47. .Where(o => o.Id == orgId && o.TenantId == tenantId)
  48. .AnyAsync();
  49. if (belongsToTenant) return orgId;
  50. }
  51. // 2) 租户内工厂类型组织;必须唯一,否则要求显式消歧。
  52. var factories = await _orgRep.AsQueryable()
  53. .Where(o => o.TenantId == tenantId && o.Type == FactoryOrgType)
  54. .Select(o => o.Id)
  55. .ToListAsync();
  56. // 文案面向使用者:说清是什么状态、该找谁、去哪里配,不暴露表名 / 列名。
  57. if (factories.Count == 1) return factories[0];
  58. if (factories.Count == 0)
  59. throw Oops.Oh("当前账号尚未归属有效工厂,请联系管理员在「机构管理」中为该租户配置工厂并绑定用户所属机构");
  60. throw Oops.Oh("当前租户存在多个工厂,无法确定本次操作的工厂,请联系管理员为该账号绑定唯一所属工厂");
  61. }
  62. }