Files
starRiverProperty/docs/business/访客邀约与派梯楼层一致性梳理.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

358 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 访客邀约与派梯楼层一致性梳理
> **文档用途**:把「邀约页展示的楼层」与「电梯开通权限实际生效的楼层」如何在现有代码中对齐,用大白话 + 源码位置一并说明,便于产品、集成与排障。
> **关联文档**:[访客与电梯业务完整说明](../../maven-cw-elevator-application/cw-elevator-application-service/docs/08-visitor-registration-and-elevator-auth.md)、[租户访客默认楼层技术产品方案](租户访客默认楼层技术产品方案.md)、[租户访客策略迁入组织规格](../superpowers/specs/2026-05-06-tenant-visitor-policy-organization-implementation.md)、[**租户访客楼层策略 — 代码重构实施指南**](租户访客楼层策略-代码重构实施指南.md)(落地步骤与阶段划分)。
---
## 1. 一句话原则
**所有环节只信同一串楼层 ID**:能去哪几层,应在**拉被访人详情**时由组织侧算清(含租户默认楼层策略时写入 `floorList`);邀约登记把用户选定写进自己的业务单;**派梯时把这份楼层列表放进请求里的 `floorIds`**,电梯就按这一串执行。**不要用「人员列表分页」等接口顶替详情里的楼层主数据。**
---
## 2. 大白话说明
| 诉求 | 做法 |
|------|------|
| 邀约和派梯不要两套楼层 | 选人后楼层候选以 **`PersonService.detail`(组织 `/component/person/detail`)→ `floorList`** 为准;租户「默认只开放某几层」应在组织 **`detail` 链路内完成策略替代后再返回(规范见 §5,落地状态见 §6)。 |
| 邀约单已经定了访问楼层 | 后续 **`POST /elevator/person/add/visitor` 必须把邀约单里保存的那串楼层放进 `floorIds`**;电梯**不会**根据邀约单号去数据库查记录——**只认本次 HTTP 参数**。 |
| 派梯请求不传楼层 | 电梯会用 **被访人详情里的 `floorList`** 作为生效楼层,**不会**自动对齐你们库里存的邀约楼层——若要与邀约一致,**集成侧应始终传 `floorIds`**。 |
---
## 3. 时序图(实现归属:组织侧 / 电梯侧 / 集成侧)
**图例**
| 标注 | 含义 |
|------|------|
| **组织侧** | `maven-ninca-common-component-organization``PersonController``ImgPersonServiceImpl#detail`、组织库、(规范中的)租户访客策略服务 |
| **电梯侧** | `maven-cw-elevator-application``AcsPersonController``PersonRuleServiceImpl#addVisitor`、电梯库 `image_rule_ref` / `zone` / 楼栋图库映射等 |
| **Intelligent** | `maven-intelligent-cwoscomponent``PersonService.detail` → Feign 调组织 `/component/person/detail` |
| **集成侧** | 第三方 / BFF / 访客业务库:邀约单持久化、派梯前组装 `floorIds`(电梯代码中无邀约表) |
### 3.0 端到端:访客建档在前、派梯在后(推荐集成顺序)
典型闭环:**先**完成 **访客人员建档** 并得到 **`visitorId`**(平台人员主键),**再**调用 **`POST /elevator/person/add/visitor`**。邀约页拉 **`detail`**、保存 **`floorIds`** 可与建档分步编排;但 **`addVisitor` 必须在建档成功之后调用**(电梯侧不写访客主数据,见 §4.4)。
```mermaid
sequenceDiagram
autonumber
participant U as 用户 / 前台
participant BFF as BFF / 集成侧
participant PS as Intelligent<br/>PersonService.detail
participant OC as 组织侧<br/>ImgPersonServiceImpl#detail
participant ORG as 组织侧<br/>访客建档(示意接口)
participant AC as 电梯侧<br/>AcsPersonController addVisitor
rect rgb(245,248,250)
Note over U,OC: ① 邀约:选被访人 + 楼层候选(与 §3.1 同源)
U->>BFF: 选被访人 hostPersonId
BFF->>PS: detail(hostPersonId)
PS->>OC: POST /component/person/detail
OC-->>BFF: PersonResult.floorList 等
U->>BFF: 勾选楼层并提交邀约单
BFF->>BFF: 持久化邀约记录(hostPersonId、floorIds、访期等)
end
rect rgb(248,252,255)
Note over BFF,ORG: ② 访客建档(须先于派梯;具体路径以现场组织/访客模块为准)
BFF->>ORG: 创建访客人员 / 人像入库(示意)
Note over ORG: 组织侧或其它访客服务:人员落库,得到 visitorId
ORG-->>BFF: visitorId(平台 personId
end
rect rgb(255,248,245)
Note over BFF,AC: ③ 派梯开通(visitorId 已存在)
BFF->>AC: POST /elevator/person/add/visitor<br/>personId=hostPersonId, visitorId, floorIds=邀约单楼层…
Note over AC: 电梯侧:§3.3 UC-02;不传 floorIds 则 §3.2 UC-01
AC-->>BFF: CloudwalkResult
end
```
| 区块 | 实现归属 | 说明 |
|------|-----------|------|
| ① 邀约与楼层候选 | **集成侧** + **Intelligent → 组织** `detail` | 楼层权威路径与 **§3.1** 一致;邀约单仅存 **集成侧** 业务库 |
| ② 访客建档 | **组织侧为主(示意)** | 本仓库 **`addVisitor`** **不包含**建档;返回的 **visitorId** 即后续规则与图库绑定的主体 |
| ③ 派梯 | **电梯侧** `addVisitor` + 组织 Feign 绑图库 | 详见 **§4.5**;请求体**无邀约单号****floorIds** 须由集成侧按单据填入(对齐 **§3.3** |
---
### 3.1 邀约页初始化 — 拉被访人「可访问楼层」主路径(与 UC-01 同源)
组织侧组装 `floorList`;其中 **`listByImageId`** 通过 Feign **回调电梯 HTTP** 读取通行规则。
```mermaid
sequenceDiagram
autonumber
participant FE as 前端 / BFF<br/>集成侧
participant PS as Intelligent<br/>PersonService.detail
participant OC as 组织侧<br/>PersonController / ImgPersonServiceImpl#detail
participant EF as 电梯 HTTP<br/>passRule/imagelistByImageId
participant TP as 租户策略(规范)<br/>组织侧 DB / Service
FE->>PS: detail(personId, businessId)
Note over PS: Intelligent:路由实现
PS->>OC: POST /component/person/detail
Note over OC: 组织侧:查人员、组装机构标签等
OC->>EF: Feign listByImageId(通行楼层原始列表)
Note over EF: 电梯侧:凭规则返回 zoneId 列表
EF-->>OC: AcsPassRuleImageResultDto 列表
OC->>TP: (可选)命中租户访客策略则替代 floorList
Note over TP: 组织侧:策略语义见 §5<br/>未落地时仍为 listByImageId 结果(§6
TP-->>OC: allow_zone_ids / 未启用
OC-->>PS: PersonResult(含 floorList
PS-->>FE: 展示可选楼层 / 默认勾选依据
```
| 步骤 | 实现位置 |
|------|-----------|
| 对外 detail 聚合入口 | **Intelligent** → 组织 **`PersonController`** |
| `floorList` 原始数据来源(通行规则) | **电梯侧** HTTP,由组织 **`ElevatorFeignClient.listByImageId`** 调用 |
| 租户默认楼层「替代」写入 `floorList` | **组织侧**(规范:`ImgPersonServiceImpl#detail` 内;见 §5~§6 |
| 邀约单保存用户勾选 | **集成侧**业务库,不在本仓库电梯模块 |
---
### 3.2 派梯 UC-01 — 请求 **未带** `floorIds`(电梯用组织 detail 的 `floorList`
电梯侧编排;**被访人详情**仍在 **组织侧** 计算;写规则、绑图库在 **电梯侧 + 组织 Feign**
```mermaid
sequenceDiagram
autonumber
participant BFF as BFF / 调用方<br/>集成侧
participant AC as 电梯侧<br/>AcsPersonController
participant PR as 电梯侧<br/>PersonRuleServiceImpl#addVisitor
participant PS as Intelligent<br/>PersonService.detail
participant OC as 组织侧<br/>ImgPersonServiceImpl#detail
participant DB as 电梯侧 DB<br/>image_rule_ref 等
participant IS as 组织侧<br/>ImageStorePersonService.batchBindFeign
BFF->>AC: POST /elevator/person/add/visitor<br/>floorIds 为空
AC->>PR: addVisitor(param)
PR->>PS: detail(personId)
PS->>OC: /component/person/detail
Note over OC: 组织侧:返回 floorList(§3.1 同源)
OC-->>PS: PersonResult
PS-->>PR: floorList
PR->>PR: effective = personResult.floorListUC-01
PR->>DB: 按楼层写规则、取楼栋 imageStoreId
Note over DB: 电梯侧:PersonRuleServiceImpl 本地 Dao
PR->>IS: batchBind(visitorId, 访期…)
Note over IS: 组织侧:图库绑定
IS-->>PR: 成功 / 失败
PR-->>AC: CloudwalkResult
AC-->>BFF: 结果
```
| 步骤 | 实现位置 |
|------|-----------|
| HTTP 入口 | **电梯侧** `AcsPersonController` |
| UC-01 取楼层 | **电梯侧** `PersonRuleServiceImpl` 使用 **组织 detail** 返回的 `floorList` |
| `PersonService.detail` 实现 | **组织侧** `ImgPersonServiceImpl#detail` |
| 写 `image_rule_ref`、选首层换楼栋 | **电梯侧** `PersonRuleServiceImpl` |
| 访客绑图库 | **组织侧** 服务,电梯 **Feign** 调用 |
---
### 3.3 派梯 UC-02 — 请求 **携带** `floorIds`(与邀约单保存的楼层一致)
与 UC-01 共用同一入口;**生效楼层**取请求体,**不再**用 `detail.floorList` 替换,但仍会调 **detail** 做被访人存在性与前置校验。
```mermaid
sequenceDiagram
autonumber
participant BFF as BFF / 调用方<br/>集成侧
participant AC as 电梯侧<br/>AcsPersonController
participant PR as 电梯侧<br/>PersonRuleServiceImpl#addVisitor
participant PS as Intelligent<br/>PersonService.detail
participant OC as 组织侧<br/>detail(仅校验被访人)
participant DB as 电梯侧 DB
participant IS as 组织侧<br/>batchBindFeign
BFF->>AC: POST /elevator/person/add/visitor<br/>floorIds = 邀约单持久化的列表
Note over BFF: 集成侧:从邀约记录读出楼层写入 body
AC->>PR: addVisitor(param)
PR->>PS: detail(personId)
PS->>OC: /component/person/detail
OC-->>PR: PersonResultfloorList 本轮可不用于生效集)
PR->>PR: effective = param.getFloorIds()UC-02
Note over PR: 电梯侧:不做 floorIds ⊆ detail.floorList 校验(信任调用方)
PR->>DB: 按 effective 写规则…
PR->>IS: batchBind…
IS-->>PR: ok
PR-->>BFF: 结果
```
| 步骤 | 实现位置 |
|------|-----------|
| 从邀约单带出 `floorIds` | **集成侧** |
| UC-02 分支(`effective = param.floorIds` | **电梯侧** `PersonRuleServiceImpl` |
| 子集校验(可选) | **集成侧 BFF** 或后续扩展;**当前电梯侧未实现** |
---
## 4. 派梯接口代码走查(电梯应用)
### 4.1 HTTP 入口
- **路径**`POST /elevator/person/add/visitor`
- **类**`maven-cw-elevator-application/cw-elevator-application-web/.../AcsPersonController.java`
- **请求体**`AcsPersonAddVisitorForm` → 拷贝为 `AcsPersonAddVisitorParam``PersonRuleService.addVisitor`
### 4.2 请求体字段(与邀约记录的关系)
| 字段 | 含义 |
|------|------|
| `personId` | 被访人在组织侧的人员 ID |
| `visitorId` | 访客人员 ID(平台 personId |
| `begVisitorTime` / `endVisitorTime` | 访期(绑图库 et al. |
| `floorIds` | **本次开通涉及的楼层 zoneId 列表**;由调用方填入。**无邀约单号字段**——电梯侧不读访客邀约业务表 |
定义见:`cw-elevator-application-web/.../form/AcsPersonAddVisitorForm.java`
### 4.3 生效楼层如何决定(`PersonRuleServiceImpl#addVisitor`
实现类:`cw-elevator-application-service/.../PersonRuleServiceImpl.java`
1. **始终先调** `personService.detail`(被访人存在性 / 组织侧详情)。失败则直接返回(如 `76260531`)。
2. **决定 `effective`(最终开通的楼层列表)**
- **`floorIds` 非空**(实现里称 **UC-02**):**`effective = 请求里的 `floorIds`**,原样使用。
- **`floorIds` 为空****UC-01**):**`effective = personResult.getFloorList()`**(来自组织 `detail`)。
3. **空列表**:返回失败(`76260531`)。
4. 后续:按 `effective` 每层写 `image_rule_ref`、取楼栋图库 `imageStoreId`、对 **`visitorId`** 调组织侧 `batchBind`、更新组人员引用等。
**重要**:显式传入 `floorIds` 时,当前实现**不会**再与 `personResult.getFloorList()` 做「子集校验」——楼层是否合规依赖**调用方 / BFF**;电梯信任请求体。
### 4.4 业务目标与范围(访客派梯做什么、不做什么)
| 维度 | 说明 |
|------|------|
| **目标** | 在已有 **访客人员 ID**`visitorId`)前提下,为本租户本次访问开通 **电梯通行规则**(电梯库 `image_rule_ref`),并把访客绑定到 **对应楼栋的人脸图库**(组织侧 `batchBind`),使闸机/电梯侧能按楼层放行。 |
| **不做** | **不在此接口创建访客档案**;访客姓名、证件、人像建档应在组织/访客业务前置完成并得到 **`visitorId`**。 |
| **租户上下文** | `businessId` 来自调用上下文(与 `CloudwalkCallContext.company` 一致),与被访人 `personId`、访客 `visitorId` 同属该租户数据范围。 |
---
### 4.5 分阶段业务逻辑(与 `PersonRuleServiceImpl#addVisitor` 对齐)
```mermaid
flowchart LR
S1[1 被访人 detail] --> S2[2 确定 effective]
S2 --> S3[3 首层 zone → 楼栋 → imageStoreId]
S3 --> S4[4 每层写 image_rule_ref<br/>personId=visitorId]
S4 --> S5[5 batchBind 访期 + updateGroupPersonRef]
```
下列阶段均在 **`maven-cw-elevator-application`** · **`PersonRuleServiceImpl.addVisitor`** 中顺序执行。
#### 阶段 1 — 校验被访人可查(组织侧)
- 构造 `PersonDetailParam``id = personId``businessId = context 租户`),调用 **`personService.detail`**(经 Intelligent → 组织 **`ImgPersonServiceImpl#detail`**)。
- **成功**:得到 **`PersonResult`**,后续 UC-01 需要其中的 **`floorList`**。
- **失败**:透传组织返回的 **code / message**;若结果为 null 或不成功且无语义,使用 **`76260531`**(无可用被访人信息或详情不可用)。
- **数据为空**`personResult == null`**`76260531`**。
#### 阶段 2 — 确定本次生效楼层列表 `effective`
- 见 §4.3**UC-02** 用请求 **`floorIds`****UC-01** 用 **`personResult.getFloorList()`**。
- **`effective` 为空**(含 UC-01 时 `floorList` 为空、或 UC-02 传空列表):**`76260531`**。
#### 阶段 3 — 解析「楼栋」并确定组织侧图库 `imageStoreId`(电梯侧 + 空间服务)
-**`effective` 的第一个元素**作为 **`zoneQueryParam.id`**,调用 **`zoneService.page`**(空间服务)解析 **楼层节点**
- 用返回的 **`ZoneResult.get(0).getParentId()`** 作为 **楼栋 ID**,再 **`deviceImageStoreDao.getByBuildingId(parentId)`** 得到 **`imageStoreId`**。
- **业务含义**:多楼层同一次开通时,**以列表首层所在楼栋**作为本次绑定的图库归属;各 **`floorId` 仍分别落在对应分区规则上,但 **人脸图库绑定指向同一 `imageStoreId`**(由首层楼栋决定)。
#### 阶段 4 — 按楼层写入访客通行规则(电梯侧库)
-**`effective` 中每个 `floorId`**
**`imageRuleRefDao.getDefaultByZoneId(floorId)`** 取该分区默认规则模板 → 组装 **`ImageRuleRefAddDto`****`personId` 填的是 `visitorId`(访客)**,写入 **`image_rule_ref`**(批量 **`insertList`**)。
- **语义**:访客在每个选定楼层上各有一条「挂默认父规则」的通行引用,与被访人自有规则分离。
#### 阶段 5 — 访期内人脸图库绑定与组同步(组织侧 Feign)
- **`ImageStorePersonBindParam`**`imageStoreId`(阶段 3)、**`personIds = [visitorId]`**、**`nullDateIsLongTerm = true`**、**`expiryBeginDate` / `expiryEndDate`** 取自请求的 **`begVisitorTime` / `endVisitorTime`**。
- 调用 **`imageStorePersonService.batchBind`**(失败则返回 **组织侧错误码**,电梯透传)。
- 成功后 **`updateGroupPersonRef`**:同一 **`visitorId`** + **`imageStoreId`**,用于组人员引用同步(具体语义见组织组件实现)。
#### 阶段 6 — 异常兜底
- 未捕获的 **`ServiceException`**:向上抛出。
- 其他 **`Exception`**:包装为 **`76260530`**(通用失败)。
---
### 4.6 访期参数(`begVisitorTime` / `endVisitorTime`
| 项 | 说明 |
|------|------|
| **用途** | 传入组织 **`batchBind`**,作为访客在该图库上的 **有效期起止**(与通行规则配合使用,以现场组织/图库策略为准)。 |
| **与楼层关系** | 访期 **不改变** `effective` 楼层集合;仅影响绑定是否在时间上有效。 |
| **`nullDateIsLongTerm=true`** | 代码固定传入;具体与空起止如何组合以组织 **`batchBind`** 实现为准,集成时建议在测试环境验证边界。 |
---
### 4.7 错误码与典型原因(电梯侧可见)
| 错误码 | 典型场景 |
|--------|-----------|
| **`76260531`** | `detail` 失败;被访人 `PersonResult` 为空;UC-01 时 **`floorList` 为空**;或 **`effective` 最终仍为空**。 |
| **`76260530`** | `addVisitor` 内部未预期的 **`Exception`**(如 DB、空指针、空间分页无数据等未单独映射时)。 |
| **组织 / Feign 返回码** | **`batchBind`** 失败时 **透传**对方 **code / message**,成功后再执行 **`updateGroupPersonRef`**;若 bind 失败则 **不会**继续组引用更新。 |
| **`76260521`** | Controller 层捕获 **`ServiceException`** 时映射(多见于其它接口;`addVisitor` 内多为 fail 码直接返回)。 |
排障时建议按日志顺序核对:**detail → effective → zone 首层 → imageStoreId → 每层 getDefaultByZoneId → batchBind**。
---
### 4.8 业务边界与集成注意
1. **首层决定楼栋图库**:若 `effective` 中楼层分属不同楼栋,当前实现**仅以第一项**定 `imageStoreId`;产品若要求「多楼栋多图库」,需拆分多次调用或扩展实现(现状未支持单次多楼栋)。
2. **每层必须有默认规则**:某 **`floorId`** 下 **`getDefaultByZoneId` 若为空**,可能在后续组装/插入时异常并落入 **`76260530`**,需在数据配置层保证各访客可达层已配置默认规则。
3. **幂等与重复开通**:同一访客重复调用可能产生多条规则引用或组织侧重复绑定行为,**以组织与电梯 DAO 约束为准**;重要场景建议业务层幂等(例如按邀约单号去重)。
4. **部分失败**:规则已 **`insertList`** 后若 **`batchBind`** 失败,当前流程 **不会自动回滚**已插入的规则行(需运维或补偿策略关注)。
---
## 5. 与「租户访客默认楼层」策略的关系
- **规范方向**(详见 `docs/superpowers/specs/2026-05-06-tenant-visitor-policy-organization-implementation.md`):租户允许楼层在组织 **`ImgPersonServiceImpl#detail`** 内以 **`allow_zone_ids` 替代**写入返回的 **`floorList`**;电梯 **UC-01** 仅透传该 `floorList`,不在电梯库再与策略表求交。
- **邀约初始化**:应通过 **`detail``floorList`** 展示可选楼层,与 **UC-01** 同源。
- **邀约单已保存楼层后的派梯**:把单据里的楼层写入 **`floorIds`**(UC-02),与单据一致;若需防止超范围,应在 **BFF** 侧校验 `floorIds ⊆ detail.floorList`(策略替代后)。
---
## 6. 实现状态提示(避免误判)
组织侧已实现:**`TenantVisitorFloorPolicyService`** 读取组织库 **`tenant_visitor_floor_policy`**,在 **`ImgPersonServiceImpl#detail`**(及 **`page(isVisitor)`**)对 **`floorList` / 楼层展示** 做 **`allow_zone_ids` 替代**。部署前须在组织库执行 **`docs/sql/organization_tenant_visitor_floor_policy.sql`**;未建表时策略查询失败会自动回退为 **`listByImageId` 原始结果**。详见 [租户访客楼层策略-代码重构实施指南](租户访客楼层策略-代码重构实施指南.md)。
---
## 7. 集成 checklist
- [ ] 邀约页初始化楼层:**走 `PersonService.detail`**,不要用人员分页/导出接口当作 `floorList` 主来源。
- [ ] 邀约保存:持久化用户选定或允许的 **楼层 zoneId 列表**(与展示同源)。
- [ ] 派梯:**从邀约单读出楼层 → 填入 `floorIds`** 调用 `/elevator/person/add/visitor`;若业务允许「不传楼层」,需知悉此时等价 UC-01,**与邀约单无自动对齐**。
- [ ] 安全(可选):BFF 校验 `floorIds``detail.floorList` 的子集(或业务规则允许的超集策略)。
- [ ] 日志:`AcsPersonController` 已打 `requestFloorSize`,便于核对是否传了楼层。
---
## 8. 源码索引(电梯)
| 环节 | 路径 |
|------|------|
| Controller | `maven-cw-elevator-application/cw-elevator-application-web/.../AcsPersonController.java``addVisitor` |
| 表单 | `.../form/AcsPersonAddVisitorForm.java` |
| 参数 | `cw-elevator-application-service/.../param/AcsPersonAddVisitorParam.java` |
| 核心逻辑(§4.4~§4.8) | `cw-elevator-application-service/.../PersonRuleServiceImpl.java``addVisitor` |
| 空间分页(首层 → 楼栋) | 同模块内 **`ZoneService`**`PersonRuleServiceImpl` 注入调用) |
| 图库绑定 / 组引用 | Feign → 组织 **`ImageStorePersonService`**`batchBind``updateGroupPersonRef` |
---
**文档版本**:与仓库梳理同步;若 `addVisitor` 行为变更,请同步更新 **§4**(含 §4.4~§4.8 业务逻辑与错误码)。