AdoS0OrgsController.cs 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. using Admin.NET.Core.Service;
  2. using Admin.NET.Plugin.AiDOP.Infrastructure;
  3. namespace Admin.NET.Plugin.AiDOP.Controllers.S0;
  4. /// <summary>
  5. /// S0 自有的**当前租户只读组织查询**(P1-2)。
  6. ///
  7. /// 目的:让 S0 页面不再依赖平台 <c>/api/sysOrg/list</c>。
  8. /// 平台该路由已登记为按钮权限 <c>sysOrg:list</c>(SysMenu Id=1300200030201),
  9. /// 普通 S0 业务角色为了填一个隐藏的公司/工厂归属字段而被迫持有平台机构管理权限,
  10. /// 并因菜单祖先补全在侧边栏暴露「系统管理 / 机构管理」入口。
  11. /// 本路由 <c>s0:common:orgs</c> 未登记为按钮权限,走 JwtHandler「未登记即放行」分支,
  12. /// 普通业务角色无需任何平台机构权限即可读取本租户组织。
  13. ///
  14. /// 边界(只读 + 只当前租户 + 最小字段):
  15. /// - 租户来自 <see cref="AdoS0TenantScope.TryResolveRequired"/>(JWT claim),不接受 payload;
  16. /// - 复用 <see cref="SysOrgService.GetList"/>,与平台口径**完全一致**(含 GetUserOrgIdList
  17. /// 数据权限收窄与 SysOrg 全局租户过滤),不新写一套查询逻辑、不放宽可见范围;
  18. /// - 只返回 id / pid / name / code / type 五个字段,不暴露机构管理的任何写能力;
  19. /// - 额外对结果再做一次当前租户断言,作为纵深防御。
  20. ///
  21. /// 返回一律为**平铺列表**:平台 GetList 在不带 Type 条件时返回树,本接口统一拍平,
  22. /// 使调用方(loadOrgList)无需再关心树/平铺两种形态。
  23. /// </summary>
  24. [ApiController]
  25. [Route("api/s0/common/orgs")]
  26. [NonUnify]
  27. public class AdoS0OrgsController : ControllerBase
  28. {
  29. private readonly SysOrgService _sysOrgService;
  30. public AdoS0OrgsController(SysOrgService sysOrgService)
  31. {
  32. _sysOrgService = sysOrgService;
  33. }
  34. /// <summary>
  35. /// 查询当前租户的组织列表。<paramref name="type"/> 为 SysOrg.Type(S0 约定 201=公司、501=工厂,
  36. /// 501 沿用 S0 legacy 兼容语义,见 AdoS0OrgScope 注释);不传则返回本租户全部组织。
  37. /// </summary>
  38. [HttpGet]
  39. public async Task<IActionResult> GetListAsync([FromQuery] string? type)
  40. {
  41. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  42. var raw = await _sysOrgService.GetList(new OrgInput { Id = 0, Type = type });
  43. var flat = new List<SysOrg>();
  44. Flatten(raw, flat);
  45. var result = flat
  46. .Where(x => x.TenantId == tenantId) // 纵深防御:平台侧已按租户过滤,这里再断言一次
  47. .GroupBy(x => x.Id)
  48. .Select(g => g.First())
  49. .OrderBy(x => x.OrderNo).ThenBy(x => x.Id)
  50. .Select(x => new
  51. {
  52. id = x.Id.ToString(), // 雪花 Id 超 JS number 精度,一律以 string 下发
  53. pid = x.Pid.ToString(),
  54. name = x.Name,
  55. code = x.Code,
  56. type = x.Type,
  57. })
  58. .ToList();
  59. return Ok(result);
  60. }
  61. /// <summary>把可能的树形结果拍平(平台 GetList 不带条件时返回树,子节点在 Children 内)。</summary>
  62. private static void Flatten(List<SysOrg>? nodes, List<SysOrg> sink)
  63. {
  64. if (nodes == null) return;
  65. foreach (var n in nodes)
  66. {
  67. sink.Add(n);
  68. Flatten(n.Children, sink);
  69. }
  70. }
  71. }