Files
starRiverProperty/docs/superpowers/specs/2026-05-05-tenant-visitor-floor-policy-correction.md
T
hpd840321 7b2bd307f1 Initial commit: reorganized source tree
- backend/: 13 Maven modules (cw-elevator-application, cloudwalk-cloud, intelligent-cwoscomponent, ninca-crk, etc.)
- frontend/: 4 Vue projects (elevator-front, cwos-portal, alarm-front, front_acs) + decompiled + scripts
- scripts/: build, test-env, tools (Docker Compose, service templates, API parity)
- docs/: AGENTS.md, superpowers specs, architecture docs
- .gitignore: standard Java/Maven exclusions

Moved from legacy maven-*/ root layout to backend/ organized structure.
2026-05-09 09:56:45 +08:00

18 KiB
Raw Blame History

租户访客默认楼层策略 — 业务逻辑设计

日期2026-05-05 状态:设计稿(待审核) 基线版本v2.0.20


1. 现有问题

PersonRuleServiceImpl.addVisitor 中策略查询被包裹在 if (!callerProvidedFloors) 条件内,导致调用方一旦传了 floorIdsUC-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.javafloorList 的组装逻辑:

// 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 废止;规范语义为组织侧 替代,电梯透传 floorList