# 租户访客默认楼层策略 — 业务逻辑重设计 **日期**:2026-05-05 **状态**:待审核 **设计依据**:产品方案 [租户访客默认楼层技术产品方案](../../business/租户访客默认楼层技术产品方案.md) **涉及代码**:`PersonRuleServiceImpl.addVisitor` --- ## 1. 业务规则(核心不变量) | 规则 | 说明 | |------|------| | **策略全时生效** | 无论调用方是否传入 `floorIds`,**始终查询**该机构是否有启用策略 | | **策略即安全边界** | 策略的 `allow_zone_ids` 定义访客可达楼层的**上限集合**,任何路径都不能超出此集合 | | **无策略不禁锢** | 机构未配置策略或策略未启用时,行为与无策略版本完全一致 | | **交集为空必须拒绝** | `candidate ∩ allow` 为空时禁止继续开通,返回明确错误码 | --- ## 2. 总流程 ### 2.1 UC-01:调用方未传 floorIds ```mermaid sequenceDiagram participant Caller as 调用方/BFF participant Elevator as 电梯应用
cw-elevator-application participant Org as 组织服务
ninca-common-component-organization participant PolicyDB as 策略表
tenant_visitor_floor_policy Caller->>Elevator: POST /elevator/person/add/visitor
{personId, visitorId}
(不传 floorIds) Note over Elevator: 阶段1:查被访人信息 Elevator->>Org: POST /component/person/detail
{personId, businessId} Org-->>Elevator: PersonResult
{floorList, organizationIds} Note over Elevator: 阶段2:候选楼层 = floorList Note over Elevator: floorList 来自组织服务 Note over Elevator: 阶段3:查策略 Elevator->>PolicyDB: SELECT * FROM tenant_visitor_floor_policy
WHERE org_id IN (organizationIds) AND enabled=1 PolicyDB-->>Elevator: policy 行 / 空 alt 策略存在且生效 Note over Elevator: 最终楼层 = floorList ∩ allow_zone_ids alt 交集为空 Elevator-->>Caller: 失败 76260532
(租户策略与被访人授权无交集) else 交集非空 Note over Elevator: 继续开通流程 Elevator-->>Caller: 成功,仅开通交集内楼层 end else 无策略或未启用 Note over Elevator: 最终楼层 = floorList(原值) Elevator-->>Caller: 成功 end ``` ### 2.2 UC-02:调用方传入 floorIds ```mermaid sequenceDiagram participant Caller as 调用方/BFF participant Elevator as 电梯应用
cw-elevator-application participant Org as 组织服务
ninca-common-component-organization participant PolicyDB as 策略表
tenant_visitor_floor_policy Caller->>Elevator: POST /elevator/person/add/visitor
{personId, visitorId, floorIds:[...]} Note over Elevator: 阶段1:查被访人信息(仅取 organizationIds) Elevator->>Org: POST /component/person/detail
{personId, businessId} Org-->>Elevator: PersonResult
{organizationIds} Note over Elevator: 阶段2:候选楼层 = 调用方传入的 floorIds Note over Elevator: 阶段3:查策略(ALWAYS) Elevator->>PolicyDB: SELECT * FROM tenant_visitor_floor_policy
WHERE org_id IN (organizationIds) AND enabled=1 PolicyDB-->>Elevator: policy 行 / 空 alt 策略存在且生效 Note over Elevator: 最终楼层 = callerFloorIds ∩ allow_zone_ids alt 交集为空 Elevator-->>Caller: 失败 76260532
(请求楼层不在策略允许范围内) else 交集非空 Elevator-->>Caller: 成功,仅开通交集内楼层 end else 无策略或未启用 Note over Elevator: 最终楼层 = callerFloorIds(原值) Elevator-->>Caller: 成功 end ``` --- ## 3. 控制流伪代码 ``` addVisitor(param, context): // === 阶段1:查被访人组织信息(ALWAYS)=== detailResult = personService.detail(personId, businessId) if failed: return detailResult.error person = detailResult.data // === 阶段2:确定候选楼层 === if param.floorIds 非空: ← UC-02 candidate = param.floorIds else: ← UC-01 candidate = person.floorList if candidate 为空: return 76260531 // === 阶段3:ALWAYS 查策略 === policy = findEnabledPolicy(person.organizationIds) if policy != null: effective = intersect(candidate, policy.allow_zone_ids) if effective 为空: return 76260532 else: effective = candidate // === 阶段4:空集校验 === if effective 为空: return 76260531 param.floorIds = effective // === 阶段5:开通流程(不变)=== zoneService.page → image_rule_ref → batchBind → ... ``` --- ## 4. 场景对照矩阵 | 场景 | 调用方 floorIds | 策略状态 | 候选楼层 | 最终结果 | |------|----------------|---------|---------|---------| | UC-01 无策略 | 空 | 无策略行 | `floorList` | `floorList` ✅ | | UC-01 + 策略通过 | 空 | 有且生效 | `floorList` | `floorList ∩ allow` ✅ | | UC-01 + 策略无交集 | 空 | allow 与 floorList 无交集 | `floorList` | 失败 76260532 ✅ | | UC-01 被访人无楼层 | 空 | 任意 | `floorList`=空 | 失败 76260531 ✅ | | UC-02 无策略 | [A,B] | 无策略行 | [A,B] | [A,B] ✅ | | UC-02 + 策略包含 | [A,B] | allow=[A,C] | [A,B] | [A] ✅ | | UC-02 + 策略不包含 | [A,B] | allow=[C,D] | [A,B] | 失败 76260532 ✅ | ### 与当前实现的差异 | 场景 | 当前实现 | 重设计后 | |------|---------|---------| | UC-02 + 策略存在 | 绕过策略,按请求楼层开通 ❌ | 策略求交 ✅ | | UC-02 + 策略不包含请求楼层 | 成功开通(本应拒绝)❌ | 失败 76260532 ✅ | --- ## 5. 错误码 | 错误码 | 触发条件 | 说明 | |--------|---------|------| | 76260531 | 候选楼层为空 | 被访人 `floorList` 为空,或有效楼层为空 | | 76260532 | `candidate ∩ allow` 无交集 | 策略约束了可访楼层,但候选楼层全部不在允许范围内 | | 76260533 | 策略配置错误 | `allow_zone_ids` 包含被访人无权限的 zoneId | --- ## 6. 日志规范 | 关键路径 | 日志内容 | |---------|---------| | 入口 | `businessId, personId, visitorId, requestFloorSize` | | UC-01 | `调用方未传楼层,取被访人默认楼层 floorList=xxx` | | UC-02 | `调用方已指定楼层,候选楼层=candidate` | | 策略查询 | `查询组织 orgIds=xxx` | | 策略命中 | `找到启用策略 policyId=xxx allow=xxx` | | 策略未命中 | `未找到启用策略,使用候选楼层原值` | | 求交成功 | `策略生效,最终楼层=effective` | | 求交为空 | `候选楼层与策略无交集,返回 76260532` | | 空楼层 | `无可用楼层,返回 76260531` | --- ## 7. 实施范围 | 变更点 | 影响 | 风险 | |--------|------|------| | UC-02 增加 `personService.detail()` 调用 | 多一次 RPC(数百微秒) | 低 | | UC-02 增加策略求交逻辑 | 对传入楼层做过滤 | 中——调用方可能不符合预期 | | 删除 `callerProvidedFloors` 分支 | 简化代码结构 | 低 | > **兼容性提醒**:UC-02 行为改变意味着:之前能开通的请求(传入策略允许范围外的楼层)现在会失败。需通知集成方。