# 租户访客楼层策略 — 代码重构实施指南 > **目标**:在**不破坏对外 HTTP / Feign 契约**的前提下,实现规范中的 **「组织 `detail` 内以 `allow_zone_ids` 替代 `PersonResult.floorList`」**,使**邀约页、UC-01 派梯**与**租户策略**一致。 > **依据**:[访客邀约与派梯楼层一致性梳理](访客邀约与派梯楼层一致性梳理.md)、[迁入组织规格](../superpowers/specs/2026-05-06-tenant-visitor-policy-organization-implementation.md)(以下简称 **《组织规格》**)。 > **当前状态**:电梯 **`PersonRuleServiceImpl#addVisitor`** 已**不**读策略表、**不**做 ∩。组织侧已落地 **`TenantVisitorFloorPolicyService`** + **`ImgPersonServiceImpl#detail` / `page(isVisitor)`** 的 **`allow_zone_ids` 替代**;部署组织库需执行 **`docs/sql/organization_tenant_visitor_floor_policy.sql`**。电梯工程内已无 **`TenantVisitorFloorPolicyDao`**(死代码已删)。 --- ## 1. 重构原则(必须遵守) | 原则 | 说明 | |------|------| | **策略语义** | 仅 **「替代」**:命中策略时 **`floorList` = 配置中的 `allow_zone_ids` 解析结果**;**不要**与 `listByImageId` 结果做 **∩** 作为规范主路径。 | | **唯一对外楼层权威** | 消费「被访人可派梯/可邀约楼层」**必须**走 **`PersonService.detail` → `floorList`**(经 Intelligent → 组织)。 | | **电梯侧** | **不**新增「再算一遍策略」;**不**恢复 `TenantVisitorFloorPolicyDao` 参与 `addVisitor` 有效楼层计算。 | | **接口兼容** | 《组织规格》约定:`cwos-component-organization-interface` **不**为策略新增公开 DTO/方法时,策略仅作为**服务层内部实现**;对外仍只通过现有 `detail` 返回里的 `floorList` / `floorNames` 体现。 | | **UC-02** | 显式 `floorIds` 仍由调用方负责与业务单一致;若需 **⊆ 策略** 的硬约束,在 **BFF** 实现,或单独立项改电梯**且**经评审。 | --- ## 2. 分阶段实施任务 ### 阶段 A — 数据与组织工程骨架 1. **策略表落库位置**(二选一,推荐 **组织库** 为唯一主库) - **推荐**:在 **组织服务使用的 MySQL 库** 中创建与《组织规格》一致的表结构(字段含 `business_id`、`policy_type`、`allow_zone_ids` JSON、`enabled`、`building_id` 可空等)。 - **迁移期**:若生产数据仍在**电梯库** `tenant_visitor_floor_policy`,做 **一次性迁移脚本** + 运维切换窗口;迁移完成后电梯侧 Dao **仅删除调用**,不必双写。 2. **组织模块新增(均在 `maven-ninca-common-component-organization`)** - Entity / Mapper / XML:`tenant_visitor_floor_policy` CRUD 或至少 **按 `business_id` + enabled** 查询租户默认行(`building_id IS NULL`)。 - **`TenantVisitorFloorPolicyService`**(命名可依项目惯例): - `boolean isPolicyActive(String businessId)` 或 `Optional findTenantDefault(String businessId)` - `List parseAllowZoneIds(String json)`(健壮解析,非法 JSON → 视为未启用或记录告警)。 - **不做**:在 interface 模块暴露新 REST(除非产品明确要求管理 API)。 ### 阶段 B — 在 `ImgPersonServiceImpl#detail` 接入替代逻辑(核心) **文件**:`cwos-component-organization-service/.../ImgPersonServiceImpl.java`,方法 **`detail`**。 **插入点**:在 **`elevatorFeignClient.listByImageId` 成功**、已根据 `images` 遍历得到 **`floorList` / `floorNames`(原始)** 之后;在 **`result.setFloorList` / `setFloorNames`** 之前增加分支: ``` 伪代码: 原始列表 = 当前遍历 listByImageId 得到的 floorList, floorNames 若 TenantVisitorFloorPolicyService 对 businessId(及必要时 organizationIds)判定「启用且 allow 非空」: floorList := allow_zone_ids 解析后的 zoneId 列表(顺序:与 JSON 数组顺序一致) floorNames := 需与 floorList 对齐展示 选项 1:配置侧同时存 id→name 映射(扩展列或独立字典表) 选项 2:对 allow 中每个 zoneId 调现有 Zone Feign 批量查名称(注意批量与超时) 选项 3:若产品允许仅展示 zoneId,则 floorNames 可与 zoneId 同步占位(不推荐体验) 否则: 保持现有原始 listByImageId 语义 result.setFloorList(...) result.setFloorNames(...) ``` **注意**: - **`defaultFloor` / `floorName`(单人默认层展示)** 与 **`floorList`(通行可达层)** 语义不同,勿混写;参见 [08 文档](../../maven-cw-elevator-application/cw-elevator-application-service/docs/08-visitor-registration-and-elevator-auth.md)。 - **启用判定键**:与《组织规格》一致,以 **`business_id`**(Header `businessid` / `companyId`)为主;若后续要 **按机构细分**,再扩展查询条件,不在本阶段默认实现。 ### 阶段 C — 访客列表 `page(isVisitor)` 对齐(强烈建议) **文件**:同一 `ImgPersonServiceImpl`,方法 **`page`**,分支 **`param.getIsVisitor()` 非空**。 - 现状:该分支内有 **星河湾 40F/6F** 等与 **`detail`** 不一致的展示逻辑。 - **建议**:在策略**命中**时 **优先应用与 `detail` 相同的替代结果**(或抽 **私有方法** `applyTenantVisitorFloorPolicy(resultRow, businessId, …)` 供 `detail` 与 `page` 共用),并 **跳过**与租户策略冲突的硬编码默认层块(参见《组织规格》§4.3)。 - 避免:邀约走 `detail`、列表走 `page` 时出现两套楼层。 ### 阶段 D — 电梯侧清理(收尾) 1. **确认** `PersonRuleServiceImpl` **无** `TenantVisitorFloorPolicyDao` 注入与 ∩ 逻辑(当前梳理版本已符合则仅做静态检查)。 2. **删除或标注废弃**:`cw-elevator-application-data` 下 **`TenantVisitorFloorPolicyDao` / Mapper / XML** 若已不再被任何 Bean 引用 — **删除**可减少歧义;若迁移脚本仍需参考表结构,可保留 DDL 文档到 `docs/sql`,代码删除前 grep 全仓引用。 3. **电梯库表**:迁移完成后由 DBA **删表或归档**(按运维规范)。 ### 阶段 E — UC-02 与集成(可选增强) - **BFF**:派梯前读取邀约单 `floorIds`,调用 **`detail`** 得到 **`floorList`**(已含替代),校验 **`floorIds ⊆ floorList`**(集合意义),失败则拒绝派梯并返回明确错误码。 - **幂等**:邀约单号 + 派梯状态机由业务系统保证,电梯接口本身不变。 ### 阶段 F — 测试与验收 | 场景 | 期望 | |------|------| | 未配置策略 | `detail.floorList` 与改造前 **listByImageId** 一致;UC-01 行为不变。 | | 配置启用且 allow 非空 | `detail.floorList` **等于** allow 列表(替代);UC-01 开通层与邀约页一致。 | | 关闭策略 / JSON 无效 | 回退原始语义;无启动报错。 | | `page(isVisitor)` | 与 `detail` 策略表现一致(验收抽样)。 | --- ## 3. 风险与缓解 | 风险 | 缓解 | |------|------| | **`floorNames` 与 id 不对齐** | 替代后必须统一用 Zone 服务补全名称或扩展配置。 | | **性能**:detail 每次多一次 DB + 可选批量 Zone | 策略行缓存(短 TTL)或本地缓存按 `business_id`。 | | **跨楼栋首层**(§4.8) | 产品确认单次 `effective` 是否允许多楼栋;否则约束邀约/UC-02 单层栋或拆分调用。 | --- ## 4. 推荐提交顺序(便于 Code Review) 1. 组织库 DDL + Mapper + **PolicyService**(单测解析 JSON)。 2. **`detail` 接入替代** + 单元/集成测试(可 Mock Feign)。 3. **`page` 分支对齐**(若有)。 4. 电梯侧删除死代码 + 文档更新(本文 + 《一致性梳理》§6)。 5. 迁移脚本在生产前演练。 --- **文档版本**:与仓库实施同步更新;重大契约变更需同步修改《组织规格》并评审。