mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
7b2bd307f1
- 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.
560 lines
37 KiB
Markdown
560 lines
37 KiB
Markdown
# 租户访客默认楼层策略 — 迁入组织组件(组织侧唯一实现,电梯侧移除)
|
||
|
||
**日期**: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. 业务视角 — 端到端(访客邀约 + 可选派梯)
|
||
|
||
```mermaid
|
||
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`**)
|
||
|
||
```mermaid
|
||
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`(电梯,改造后)
|
||
|
||
```mermaid
|
||
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` 非空)
|
||
|
||
```mermaid
|
||
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`、**无** ∩)如下:
|
||
|
||
```java
|
||
// 改造后 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 接口定义
|
||
|
||
```java
|
||
// 新建: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)
|
||
|
||
```java
|
||
// 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)
|
||
|
||
```java
|
||
// 在 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 精简代码
|
||
|
||
```java
|
||
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.java` L211-230, L297-353
|
||
- 星河湾分支:`ImgPersonServiceImpl.java` L149-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 的引用 |
|