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("当前租户存在多个工厂,无法确定本次操作的工厂,请联系管理员为该账号绑定唯一所属工厂");
}
}