- 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.
37 KiB
租户访客默认楼层策略 — 迁入组织组件(组织侧唯一实现,电梯侧移除)
日期:2026-05-06
状态:实施前梳理 / 待评审
核心原则:策略逻辑只在 maven-ninca-common-component-organization 实现;maven-cw-elevator-application 完全移除策略相关代码。
业务约定(楼层清单):凡 调用方 / 前端 / BFF 需消费 「被访人可派梯 / 可邀约访问的楼层集合」(PersonResult.floorList 语义),必须走 intelligent PersonService.detail → PersonResult.getFloorList()(服务端路由至组织 ImgPersonServiceImpl#detail)。禁止用 listByPage 或其它接口顶替该契约来充当邀约页或 UC-01 的楼层主数据源。说明:组织服务 内部仍会 ElevatorFeignClient.listByImageId 组装原始楼层,再写入 floorList——这是 实现细节,不等于调用方可绕过 PersonService.detail 直连上述内部调用。另见 §2.3 与 UC-02 边界。
术语(强制)——「替代」与禁止「求交」
| 用语 | 含义 |
|---|---|
| 替代(Replacement) | 租户启用访客楼层策略时,allow_zone_ids 整表替换写入组织 PersonService.detail 返回的 PersonResult.floorList(及展示用 floorNames 等一致处理)。不得再将被访人 listByImageId 原始结果与 allow_zone_ids 做集合 交集(∩) 作为规范语义。 |
| 禁止表述 | 在需求/产品/排障文档中,禁止将本策略称为「与 floorList 求交」「 candidate ∩ allow」;若需描述历史实现,须标明 「历史·电梯侧过滤(已废弃)」。 |
电梯 addVisitor |
只透传 UC-01 的 personResult.floorList 与 UC-02 的 param.floorIds;不再读 tenant_visitor_floor_policy、不做 ∩。 |
硬约束:对外接口不变
| 约束 | 说明 |
|---|---|
| HTTP 路径与动词 | 不新增、不废弃、不更名组织/电梯现网已发布的 REST 路径;不调整鉴权与 Header 约定。 |
| 请求/响应契约 | cwos-component-organization-interface、intelligent PersonService / PersonResult 等已对外暴露的 DTO 与 Feign 方法签名保持兼容:不增删改字段、不增删方法。 |
| 允许变更范围 | 组织侧:新建 TenantVisitorFloorPolicyService、修改 ImgPersonServiceImpl#detail 内部 floorList 组装逻辑。电梯侧:删除 PersonRuleServiceImpl 中的策略相关代码(阶段3),删除策略 DAO/Mapper/DTO。 |
| 禁止 | 不在 cwos-component-organization-interface 中新增任何类/方法/字段。不在电梯侧新增对组织的 Feign 调用。 |
| floorList 唯一主路径 | 获取 PersonResult.floorList:必须调用 PersonService.detail(与 addVisitor UC-01、访客邀约初始化同源)。不适用于 addVisitor UC-02 的楼层候选来源(见 §2.1、§2.3)。禁止用分页接口替代 detail 来充当邀约/UC-01 的楼层主数据来源。 |
场景说明:访客邀请初始化、detail 与 listByPage
规范表述:访客邀约 / 派梯(UC-01)所依据的 floorList,业务上只认 PersonService.detail 的返回;组织侧在 ImgPersonServiceImpl#detail 内完成 listByImageId 与租户策略替代后写入同一字段,对外仍通过 PersonResult.floorList 消费。
本节约定产品 / 前端 / BFF与后端实现对齐用语;不引入新接口,楼层清单仍来自既有契约中的字段(如 floorList、floorNames、floorInfoList 等)。
1)访客邀请初始化页面需要「可访问楼层清单」
| 项 | 说明 |
|---|---|
| 业务诉求 | 访客邀约/登记页在提交前,需要展示 被访人侧允许访客选择的可达楼层(或默认勾选逻辑所依赖的楼层集合),以便用户勾选或与派梯入口对齐。 |
| 数据来源(规定主路径) | 必须通过 PersonService.detail 获取 PersonResult.floorList / 相关展示字段:被访人 personId + businessId → intelligent PersonService.detail(Feign 不变)→ 组织 ImgPersonServiceImpl#detail 内聚 listByImageId + 租户策略替代 后写入 floorList(见 §1「改造后」与 §4)。禁止将 listByPage 作为邀约页 floorList 的规范来源。 |
| 与派梯一致 | 电梯 addVisitor UC-01(未传 floorIds)以同一 PersonResult.floorList 为候选楼层;邀约页若使用该清单,可与 UC-01 同源,减少「页面选的层 ≠ 后台派的层」。 |
| 非本路径 | 邀约页若仅调人员分页、不调详情,则可能拿到 另一套 楼层展示(含星河湾 40F/6F 等),与 detail 不一定一致——见下文 3) 与 §4.0。 |
2)detail 流程的作用
| 项 | 说明 |
|---|---|
| 定位 | 单人维度的被访人详情:组织侧 ImgPersonServiceImpl#detail(对外经 /component/person/detail 等既有入口,契约不变)。 |
| 与楼层相关输出 | 在 listByImageId 成功时组装 floorList(zoneId 列表)与 floorNames;改造后在此链路插入 租户访客策略替代(命中则 floorList 以策略 allow_zone_ids 为准,见 §4.2)。 |
| 明确不包含 | 不包含星河湾分页里的 40F / 6F 默认覆盖(现网即如此);邀约页若只依赖 detail,则 不会从该接口拿到 XHW 那套默认楼层。 |
| 典型调用方 | 访客邀请初始化、被访人卡片、电梯 addVisitor 阶段 1 拉 PersonResult 等——凡需要 「这一位被访人当前可用的楼层清单」 的场景,应以 detail 为主数据源(在无不新增接口前提下)。 |
3)listByPage 流程的作用
| 项 | 说明 |
|---|---|
| 定位 | 人员列表分页:组织侧 ImgPersonServiceImpl#listByPage,面向 批量行 展示;可选参数 isVisitor 非空 时进入访客列表增强分支(见 §3.2)。 |
| 与楼层相关输出 | 行为 listByImageId 填 floorInfoList,再结合 OrgFloorMapper、xhwId / xhwDefaultFloorId / xhwSixFloorId 等写入 默认选中楼层、跨日标记 等(星河湾 40F / 6F 出现在此分支,见 §4.0)。 |
| 与邀约初始化的关系 | 适用于 访客名单列表、运营筛选 等「一行一人摘要」场景;不是邀约页「为主访人拉可选楼层清单」的首选数据源——除非产品设计明确要求列表与邀约共用同一套展示逻辑,并接受与 detail 的差异或另行对齐(§7 测试项)。 |
| 改造后 | 策略命中时应在 L331–332 插入点优先替代并 跳过原星河湾块(§4.3),避免与租户策略双重主编。 |
小结:访客邀请初始化与 UC-01 派梯,均以 PersonService.detail 返回的 PersonResult.floorList 为权威楼层清单(组织侧在 ImgPersonServiceImpl#detail 内完成策略替代);listByPage(isVisitor) 仅服务访客名单列表与项目定制默认层,不作为邀约页 floorList 的主数据路径。详情与分页并存时的差异见 §4.0。
访客邀约:完整业务流程图与代码流程图(改造后目标)
以下图表与仓库约定一致:主数据访客登记可能在第三方/BFF;电梯 addVisitor 仅负责「已有访客人员 ID 后的派梯授权」;策略在组织库、替代语义写入 detail 的 floorList。
A. 业务视角 — 端到端(访客邀约 + 可选派梯)
flowchart TB
subgraph invite["访客邀约 / 登记(主流程)"]
A1[打开访客邀约页] --> A2[选定被访人 hostPersonId]
A2 --> A3[PersonService.detail<br/>取 PersonResult.floorList]
A3 --> A4[渲染可选楼层 / 默认勾选逻辑]
A4 --> A5[填写访客信息、访期等]
A5 --> A6[提交邀约 — 访客档案落库]
end
subgraph elevator_domain["电梯域 — 派梯授权(可与邀约异步或分步)"]
B1[触发派梯授权] --> B2[POST /elevator/person/add/visitor]
B2 --> B3[依赖 PersonResult.floorList 或显式 floorIds]
B3 --> B4[写通行规则引用 / 图库绑定]
end
A3 -.->|唯一规范源| A3note["PersonService.detail<br/>PersonResult.floorList<br/>与 addVisitor UC-01 同源"]
A6 -.->|可选后续| B1
说明:邀约页 A3 必须通过 PersonService.detail → PersonResult.floorList 拉楼层清单(组织侧 ImgPersonServiceImpl#detail 实现);禁止用 listByPage(isVisitor) 替代 A3 作为 floorList 规范来源(见场景说明 3))。
B. 代码视角 — 邀约初始化:拉「可访问楼层清单」(唯一规范:PersonService.detail → floorList)
sequenceDiagram
autonumber
participant FE as 前端 / BFF
participant PS as PersonService<br/>Intelligent Feign
participant OC as PersonController<br/>/component/person/detail
participant IM as ImgPersonServiceImpl#detail
participant EF as ElevatorFeignClient<br/>listByImageId
participant TP as TenantVisitorFloorPolicyService<br/>组织库
FE->>PS: detail(personId, businessId)
PS->>OC: HTTP POST(契约不变)
OC->>IM: detail(param, context)
IM->>IM: selectByPrimaryKey、getImgStorePersonResults
IM->>EF: listByImageId(AcsPassRuleImageForm)
EF-->>IM: List AcsPassRuleImageResultDto(原始通行楼层)
IM->>TP: isEnabled(organizationIds)
TP-->>IM: true / false
alt 策略启用
IM->>TP: getAllowZoneIds(organizationIds)
TP-->>IM: allow_zone_ids 列表
IM->>IM: floorList = 策略替代结果
else 策略未启用
IM->>IM: floorList = 遍历 images 的 zoneId(现网 L613-626 语义)
end
IM->>IM: setFloorList / setFloorNames
IM-->>OC: ImgStorePersonGetResult
OC-->>PS: CloudwalkResult
PS-->>FE: PersonResult / 映射后 floorList
落点:组织 cwos-component-organization-service · ImgPersonServiceImpl;行号以 §3.1 为准(插入点在 listByImageId 成功块内)。
C. 代码视角 — 派梯授权:addVisitor(电梯,改造后)
flowchart TD
START([AcsPersonController<br/>POST /elevator/person/add/visitor]) --> P1[PersonRuleServiceImpl.addVisitor]
P1 --> D1[PersonService.detail<br/>PersonResult.floorList]
D1 --> D2{param.floorIds<br/>非空?}
D2 -->|UC-02 是| E1[effective = param.floorIds]
D2 -->|UC-01 否| E2[effective = personResult.floorList<br/>组织 detail 已含策略替代]
E1 --> V{effective<br/>为空?}
E2 --> V
V -->|是| FAIL[失败 76260531 等]
V -->|否| P2[param.setFloorIds effective]
P2 --> Z1[zoneService.page 首楼层]
Z1 --> Z2[deviceImageStoreDao<br/>imageStoreId]
Z2 --> R1[按楼层写 ImageRuleRef]
R1 --> B1[imageStorePersonService.batchBind<br/>访期]
B1 --> G1[updateGroupPersonRef]
G1 --> OK([返回成功])
要点:改造后 无 TenantVisitorFloorPolicyDao、无 candidate ∩ allow;UC-01 完全信任 组织侧写入的 floorList。
D. 代码视角 — 访客名单分页:listByPage(isVisitor 非空)
flowchart TD
LP([listByPage]) --> Q[PageHelper + imgStorePersonMapper.gets]
Q --> ENRICH[getImgStorePersonResults<br/>组织/用户姓名等]
ENRICH --> IV{param.isVisitor<br/>非空?}
IV -->|否| OUT1([直接分页返回])
IV -->|是| LOOP[逐行:listByImageId]
LOOP --> POL{TenantVisitorFloorPolicyService<br/>isEnabled?}
POL -->|是| POLSET[替代 floorInfoList / 默认楼层<br/>跳过星河湾块]
POL -->|否| DF[setDefaultChooseFloor defaultFloor]
DF --> OF[OrgFloorMapper.listByOrgIds]
OF --> EMPTY{orgFloorList<br/>为空?}
EMPTY -->|是| AD[setIsAcrossDay=1<br/>无 40/6 覆盖]
EMPTY -->|否| XHW{xhwId in<br/>organizationIds?}
XHW -->|是| F40[40F 默认 + floorInfoList]
XHW -->|否| F6[6F 默认 + floorInfoList]
POLSET --> FILTER[过滤标签含访客等<br/>现网逻辑]
F40 --> FILTER
F6 --> FILTER
AD --> FILTER
FILTER --> OUT2([CloudwalkPageAble 返回])
要点:邀约初始化不要依赖本分支作为主楼层清单;本图仅供列表页与 §4 优先级对照。
1. 数据流 — 改造前 vs 改造后
改造前(历史·已废弃)
addVisitor(floorIds)
→ personService.detail → PersonResult.floorList (listByImageId)
→ if UC-02: candidate = floorIds
→ if UC-01: candidate = floorList
→ 【电梯侧查策略表】findPolicyByOrgIds
→ 【错误语义·已废弃】candidate 与 allow_zone_ids 做 ∩ 过滤
→ 派梯
改造后(当前规范)
【访客邀约页 / UC-01 共用】PersonService.detail → PersonResult.floorList
(组织 ImgPersonServiceImpl#detail:listByImageId 后,策略命中则以 allow_zone_ids **替代** floorList)
addVisitor(floorIds)
→ personService.detail → PersonResult.floorList ← 与邀约页同一契约来源
→ if UC-02: effective = floorIds
→ if UC-01: effective = floorList
→ effective = 上式(电梯侧 **无** 策略表、**无** ∩)
→ 派梯
关键变化
| 步骤 | 改造前(历史) | 改造后(规范) |
|---|---|---|
| 策略权威 | 电梯库读表 + ∩ 收窄(错误表述:「求交」) | 组织库 / 组织 detail:策略命中则 floorList = allow_zone_ids(仅替代) |
PersonService.detail → floorList |
主要为 listByImageId 原始楼层 |
策略命中时 = allow_zone_ids(替代);未命中 = 原始遍历结果 |
addVisitor(电梯) |
查电梯库策略 + ∩ | 删除策略运算:仅透传 detail / 请求 |
| effective:UC-01 | 曾再度与 allow ∩ | effective = personResult.floorList(组织已替代,电梯不二次运算) |
| effective:UC-02 | 曾与 allow ∩(若走策略分支) | effective = param.getFloorIds(显式楼层;规范上不由电梯做 ∩) |
2. 业务语义
2.1 UC-01 与 UC-02:业务场景说明(电梯「访客派梯授权」接口)
以下针对 POST /elevator/person/add/visitor(PersonRuleServiceImpl#addVisitor)中 floorIds 是否由调用方传入 的两种业务模式;与 访客邀约页拉楼层清单(依赖 PersonService.detail)的关系见 §2.3。
| 代号 | 接口条件(代码侧) | 业务含义 | 典型场景举例 |
|---|---|---|---|
| UC-01 | 请求体 未传 floorIds,或传 空列表(以最终实现判定为准) |
由系统依据被访人维度推导要开通的楼层:调用方不显式指定「开哪几层」,派梯授权使用的楼层集合应与 被访人详情中的可达楼层清单一致(经租户策略在组织侧写入 PersonResult.floorList)。 |
① 登记完成后 一键开通派梯,前台未做逐层勾选;② BFF 只带 personId/visitorId/访期,楼层完全跟被访人档案 + 租户策略走;③ 与 访客邀约初始化页使用同一 PersonService.detail → floorList 对齐 UC-01,避免「页面以为的层」与「后台开通的层」不一致。 |
| UC-02 | 请求体 floorIds 非空 |
由调用方明确指定要开通的楼层(zoneId 列表):业务上多为用户或上游系统 已选定具体楼层,派梯接口按 显式列表 写入通行规则,而 不以 PersonResult.floorList 作为开通列表来源。 |
① 接待岗在终端 勾选 7F、8F 后提交派梯;② 第三方访客系统 合同只允许指定楼层;③ 邀约页提交时 把用户勾选的楼层原样 传给派梯(此时开通列表以请求为准,不等于邀约页展示用的 detail.floorList 必须相同,取决于产品设计)。 |
一句话对照:UC-01 =「开通哪些层」交给系统(跟被访人 detail + 策略后的 floorList);UC-02 =「开通哪些层」由调用方在请求里写死(floorIds)。
与邀约页的关系:邀约页展示 可选楼层 仍 规定走 PersonService.detail(§「业务约定」);用户若在邀约或后续页面 勾选了具体楼层 再派梯,对接侧通常走 UC-02;若 未勾选、由后台直接派梯,则多为 UC-01。
2.2 唯一规范语义:替代(Replacement);禁止「求交」作为策略定义
| 禁止(历史·电梯侧 ∩) | 唯一规范(组织 detail·替代) | |
|---|---|---|
| 逻辑 | candidate ∩ allow_zone_ids |
策略命中时 floorList := allow_zone_ids(整表替换,非与原楼层求交) |
| 实现位置 | PersonRuleServiceImpl 阶段 3 |
ImgPersonServiceImpl#detail(待接入 TenantVisitorFloorPolicyService 时在此 替代) |
| 策略命中时含义 | PersonResult.floorList 完全由策略列表定义;未命中策略时才保留 listByImageId 遍历结果 |
核心语义:租户访客楼层策略 只能是替代,不是与被访人电梯原始授权楼层 求交。
2.3 addVisitor 实现要点:UC-02 与「必须 detail」的边界(代码与契约)
| 路径 | 是否必须 PersonService.detail |
说明 |
|---|---|---|
| 访客邀约页初始化 / UC-01 派梯 | 是 | 楼层权威清单 = PersonService.detail → floorList(§「业务约定」)。 |
addVisitor UC-02 |
仍调用 personService.detail(阶段 1 校验被访人存在等),但 effective 楼层仅取自 param.getFloorIds(),不采用 personResult.getFloorList() |
用于 BFF/调用方已替用户选定楼层的派梯;不是邀约页拉清单的主路径。 |
电梯侧 addVisitor 生效楼层计算(无 TenantVisitorFloorPolicyDao、无 ∩)如下:
// 改造后 addVisitor:阶段 2
List<String> effective;
if (!CollectionUtils.isEmpty(param.getFloorIds())) {
effective = param.getFloorIds(); // UC-02 — 显式楼层
} else {
effective = personResult.getFloorList(); // UC-01 — 与邀约页同源,来自 detail
}
// 无 TenantVisitorFloorPolicyDao;effective 直接用于后续派梯
避免误读:上文「UC-02 不经 detail 的 floorList」仅指 候选生效楼层字段不取自 PersonResult.floorList;阶段 1 的 detail 调用仍可能存在(取被访人元数据)。若产品要求 显式 floorIds 也必须被租户策略约束,属新需求,与本篇「对外接口不变」前提冲突时需另案评审。
3. 改造后业务时序图(含代码行号对照)
3.1 组织侧 detail 内策略插入 + 电梯 addVisitor 消费(对照)
下图左侧为
addVisitor内触发detail的上下文;楼层写入发生在组织ImgPersonServiceImpl#detail(策略插入点见 §3.3)。
Elevator Intelligent Component-Org Component-Org
PersonRuleImpl PersonService ImgPersonServiceImpl TenantVisitorPolicyService
(after removal) (Feign, unchanged) (L569 detail方法) (新建)
│ addVisitor() │ │ │
│──────────────────────────>│ │ │
│ │ detail(param) │ │
│ │───────────────────>│ │
│ │ │ │
│ │ │ ① selectByPrimaryKey L577 │
│ │ │ ② getVehicleIds L598 │
│ │ │ │
│ │ │ ③ elevatorFeignClient │
│ │ │ .listByImageId() L611 │
│ │ │ ← images (原始通行楼层) │
│ │ │ │
│ │ │ ╔═══════════════════════╗ │
│ │ │ ║ ★ 插入点 (L612之后) ║ │
│ │ │ ╚═══════════════════════╝ │
│ │ │ │
│ │ │ isEnabled(orgIds) │
│ │ │ ─────────────────────────────>│
│ │ │ ← true / false │
│ │ │ │
│ │ │ if true: │
│ │ │ getAllowZoneIds(orgIds) │
│ │ │ ─────────────────────────────>│
│ │ │ ← [zone1, zone2, ...] │
│ │ │ floorList = 策略结果 │
│ │ │ │
│ │ │ if false: │
│ │ │ floorList = L613-626 │
│ │ │ (原始images遍历组装) │
│ │ │ │
│ │ │ ④ setFloorList L628-629 │
│ │ │ │
│ │ ← PersonResult │ │
│ │ .floorList │ │
│ │ (已含策略替代) │ │
│ │ │ │
│ ← PersonResult │ │ │
│ UC-02: effective=param.floorIds │ │
│ 否则: effective=personResult.floorList │ │
│ (组织 detail 已替代;电梯不 ∩ allow) │ │
│ 派梯 │ │
▼ ▼ ▼ ▼
3.2 listByPage() 中的插入点(isVisitor 场景,L319-358)
ImgPersonServiceImpl#listByPage
│
├─ L330: setFloorInfoList(images.getData()) ← listByImageId 原始结果
│
│ ╔═══════════════════════════════════════╗
│ ║ ★ 插入点 (L331-332 之间) ║
│ ╚═══════════════════════════════════════╝
│ │
│ │ if tenantVisitorFloorPolicyService.isEnabled(orgIds):
│ │ floorInfoList = 策略 allow_zone_ids
│ │ setFloorInfoList(floorInfoList)
│ │ setDefaultChooseFloor(策略默认楼层)
│ │ continue ← 跳过 XHW 分支 (L332-357)
│ │
│ ▼ (策略未命中时,继续原有逻辑)
│
├─ L332: setDefaultChooseFloor(defaultFloor) ← 原逻辑
├─ L334: orgFloorMapper.listByOrgIds(orgIds) ← 原逻辑
├─ L340: if xhwId → setDefaultChooseFloor(40F) ← 星河湾分支
└─ L349: else → setDefaultChooseFloor(6F) ← 星河湾分支
3.3 代码插入点与行号对照
| 方法 | 插入位置(与 §3.1 文字一致) | 上下文 | 变量来源 |
|---|---|---|---|
detail() |
images.getCode() 为成功码、floorList 组装循环之前(文中曾写 L612–613 间,以当前 ImgPersonServiceImpl 为准) |
if (Objects.equals(images.getCode(), "00000000")) 块内 |
result.getOrganizationIds() |
listByPage() |
setFloorInfoList(images.getData()) 之后、setDefaultChooseFloor 之前(约 L331–332) |
访客分支内逐行处理 | imgStorePersonResult.getOrganizationIds() |
3.4 策略 Service 接口定义
// 新建:cn.cloudwalk.service.organization.service.visitorpolicy.TenantVisitorFloorPolicyService
public class TenantVisitorFloorPolicyService {
// 查询是否存在启用策略(任一 orgId 命中即返回 true)
public boolean isEnabled(List<String> orgIds);
// 返回解析后的 allow_zone_ids(JSON Array → List<String>)
public List<String> getAllowZoneIds(List<String> orgIds);
}
4. 与星河湾并存:适用范围、优先级与伪代码
4.0 现网代码事实(走查结论,避免误实现)
| 方法 | 星河湾 40F / 6F(xhwDefaultFloorId / xhwSixFloorId) |
租户访客策略(拟插入) |
|---|---|---|
ImgPersonServiceImpl#detail |
未实现:仅 listByImageId → floorList / floorNames(见 §3.1) |
仅在 floorList 组装处做 P1 策略替代;不要在本方法内照搬 listByPage 的 XHW 分支,除非产品明确要求「详情与访客分页默认完全一致」(属行为变更,需单独评审)。 |
ImgPersonServiceImpl#listByPage(param.getIsVisitor() 非空时) |
已实现:在 OrgFloorMapper.listByOrgIds 非空且 isAcrossDay=0 时,按是否含 xhwId 覆盖 defaultChooseFloor 与 floorInfoList(40F vs 6F)(见 §3.2) |
在 L331–332 之间插入:若 P1 策略命中,则用策略结果替换 floorInfoList / 默认楼层并 跳过 原 XHW 块(L332–357);若未命中则保持现有星河湾逻辑。 |
详情 vs 分页的一致性:改造前已存在「detail 无 XHW、分页访客分支有 XHW」差异;若在 detail 仅加 策略替代、在 listByPage 加 策略优先于 XHW,可能出现「同一被访人在详情 floorList 与分页默认展示仍不完全一致」。若需对齐,应在产品层明确是否要给 detail 增加与分页相同的默认楼层规则(接口字段不变,仅内部赋值变化)。
4.1 优先级总表(与现网 XHW 不冲突的前提)
| 优先级 | 条件 | 行为 |
|---|---|---|
| P1 | 租户访客策略命中且启用 | 替代:① PersonService.detail → floorList = allow_zone_ids;② listByPage 访客行上用于展示的 floorInfoList/默认层(见 §4.3)。访客邀约页只吃 ①,不吃 ②。 |
| P2 | 未命中策略,且走 listByPage 访客分支 且满足现网 XHW 前置条件(含 orgFloorList 非空等) |
保持现有 40F / 6F 逻辑(xhwId vs xhwSixFloorId)。 |
| P3 | 兜底 | listByImageId 原始结果;detail 路径下无 P2(因现网无 XHW)。 |
4.2 伪代码:detail() — 仅 P1 与 P3(不要写入 XHW else-if)
// ImgPersonServiceImpl#detail — listByImageId 已成功返回后、setFloorList 之前(参见 §3.1 插入点)
List<String> orgIds = result.getOrganizationIds();
if (tenantVisitorFloorPolicyService.isEnabled(orgIds)) {
// P1:策略替代(PersonResult / 映射链最终消费的 floorList 与此一致)
List<String> allow = tenantVisitorFloorPolicyService.getAllowZoneIds(orgIds);
floorList = allow; // 另需同步 floorNames 等与契约一致的展示字段,实现自行拆解 zoneId→名称若需要
} else {
// P3:与现网一致 — 保留 listByImageId 遍历结果(L613-626)
// 此处不要添加 else if (xhwId) — 现网 detail 本无星河湾分支
}
result.setFloorList(floorList);
4.3 伪代码:listByPage 访客分支 — P1 跳过 XHW(P2)
// 在 setFloorInfoList(images.getData()) 之后、setDefaultChooseFloor 之前(§3.2 L331-332 间)
if (tenantVisitorFloorPolicyService.isEnabled(orgIds)) {
// P1:用策略替代楼层展示;跳过下方 OrgFloor / 星河湾 40F/6F
applyPolicyToFloorInfoListAndDefault(imgStorePersonResult, orgIds);
continue; // 或等价结构,避免进入 L332-357 原 XHW 逻辑
}
// 未命中策略:保持现网顺序 — L332 起 defaultFloor、OrgFloor、xhwId→40F / else→6F
5. 组织组件 — 新增/修改清单
5.1 数据层(cwos-component-organization-data)
| 工作项 | 详情 |
|---|---|
| DDL | 从 releases/cw-elevator-application-V2.0.20.20260505/ddl/tenant_visitor_floor_policy.sql 迁移到 docs/sql/tenant_visitor_floor_policy.sql |
| Entity | cn.cloudwalk.data.organization.entity.TenantVisitorFloorPolicy |
| Mapper | cn.cloudwalk.data.organization.mapper.TenantVisitorFloorPolicyMapper.java + XML |
| 数据迁移 SQL | 从电梯库 cwo_elevator_db.tenant_visitor_floor_policy → 组织库(一次性,含回滚) |
5.2 服务层(cwos-component-organization-service)
| 工作项 | 详情 |
|---|---|
| 新建 | TenantVisitorFloorPolicyService |
| 方法 | boolean isEnabled(List<String> orgIds) — 是否存在启用策略 |
| 方法 | List<String> getAllowZoneIds(List<String> orgIds) — 返回 allow_zone_ids(已解析 JSON) |
| 修改 | ImgPersonServiceImpl#detail — floorList 组装处插入 §4.2(P1/P3,无 XHW) |
| 修改 | ImgPersonServiceImpl 中星河湾分支 — 策略命中时跳过 xhwDefaultFloorId/xhwSixFloorId 覆盖 |
5.3 接口/Web 层
| 模块 | 操作 |
|---|---|
cwos-component-organization-interface |
零变更 |
cwos-component-organization-web |
仅更新 PersonController.java 类注释 |
6. 电梯应用 — 完整删除清单
6.1 删除文件清单
| 文件 | 路径 |
|---|---|
| TenantVisitorFloorPolicyDao.java | cw-elevator-application-data/.../person/dao/ |
| TenantVisitorFloorPolicyDaoImpl.java | cw-elevator-application-data/.../person/impl/ |
| TenantVisitorFloorPolicyMapper.java | cw-elevator-application-data/.../person/mapper/ |
| TenantVisitorFloorPolicyMapper.xml | cw-elevator-application-data/src/main/resources/mapper/ |
| TenantVisitorFloorPolicyDto.java | cw-elevator-application-data/.../person/dto/ |
| DDL 文件 | releases/*/ddl/tenant_visitor_floor_policy*.sql(保留历史参考或删除) |
6.2 修改代码清单
| 文件 | 行号 | 操作 |
|---|---|---|
PersonRuleServiceImpl.java |
L32-33 | 删除 import TenantVisitorFloorPolicyDao / TenantVisitorFloorPolicyDto |
PersonRuleServiceImpl.java |
L83 | 删除 @Autowired TenantVisitorFloorPolicyDao |
PersonRuleServiceImpl.java |
L211-230 | 删除整个阶段3(查策略+求交逻辑) |
PersonRuleServiceImpl.java |
L232-238 | 修改阶段4:effective 改为 candidate |
PersonRuleServiceImpl.java |
L297-353 | 删除 findPolicyByOrgIds() 和 parseAllowZoneIds() 私有方法 |
08-visitor-registration-and-elevator-auth.md |
— | 更新文档:"租户策略求交" → "组织侧策略替代" |
6.3 改造后 addVisitor 精简代码
public CloudwalkResult<Boolean> addVisitor(AcsPersonAddVisitorParam param, CloudwalkCallContext context) {
// 阶段1:查询被访人(含组织信息 + 人行规楼层)
PersonDetailParam detailParam = new PersonDetailParam();
detailParam.setId(param.getPersonId());
detailParam.setBusinessId(context.getCompany().getCompanyId());
CloudwalkResult<PersonResult> detailResult = this.personService.detail(detailParam, context);
// ... 错误检查 ...
PersonResult personResult = (PersonResult) detailResult.getData();
// 阶段2:确定生效楼层(直接使用 candidate,不做交集)
List<String> effective;
if (!CollectionUtils.isEmpty(param.getFloorIds())) {
effective = param.getFloorIds(); // UC-02
} else {
effective = personResult.getFloorList(); // UC-01,组织侧已含策略
if (CollectionUtils.isEmpty(effective)) {
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
}
// 阶段3:空集校验 + 派梯
param.setFloorIds(effective);
// ... 后续派梯逻辑不变 ...
}
7. 测试清单
- 业务路径:访客邀约 / 楼层初始化 仅通过
PersonService.detail→PersonResult.floorList获取权威清单(未用分页或其它接口顶替) - 有策略租户:
detail返回的floorList=allow_zone_ids(纯替代,非交集) - 有策略 + addVisitor:effective = floorList(不含交集过滤)
- 无策略 + 星河湾:行为与现网 40F/6F 一致
- 有策略 + 星河湾同时配置:策略优先,不触发 xhw 覆盖
- 无策略时 detail vs 分页:确认是否接受「detail 仍无 XHW、分页访客分支仍有 40F/6F」的现网差异;若产品要求一致,需另立 story(见 §4.0)
- UC-02(派梯接口):请求体传
floorIds→effective以请求为准(与邀约页「必须 detail」并行不悖:邀约仍只认 detail;业务含义见 §2.1,边界见 §2.3) - 数据迁移:行数一致、org_id/business_id 正确
- 接口回归:组织/电梯/智能组件对外 API 无路径/字段/方法变更
- 电梯回退:若组织侧部署失败,电梯仍可回退到旧版(DDL 保留期间)
8. 实施步骤
| 步骤 | 内容 | 验证方式 |
|---|---|---|
| 1 | 组织 data 模块:建表 DDL + Entity + Mapper | mvn compile data 模块通过 |
| 2 | 组织 service 模块:新建 TenantVisitorFloorPolicyService | 单元测试 |
| 3 | 组织 service 模块:修改 ImgPersonServiceImpl#detail 与 listByPage 访客分支(§4.2 / §4.3) |
集成测试 |
| 4 | 数据迁移:电梯库 → 组织库 | 行数对比 |
| 5 | 电梯侧:删除策略相关代码(§6.1 + §6.2) | mvn compile 通过 |
| 6 | 端到端:addVisitor + detail 行为验证 | API 对拍测试 |
| 7 | 发布:先发组织侧,观察后发电梯侧(兼容窗口) | 监控 |
9. 参考
- 电梯访客文档:
maven-cw-elevator-application/cw-elevator-application-service/docs/08-visitor-registration-and-elevator-auth.md - 数据模型:
docs/architecture/租户组织人员访客-数据模型与用例.md - 历史设计:
docs/business/租户访客默认楼层-数据库配置阶段技术设计.md - 当前策略代码:
PersonRuleServiceImpl.javaL211-230, L297-353 - 星河湾分支:
ImgPersonServiceImpl.javaL149-354
修订记录
| 版本 | 日期 | 说明 |
|---|---|---|
| 0.1 | 2026-05-06 | 初稿 |
| 0.2 | 2026-05-06 | 增加硬约束 |
| 0.3 | 2026-05-06 | 重构:明确"组织侧唯一实现,电梯侧完全移除";增加数据流对比图、删除清单、伪代码 |
| 0.4 | 2026-05-06 | 增加:业务时序图(含代码行号对照)、detail/listByPage 插入点、策略 Service 接口定义 |
| 0.5 | 2026-05-06 | 修订:§4 拆分 detail(仅 P1/P3,不含 XHW)与 listByPage(P1 跳过星河湾);补充现网 XHW 适用范围表;修正重复「## 4」章节编号(组织清单改为 §5,电梯 §6,顺延);标注 detail/分页一致性风险 |
| 0.6 | 2026-05-06 | 增加:文首「场景说明」— 访客邀请初始化与楼层清单、detail / listByPage 职责划分及与 UC-01 对齐说明 |
| 0.7 | 2026-05-06 | 增加:访客邀约端到端业务流图;代码侧 sequence(detail+策略)、flowchart(addVisitor 改造后、listByPage isVisitor);修正 §1 笔误「侧略」→「策略」 |
| 0.8 | 2026-05-06 | 明确:业务楼层清单 必须走 PersonService.detail → PersonResult.floorList;硬约束与场景说明升级为「规定主路径」;流程图 A3/B 标题与 §1 数据流对齐 |
| 0.9 | 2026-05-06 | 冲突清理:区分对外契约 vs 组织内 listByImageId;§1 关键变化表拆分 UC-01/UC-02;§2.2 与「必须 detail」边界表;§2.1 交集/替代表述与改造目标对齐;§4.1 P1 区分邀约只吃 detail;§3.1 标题与 §3.3 插入点表述统一;§7 UC-02 测试条注释 |
| 1.0 | 2026-05-06 | 增加:§2.1 UC-01/UC-02 业务场景说明(非仅代码条件);原 §2.1/2.2 顺延为 §2.2/2.3;更新文首与 §7 对 §2 的引用 |