# 访客与电梯业务:注册/登记与派梯授权完整说明 > 本文档覆盖 **组织组件**(`maven-ninca-common-component-organization`)、**智能组件 Feign 路由**(`maven-intelligent-cwoscomponent`)与 **电梯应用**(`maven-cw-elevator-application`)中与「访客派梯」相关的可追踪路径;区分 **访客主数据登记** 与 **`POST /elevator/person/add/visitor` 派梯授权**。文中源码路径均相对于各 Maven 模块仓库根目录。 --- ## 1. 概念与边界 | 概念 | 通常含义 | 在本项目中的落点 | |------|----------|------------------| | **访客主数据登记/注册** | 在标准访客/一卡通中录入档案 | **不在**电梯应用内完成完整登记 UI;人员以 **`visitorId`(平台 personId)** 等形式存在于外部服务。 | | **电梯侧「访客派梯授权」** | 在已有访客人员 ID 前提下写入通行规则、绑定图库、访期 | **`PersonRuleServiceImpl#addVisitor`** → **`POST /elevator/person/add/visitor`**(`AcsPersonController`)。 | | **通行记录「是否访客」打标** | 识别流水写库时标记访客/被访人 | **`AcsElevatorRecordServiceImpl#add`** → CRK **`/intelligent/three/visitor/record/query`**(见第 8 节)。 | --- ## 2. 组件关系总览 访客邀约页、电梯 **`addVisitor`** 在 **`floorList` 语义上应对齐**:二者都应依赖 **`PersonService.detail` → `PersonResult.floorList`**(经 Intelligent 转发到组织 **`POST /component/person/detail`**)。组织组装 `floorList` 时会 **Feign 回调电梯**(见 **§3.3**):电梯 **`image_rule_ref`** 里已有的人员—楼层规则,经 **`/elevator/passRule/image`** 转成 **`zoneId` 列表**,再写回组织的 **`floorList`**。 ```mermaid flowchart LR subgraph fe["前端 / BFF / 第三方"] UI[访客邀约 / 派梯调用方] end subgraph intelligent["maven-intelligent-cwoscomponent"] PS[PersonService / RestPersonServiceImpl] PFC[PersonFeignClient → /component/person] end subgraph org["maven-ninca-common-component-organization"] PC[PersonController /component/person] IMG[ImgPersonServiceImpl] EF[ElevatorFeignClient → /elevator/passRule/image] end subgraph elevator["maven-cw-elevator-application"] AC[AcsPersonController /elevator/person/add/visitor] PR[PersonRuleServiceImpl addVisitor] IRR[ImageRuleRefDao / ZoneService / DeviceImageStoreDao] IS[ImageStorePersonService Feign 绑定图库] end UI --> PS PS --> PFC --> PC --> IMG IMG --> EF UI --> AC --> PR PR --> PS PR --> IRR PR --> IS ``` --- ## 3. 组织侧:被访人 `detail` 与 `floorList`(邀约页 / UC-01 的数据源) ### 3.1 HTTP 入口与实现类 | 项 | 说明 | |----|------| | **路径** | **`POST /component/person/detail`** | | **Controller** | `cwos-component-organization-web` · `PersonController#detail` | | **服务** | `ImgStorePersonService#detail` → **`ImgPersonServiceImpl#detail`** | 对外契约返回 **`CloudwalkResult`**;经 Intelligent **`PersonFeignClient`** 解码为 **`PersonResult`**(字段名一致部分落入 **`floorList`**,电梯 **`addVisitor`** 仅消费 **`PersonResult.getFloorList()`**)。 ### 3.2 `ImgPersonServiceImpl#detail` 处理顺序(与源码一致) | 步骤 | 做什么 | 与 `floorList` 的关系 | |------|--------|----------------------| | 1 | **`selectByPrimaryKey`** 查组织库人员 | 无此人则 **`data` 可为 null**(见下) | | 2 | 可选 **`defaultFloor`** → **`zoneFeignClient.findZonelist`** | 仅 **`floorName` 展示**,**不等于**下面得到的 **`floorList`** | | 3 | 可选 **`vehicleFeignClient.getVehicleIds`** | 与楼层列表无关 | | 4 | **`getImgStorePersonResults`** | 得到 **`organizationIds`、`labelIds`**,供 **§3.3** 调用电梯 | | 5 | **`elevatorFeignClient.listByImageId(...)`**(Feign,语义见 **§3.3**) | **唯一**写入 **`floorList` / `floorNames`**(在返回码成功且进入分支时) | | 6 | (规范)租户 **`allow_zone_ids` 替代** | **当前未实现**,运行态 **`floorList`** = 步骤 5 结果 | | 7 | **`portalUserService.query`** 填创建/更新人姓名 | 与楼层无关 | - **查无此人**:**`result` 仍为 null**,接口 **`CloudwalkResult.success(null)`**;电梯 **`addVisitor`** 取 **`personResult == null`** → **`76260531`**。 - **步骤 5 失败或未进入分支**:**`floorList` 不会被赋值**(可能为 **null** / 未覆盖);UC-01 **`addVisitor`** → **`76260531`**。 --- ### 3.3 组织 Feign `listByImageId` ↔ 电梯 HTTP ↔ 真实落库(避免混淆) 这一块名字容易混:**组织侧 Java 方法叫 `listByImageId`,并不等于电梯里另一个 DAO 方法 `listByImageId`。** #### 3.3.1 组织侧调用(入口) | 项 | 说明 | |----|------| | **组织代码** | `ElevatorFeignClient#listByImageId`(`cwos-component-organization-service/.../feign/ElevatorFeignClient.java`) | | **HTTP** | **`POST {elevator-base}/elevator/passRule/image`**(Feign **`name`** 一般为 **`feign.elevator.name`**,如 **`elevator-app`**) | | **请求体** | **`AcsPassRuleImageForm`**:`personId`、`businessId`、`includeOrganizations`、`includeLabels`(对应组织 **`detail`** 里 **`getImgStorePersonResults`** 填好的档案字段) | #### 3.3.2 电梯侧实际执行(与「另一个 listByImageId」区分) | 易混点 | 说明 | |--------|------| | **本链路** | **`AcsPassRuleController`**(类上 **`@RequestMapping("/elevator/passRule")`**)方法 **`@RequestMapping("/image")`** → **`ImageRuleRefServiceImpl#listByPersonInfo`** → **`ImageRuleRefDao#listByPersonInfo`** → **`ImageRuleRefMapper.xml#listByPersonInfo`** | | **勿混** | **`AcsPassRuleServiceImpl#listByImageId`** → **`acsPassRuleDao.listByImageId`** 查的是 **`it_acs_pass_rule`**,按 **`imageStoreIds`** 过滤——**不是**组织 Feign 这条 HTTP 路径当前走的实现。 | #### 3.3.3 `listByPersonInfo` 在查什么(业务语义) - **表**:电梯库 **`image_rule_ref`**(人员与楼层通行规则引用:访客 **`addVisitor`** 写入的也是这张表的语义同类数据)。 - **返回字段**:**`DISTINCT zone_id, zone_name`**(映射 **`AcsPassRuleImageResultDto`**)。 - **查询逻辑(摘要)**: - 至少包含:**`person_id = 被访人`** 且 **`person_delete = 0`** 的规则所对应的楼层; - 若 **`includeOrganizations` / `includeLabels`** 非空,SQL 中另有 **`OR`** 分支,按机构/标签关联规则补充楼层,并排除 **`person_delete = 1`** 等条件下已标记删除的 **`zone_id`**(详见 **`ImageRuleRefMapper.xml`** 全文)。 - **排序**:**`order by CAST(zone_name as signed)`**(按楼层名称可解析的数字排序)。 因此:**组织 `detail` 里的 `floorList`,本质上是「电梯侧已为该被访人(及档案上的机构/标签)开通过的楼层 zoneId 列表」的只读投影**,不是组织库自己存的楼层字段。 #### 3.3.4 组织如何把返回值写进 `detail` 仅当 **`images.getCode()`** 等于 **`00000000`**(成功)时,`ImgPersonServiceImpl#detail` 才遍历 **`images.getData()`**,把每条 **`zoneId`** 依次 **`floorList.add`**,并把 **`zoneName`** 拼成逗号分隔的 **`floorNames`**。 若 Feign **失败**、**超时**、或业务码非 **`00000000`**:**不会进入该分支**,**`floorList` 保持未赋值** → 下游 UC-01 常 **`76260531`**。 #### 3.3.5 与「访客派梯 `addVisitor`」的数据关系(闭环) | 环节 | 数据 | |------|------| | 被访人已在电梯侧挂规则 | **`image_rule_ref`** 中有 **`person_id=被访人`** 等记录 | | 组织 **`detail`** | **`listByImageId`(Feign) → `/passRule/image` → `listByPersonInfo`** 读出 **zone 列表** → **`PersonResult.floorList`** | | 电梯 **`addVisitor` UC-01** | 再次 **`PersonService.detail`**,用同一 **`floorList`** 作为 **`effective`**,再 **写入访客**的 **`image_rule_ref`** 等 | 所以:**`floorList` 不是「组织凭空算的」,而是「电梯规则表里已有被访人楼层」经 **`/passRule/image`** 汇总后的结果**;租户策略若要做「替代」,应在步骤 **§3.2 第 6 步**改 **`floorList`**,而不是再发明一套与 **`image_rule_ref`** 无关的算法(除非产品另行约定)。 --- ### 3.4 时序图 — 组织侧「单人 detail」(含回调电梯) ```mermaid sequenceDiagram autonumber participant Caller as 调用方 / Intelligent participant PC as PersonController participant IMG as ImgPersonServiceImpl participant Zone as ZoneFeignClient participant Veh as VehicleFeignClient participant Elev as ElevatorFeignClient
方法名 listByImageId participant EA as 电梯 AcsPassRuleController
/elevator/passRule/image
→ ImageRuleRefService listByPersonInfo Caller->>PC: POST /component/person/detail PC->>IMG: detail(param, context) IMG->>IMG: selectByPrimaryKey(personId) opt defaultFloor 非空 IMG->>Zone: findZonelist Zone-->>IMG: ZoneResult end IMG->>Veh: getVehicleIds Veh-->>IMG: vehicle ids IMG->>IMG: getImgStorePersonResults → organizationIds, labelIds IMG->>Elev: listByImageId(AcsPassRuleImageForm) Elev->>EA: POST /elevator/passRule/image EA->>EA: listByPersonInfo → SQL image_rule_ref EA-->>Elev: List zoneId/zoneName Elev-->>IMG: CloudwalkResult code=00000000 + list IMG->>IMG: setFloorList / setFloorNames(策略替代:待实现) IMG-->>PC: ImgStorePersonGetResult PC-->>Caller: CloudwalkResult ``` --- ## 4. 电梯侧:`addVisitor` 业务步骤 ### 4.1 对外入口 | 项 | 值 | |----|-----| | HTTP | **`POST /elevator/person/add/visitor`** | | Controller | `cw-elevator-application-web` · **`AcsPersonController#addVisitor`** | | 实现 | **`PersonRuleServiceImpl#addVisitor`** | ### 4.2 与源码一致的执行顺序 **阶段 1 — 被访人详情(必经)** - 组装 **`PersonDetailParam`**:`id = param.getPersonId()`(被访人),**`businessId = context.getCompany().getCompanyId()`**。 - **`personService.detail(detailParam, context)`** → Intelligent **`PersonFeignClient`** → 组织 **`POST /component/person/detail`**。 - 失败或 **`personResult == null`** → 返回失败码(常见 **`76260531`**)。 **阶段 2 — 生效楼层 `effective`(UC 分流)** - **`callerProvidedFloors = !CollectionUtils.isEmpty(param.getFloorIds())`** - **`true`(UC-02)**:**`effective = param.getFloorIds()`**(**不**使用 **`personResult.getFloorList()`** 作为列表来源;**仍已执行阶段 1**,用于保证被访人可查)。 - **`false`(UC-01)**:**`effective = personResult.getFloorList()`**;**空** → **`76260531`**。 **阶段 3 — 再次空集校验** — 防御 **`effective` 仍为空**。 **阶段 4 — 楼栋与图库** - **`zoneService.page`**:查询 **`effective.get(0)`** 对应 **`ZoneResult`**,取 **`parentId`** 作为楼栋。 - **`deviceImageStoreDao.getByBuildingId(parentId)`** → **`imageStoreId`**(后续 **`batchBind` / `updateGroupPersonRef`** 均绑定该图库)。 **阶段 5 — 每层通行规则引用** - 对每个 **`floorId`**:**`imageRuleRefDao.getDefaultByZoneId(floorId)`** 取默认父规则,拼装 **`ImageRuleRefAddDto`**(**`personId = visitorId`**),**`imageRuleRefDao.insertList`**。 **阶段 6 — 图库与分组** - **`ImageStorePersonBindParam`**:`imageStoreId`、`personIds=[visitorId]`、访期 **`begVisitorTime`/`endVisitorTime`**。 - **`imageStorePersonService.batchBind`**(Feign 至 Intelligent/组织图库能力)。 - **`updateGroupPersonRef`** 同步组人员引用。 **异常** — 未捕获的运行异常包装 **`76260530`**;**`batchBind`** 失败透传下游码。 ### 4.3 租户策略语义(与仓库规范一致) 电梯 **不**注入 **`TenantVisitorFloorPolicyDao`**,**不**在 **`addVisitor` 内做 `floorList ∩ allow`**。租户 **`allow_zone_ids` 替代**须落在 **组织 `detail`**(见第 3.2 节「待实现」说明)。 --- ## 5. 时序图 — 电梯 `addVisitor`(UC-01:不传 floorIds) ```mermaid sequenceDiagram autonumber participant C as 调用方 participant API as AcsPersonController participant PR as PersonRuleServiceImpl participant PS as PersonService Intelligent participant Org as 组织 /component/person/detail participant Z as ZoneService participant DIS as DeviceImageStoreDao participant IRR as ImageRuleRefDao participant IS as ImageStorePersonService C->>API: POST /elevator/person/add/visitor(floorIds 空) API->>PR: addVisitor(param, context) PR->>PS: detail(personId, businessId) PS->>Org: Feign POST detail Org-->>PS: PersonResult.floorList PS-->>PR: PersonResult Note over PR: effective = floorList;空则 76260531 PR->>Z: page(首 floorId) Z-->>PR: ZoneResult.parentId PR->>DIS: getByBuildingId(parentId) DIS-->>PR: imageStoreId loop 每个 floorId PR->>IRR: getDefaultByZoneId + insertList end PR->>IS: batchBind(visitorId, 访期, imageStoreId) IS-->>PR: 成功 PR->>IS: updateGroupPersonRef PR-->>API: CloudwalkResult Boolean API-->>C: 结果 ``` ## 6. 时序图 — 电梯 `addVisitor`(UC-02:显式 floorIds) ```mermaid sequenceDiagram autonumber participant C as 调用方 participant API as AcsPersonController participant PR as PersonRuleServiceImpl participant PS as PersonService Intelligent participant Z as ZoneService participant DIS as DeviceImageStoreDao participant IRR as ImageRuleRefDao participant IS as ImageStorePersonService C->>API: POST /elevator/person/add/visitor(floorIds 非空) API->>PR: addVisitor PR->>PS: detail(被访人)(校验被访人存在) PS-->>PR: PersonResult Note over PR: effective = param.floorIds(不用 detail.floorList) PR->>Z: page(首 floorId) Z-->>PR: parentId PR->>DIS: getByBuildingId DIS-->>PR: imageStoreId loop 每层 PR->>IRR: 默认规则 + insertList visitorId end PR->>IS: batchBind + updateGroupPersonRef PR-->>API: success ``` --- ## 7. 活动图(addVisitor 分支汇总) ```mermaid flowchart TD Start([POST /elevator/person/add/visitor]) --> D[PersonService.detail 被访人] D --> E{success 且 PersonResult 非空?} E -- 否 --> E1[76260531 等] E -- 是 --> F{param.floorIds 非空?} F -- 是 UC-02 --> G[effective = floorIds] F -- 否 UC-01 --> H{personResult.floorList 非空?} H -- 否 --> E1 H -- 是 --> G2[effective = floorList] G --> K[zone.page 首层 → imageStoreId] G2 --> K K --> L[逐层 ImageRuleRef 挂 visitorId] L --> M[batchBind + updateGroupPersonRef] M --> Ok([true]) ``` --- ## 8. 主线 B:通行记录落库时「访客身份」认定(非派梯) **场景**:设备上报识别结果写入电梯通行记录。 **实现**:`AcsElevatorRecordServiceImpl#add` 在写库前 **`RestTemplateUtil.post`** → **`http://{ninca-crk-std}/intelligent/three/visitor/record/query`**;返回非空则 **`isVisitor=1`** 并回填被访人。该路径 **不创建** 访客主档,与第 3~7 节派梯链路独立。 ```mermaid sequenceDiagram participant R as AcsElevatorRecordServiceImpl participant HTTP as CRK three/query participant DAO as 电梯记录 DAO R->>HTTP: visitorId + tenant HTTP-->>R: 访客档案或空 R->>DAO: add(含 isVisitor, interviewee) ``` --- ## 9. 其它:MQTT 访客标签 `MqttServiceImpl` 若识别流水 **`personLabelIds` 含 "1"**,MQTT JSON 置 **`isVisitor=true`**(**标签维度**,与档案访客不同)。 --- ## 10. 错误与日志索引(addVisitor) | 场景 | 码 | |------|-----| | detail 失败 / 被访人无数据 / UC-01 **`floorList` 为空** / **`effective` 仍为空** | **`76260531`** | | 其它未预期异常 | **`76260530`** | | **`batchBind`** 失败 | 透传下游 **code/message** | **日志关键字**:`根据被访人添加访客派梯权限`、`UC-01` / `UC-02`、`最终生效楼层`、`访客添加派梯权限`、`远程调用绑定人员图库`。 --- ## 11. 关键源码索引 | 层级 | 路径 | |------|------| | 电梯 Controller | `cw-elevator-application-web/.../person/controller/AcsPersonController.java` | | 电梯派梯 | `cw-elevator-application-service/.../person/impl/PersonRuleServiceImpl.java` **`addVisitor`** | | Intelligent Feign | `intelligent-cwoscomponent-rest/.../person/feign/PersonFeignClient.java`(**`/component/person/detail`**) | | 组织 Controller | `cwos-component-organization-web/.../controller/PersonController.java` **`/detail`** | | 组织 detail 实现 | `cwos-component-organization-service/.../ImgPersonServiceImpl.java` **`detail`** | | 组织→电梯 Feign | `cwos-component-organization-service/.../feign/ElevatorFeignClient.java`(方法 **`listByImageId`** → **`POST /elevator/passRule/image`**) | | 电梯「按人员信息列楼层」 | `cw-elevator-application-web/.../passrule/controller/AcsPassRuleController.java` **`/image`** | | 电梯实现 | `cw-elevator-application-service/.../passrule/impl/ImageRuleRefServiceImpl.java` **`listByPersonInfo`** | | 电梯 SQL | `cw-elevator-application-data/.../mapper/ImageRuleRefMapper.xml` **`listByPersonInfo`**(表 **`image_rule_ref`**) | | (勿与本链路混淆) | `AcsPassRuleServiceImpl#listByImageId` / **`it_acs_pass_rule`** — **不同入口** | | 通行访客打标 | `cw-elevator-application-service/.../record/impl/AcsElevatorRecordServiceImpl.java` **`add`** | --- ## 12. 规范交叉引用 - 租户楼层策略 **替代** 语义与迁移边界:**`docs/superpowers/specs/2026-05-06-tenant-visitor-policy-organization-implementation.md`** --- **说明**:组织 **`detail` 内租户策略替代**若未落地,UC-01 的 **`floorList`** 完全等于 **`/elevator/passRule/image` → `listByPersonInfo`** 返回的 **zone 列表**(经 Feign **`listByImageId`** 写入 **`ImgStorePersonGetResult`**)。与产品「仅开放接待层」不一致时,应排查 **`image_rule_ref` 数据**、该接口 **成功与否**,以及 **组织侧策略替代**是否已实现。