mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
1cac12d940
- Add §7 initialization flow: /component/person/detail call chain from decompiled component-organization source - Document floorList assembly in ImgPersonServiceImpl (via elevatorFeignClient.listByImageId) - Analyze init vs submit consistency: gap when policy exists
18 KiB
18 KiB
租户访客默认楼层策略 — 业务逻辑设计
日期:2026-05-05 状态:设计稿(待审核) 基线版本:v2.0.20
1. 现有问题
PersonRuleServiceImpl.addVisitor 中策略查询被包裹在 if (!callerProvidedFloors) 条件内,导致调用方一旦传了 floorIds(UC-02),策略被完全跳过。
修正要求:任何时候都应当查询策略,有策略且生效则以策略 allow_zone_ids 替代 候选楼层。
2. 完整时序图
2.1 UC-01:调用方未传 floorIds(组织默认楼层)
调用方 电梯应用 组织服务 tenant_visitor_floor_policy
│ │ │ │
│ POST /add/visitor │ │ │
│ (无floorIds) │ │ │
│ ────────────────────→ │ │ │
│ │ │ │
│ │ ── Phase1: 查组织 ── │ │
│ │ POST /component/person/detail(personId) │
│ │ ─────────────────────→ │ │
│ │ ←───────────────────── │ │
│ │ PersonResult { │ │
│ │ floorList: [A,B,C], │ │
│ │ organizationIds:[...] │ │
│ │ } │ │
│ │ │ │
│ │ ── Phase2: 候选楼层 ── │ │
│ │ candidate = floorList │ │
│ │ = [A,B,C] │ │
│ │ │ │
│ │ ── Phase3: ALWAYS 查策略 ── │
│ │ SELECT * WHERE org_id=? AND enabled=1 │
│ │ ──────────────────────────────────────────→ │
│ │ ←────────────────────────────────────────── │
│ │ │ │
│ │ ┌─ policy存在? ─┐ │ │
│ │ │ │ │ │
│ │ YES NO │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ effective= effective │ │
│ │ allow_zone_ids =candidate │ │
│ │ =[6F] =[A,B,C] │ │
│ │ │ │
│ │ ── Phase4: 空集校验 ── │ │
│ │ effective=[6F] │ │
│ │ │ │
│ │ ── Phase5: 写规则绑图库 ── │
│ ←──────────────────── │ │ │
│ success(6F) │ │ │
2.2 UC-02:调用方传了 floorIds
调用方 电梯应用 组织服务 tenant_visitor_floor_policy
│ │ │ │
│ POST /add/visitor │ │ │
│ floorIds=[7F,8F] │ │ │
│ ────────────────────→ │ │ │
│ │ │ │
│ │ ── Phase1: 查组织 ── │ │
│ │ POST /component/person/detail(personId) │
│ │ ─────────────────────→ │ │
│ │ ←───────────────────── │ │
│ │ PersonResult { │ │
│ │ organizationIds │ │
│ │ } ← 仅取orgIds,不用floorList │
│ │ │ │
│ │ ── Phase2: 候选楼层 ── │ │
│ │ candidate = param.floorIds │
│ │ = [7F,8F] │ │
│ │ │ │
│ │ ── Phase3: ALWAYS 查策略 ── │
│ │ SELECT * WHERE org_id=? AND enabled=1 │
│ │ ──────────────────────────────────────────→ │
│ │ ←────────────────────────────────────────── │
│ │ │ │
│ │ ┌─ policy存在? ─┐ │ │
│ │ │ │ │ │
│ │ YES NO │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ effective= effective │ │
│ │ allow_zone_ids =candidate │ │
│ │ =[6F] =[7F,8F] │ │
│ │ │ │
│ │ ── Phase4: 空集校验 ── │ │
│ │ effective=[6F] │ │
│ │ │ │
│ │ ── Phase5: 写规则绑图库 ── │
│ ←──────────────────── │ │ │
│ success(6F) │ │ │
3. 完整控制流
addVisitor(param, context):
┌────────────────────────────────────────────────────────┐
│ Phase1: 查被访人组织(ALWAYS,用于策略获取) │
│ detail = personService.detail(personId, businessId) │
│ if detail fail → return detail.code/msg │
│ personResult = detail.getData() │
│ orgIds = personResult.getOrganizationIds() │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase2: 确定候选楼层 candidate │
│ if (param.floorIds 非空): ← UC-02 │
│ candidate = param.floorIds │
│ else: ← UC-01 │
│ candidate = personResult.getFloorList() │
│ if candidate 为空 → return 76260531 │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase3: ALWAYS 查策略,有策略则替代 │
│ policy = DAO.selectEnabledByOrgId(orgId) │
│ if (policy 存在 && enabled=1): │
│ effective = parseAllowZoneIds(policy.allow_zone_ids)│
│ ← 策略的 allow 直接替代,非求交 │
│ else: │
│ effective = candidate ← 无策略约束,原值 │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase4: 空集校验 │
│ if effective 为空 → return 76260531 │
│ param.setFloorIds(effective) │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase5: 写规则 + 绑图库(不变) │
│ zoneService.page → image_rule_ref → batchBind → ... │
└────────────────────────────────────────────────────────┘
4. 决策矩阵
| 场景 | caller传floorIds | 有生效策略 | 行为 | 最终楼层 |
|---|---|---|---|---|
| A | 空 | 无 | 取组织 floorList | floorList |
| B | 空 | 有,allow=[6F] | 策略替代 | [6F] |
| C | [7F] |
无 | 用调用方传入值 | [7F] |
| D | [7F] |
有,allow=[6F] | 策略替代(忽略调用方) | [6F] |
| E | [6F,7F] |
有,allow=[6F] | 策略替代 | [6F] |
| F | 空 | 有,allow=[](无效或空) | 等同无策略 | floorList |
关键差异
| 场景 | 旧行为(v2.0.18) | 新行为(v2.0.20) |
|---|---|---|
| D:传 7F,策略 allow=6F | 开通 7F(绕过策略 ❌) | 开通 6F(策略替代 ✅) |
| E:传 6F+7F,策略 allow=6F | 开通 6F+7F(绕过策略 ❌) | 仅开通 6F(策略替代 ✅) |
5. 错误码
| 错误码 | 场景 | 说明 |
|---|---|---|
| 76260531 | 无可用楼层 | Phase4 中 effective 为空(含被访人无楼层、策略 allow 为空等) |
| 76260533 | 策略配置错误 | allow_zone_ids JSON 解析失败(已有 parseAllowZoneIds 兜底返回空) |
6. 日志设计
| 路径 | 级别 | 内容 |
|---|---|---|
| 请求入口 | INFO | businessId, personId, visitorId, requestFloorSize |
| Phase1 detail 失败 | WARN | personId, code, msg |
| Phase2 UC-01 | INFO | candidate = floorList |
| Phase2 UC-02 | INFO | candidate = param.floorIds |
| Phase2 空 floorList | WARN | personId |
| Phase3 查策略 | INFO | orgIds |
| Phase3 策略存在 | INFO | policyId, orgId, allow_zone_ids |
| Phase3 策略不存在 | INFO | 使用候选楼层原值 |
| Phase4 空集 | WARN | businessId, personId, visitorId, candidate |
7. 初始化流程分析(登记页楼层展示)
7.1 调用路径
基于 cwos-component-organization-service 反编译分析,初始化时的 /component/person/detail 调用链:
登记页初始化
│
├─ GET/POST /component/person/detail ───→ 组织服务 (ninca-common-component-organization)
│ │
│ PersonController.java:132 │
│ → imgStorePersonService.detail() │
│ → ImgPersonServiceImpl.java │
│ line 639-650 │
│ → elevatorFeignClient │
│ .listByImageId(...) │ ← 调电梯查询人员授权楼层
│ → image_rule_ref 表查询 │
│ ← floorList (List<zoneId>) │
│ │
│ ← PersonResult { │
│ floorList: ["zoneId1","zoneId2",...], │ ← 被访人当前授权楼层
│ organizationIds: [...], │ ← 用于 addVisitor 策略查询
│ defaultFloor, chooseFloor, ... │
│ } │
│ │
└─ 展示 floorList → 用户选择 → POST /add/visitor
→ 策略替代(如有)
7.2 floorList 的数据来源
ImgPersonServiceImpl.java 中 floorList 的组装逻辑:
// line 639-650 (ImgPersonServiceImpl.java)
ArrayList<String> floorList = new ArrayList<>();
// 调电梯 Feign 接口获取该人员在 image_rule_ref 中的授权区域
CloudwalkResult<List<AcsPassRuleImageResultDto>> images =
this.elevatorFeignClient.listByImageId(acsPassRuleImageForm);
for (int i = 0; i < acsPassRuleImageResultDtoList.size(); i++) {
floorList.add(((AcsPassRuleImageResultDto)acsPassRuleImageResultDtoList.get(i)).getZoneId());
}
personResult.setFloorList(floorList);
结论:floorList 是被访人在电梯 image_rule_ref 中已有授权的楼层集合,不是全量楼层。所以初始化页展示的本身就是被访人有权限的楼层。
7.3 初始化和提交的联动关系
| 阶段 | 调用接口 | 返回数据 | 是否经过策略 |
|---|---|---|---|
| 初始化 | 组织服务 /component/person/detail |
floorList(授权楼层) |
❌ 策略在组织服务中不存在 |
| 提交 | 电梯应用 /add/visitor |
开通结果 | ✅ v2.0.20 已修复 |
7.4 潜在问题
由于初始化时不经过策略,存在展示与开通不一致的可能:
| 场景 | 初始化展示 | 用户选择 | addVisitor 实际开通 | 用户体验 |
|---|---|---|---|---|
| 被访人授权多楼层,策略 allow=[6F] | floorList=[6F,7F,8F] | 选 7F | 6F(策略替代) | 用户困惑 |
| 被访人授权多楼层,策略 allow=[6F] | floorList=[6F,7F,8F] | 不选(UC-01) | 6F(策略替代) | 与预期一致 |
| 无策略 | floorList | 任意 | 与选择一致 | 正常 |
解决方案建议(后续阶段):
在电梯应用新增 /elevator/person/effective-floors 预览接口,供初始化时展示策略约束后的有效楼层。当前阶段可先通过前端交互说明,或由第三方 BFF 自行做策略感知。
8. 与现有文档的差异
| 文档 | 旧逻辑 | 新逻辑 |
|---|---|---|
| 数据库阶段技术设计 §5.2 | UC-02 不读策略表 | UC-02 读策略表,替代 |
| 数据库阶段技术设计 §5.3 | UC-02 以请求为准 | UC-02 以策略为准 |
当前代码 if(!callerProvidedFloors) |
策略仅在 UC-01 查 | 策略 ALWAYS 查 |
resolveEffectiveFrozens 方法 |
求交 + 校验 hostFloors | 不再使用,直接 parseAllowZoneIds |