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