Files
starRiverProperty/docs/superpowers/specs/2026-05-06-tenant-visitor-policy-organization-implementation.md
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

37 KiB
Raw Permalink Blame History

租户访客默认楼层策略 — 迁入组织组件(组织侧唯一实现,电梯侧移除)

日期2026-05-06 状态:实施前梳理 / 待评审 核心原则:策略逻辑只在 maven-ninca-common-component-organization 实现;maven-cw-elevator-application 完全移除策略相关代码。

业务约定(楼层清单):凡 调用方 / 前端 / BFF 需消费 「被访人可派梯 / 可邀约访问的楼层集合」PersonResult.floorList 语义),必须走 intelligent PersonService.detailPersonResult.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与后端实现对齐用语;不引入新接口,楼层清单仍来自既有契约中的字段(如 floorListfloorNamesfloorInfoList 等)。

1)访客邀请初始化页面需要「可访问楼层清单」

说明
业务诉求 访客邀约/登记页在提交前,需要展示 被访人侧允许访客选择的可达楼层(或默认勾选逻辑所依赖的楼层集合),以便用户勾选或与派梯入口对齐。
数据来源(规定主路径) 必须通过 PersonService.detail 获取 PersonResult.floorList / 相关展示字段被访人 personId + businessId → intelligent PersonService.detailFeign 不变)→ 组织 ImgPersonServiceImpl#detail 内聚 listByImageId + 租户策略替代 后写入 floorList(见 §1「改造后」与 §4)。禁止listByPage 作为邀约页 floorList 的规范来源
与派梯一致 电梯 addVisitor UC-01(未传 floorIds)以同一 PersonResult.floorList 为候选楼层;邀约页若使用该清单,可与 UC-01 同源,减少「页面选的层 ≠ 后台派的层」。
非本路径 邀约页若调人员分页、不调详情,则可能拿到 另一套 楼层展示(含星河湾 40F/6F 等),与 detail 不一定一致——见下文 3 与 §4.0。

2detail 流程的作用

说明
定位 单人维度的被访人详情:组织侧 ImgPersonServiceImpl#detail(对外经 /component/person/detail既有入口,契约不变)。
与楼层相关输出 listByImageId 成功时组装 floorListzoneId 列表)与 floorNames;改造后在此链路插入 租户访客策略替代(命中则 floorList 以策略 allow_zone_ids 为准,见 §4.2)。
明确不包含 不包含星河湾分页里的 40F / 6F 默认覆盖(现网即如此);邀约页若只依赖 detail,则 不会从该接口拿到 XHW 那套默认楼层。
典型调用方 访客邀请初始化、被访人卡片、电梯 addVisitor 阶段 1PersonResult 等——凡需要 「这一位被访人当前可用的楼层清单」 的场景,应以 detail 为主数据源(在无不新增接口前提下)。

3listByPage 流程的作用

说明
定位 人员列表分页:组织侧 ImgPersonServiceImpl#listByPage,面向 批量行 展示;可选参数 isVisitor 非空 时进入访客列表增强分支(见 §3.2)。
与楼层相关输出 行为 listByImageIdfloorInfoList,再结合 OrgFloorMapperxhwId / xhwDefaultFloorId / xhwSixFloorId 等写入 默认选中楼层、跨日标记 等(星河湾 40F / 6F 出现在此分支,见 §4.0)。
与邀约初始化的关系 适用于 访客名单列表、运营筛选 等「一行一人摘要」场景;不是邀约页「为主访人拉可选楼层清单」的首选数据源——除非产品设计明确要求列表与邀约共用同一套展示逻辑,并接受与 detail 的差异或另行对齐(§7 测试项)。
改造后 策略命中时应在 L331332 插入点优先替代并 跳过原星河湾块(§4.3),避免与租户策略双重主编。

小结访客邀请初始化与 UC-01 派梯,均以 PersonService.detail 返回的 PersonResult.floorList 为权威楼层清单(组织侧在 ImgPersonServiceImpl#detail 内完成策略替代);listByPage(isVisitor) 仅服务访客名单列表与项目定制默认层,不作为邀约页 floorList 的主数据路径。详情与分页并存时的差异见 §4.0。


访客邀约:完整业务流程图与代码流程图(改造后目标)

以下图表与仓库约定一致:主数据访客登记可能在第三方/BFF电梯 addVisitor 仅负责「已有访客人员 ID 后的派梯授权」;策略在组织库替代语义写入 detailfloorList

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.detailPersonResult.floorList 拉楼层清单(组织侧 ImgPersonServiceImpl#detail 实现);禁止listByPage(isVisitor) 替代 A3 作为 floorList 规范来源(见场景说明 3)。

B. 代码视角 — 邀约初始化:拉「可访问楼层清单」(唯一规范:PersonService.detailfloorList

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 ∩ allowUC-01 完全信任 组织侧写入的 floorList

D. 代码视角 — 访客名单分页:listByPageisVisitor 非空)

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#detaillistByImageId 后,策略命中则以 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 / 请求
effectiveUC-01 曾再度与 allow effective = personResult.floorList(组织已替代,电梯不二次运算)
effectiveUC-02 曾与 allow (若走策略分支) effective = param.getFloorIds(显式楼层;规范上不由电梯做 ∩)

2. 业务语义

2.1 UC-01 与 UC-02:业务场景说明(电梯「访客派梯授权」接口)

以下针对 POST /elevator/person/add/visitorPersonRuleServiceImpl#addVisitor)中 floorIds 是否由调用方传入 的两种业务模式;与 访客邀约页拉楼层清单(依赖 PersonService.detail)的关系见 §2.3

代号 接口条件(代码侧) 业务含义 典型场景举例
UC-01 请求体 未传 floorIds,或传 空列表(以最终实现判定为准) 由系统依据被访人维度推导要开通的楼层:调用方不显式指定「开哪几层」,派梯授权使用的楼层集合应与 被访人详情中的可达楼层清单一致(经租户策略在组织侧写入 PersonResult.floorList)。 ① 登记完成后 一键开通派梯,前台未做逐层勾选;② BFF 只带 personId/visitorId/访期,楼层完全跟被访人档案 + 租户策略走;③ 与 访客邀约初始化页使用同一 PersonService.detailfloorList 对齐 UC-01,避免「页面以为的层」与「后台开通的层」不一致。
UC-02 请求体 floorIds 非空 由调用方明确指定要开通的楼层(zoneId 列表):业务上多为用户或上游系统 已选定具体楼层,派梯接口按 显式列表 写入通行规则,而 不以 PersonResult.floorList 作为开通列表来源。 ① 接待岗在终端 勾选 7F、8F 后提交派梯;② 第三方访客系统 合同只允许指定楼层;③ 邀约页提交时 把用户勾选的楼层原样 传给派梯(此时开通列表以请求为准,不等于邀约页展示用的 detail.floorList 必须相同,取决于产品设计)。

一句话对照UC-01 =「开通哪些层」交给系统(跟被访人 detail + 策略后的 floorListUC-02 =「开通哪些层」由调用方在请求里写死(floorIds)

与邀约页的关系:邀约页展示 可选楼层规定PersonService.detail(§「业务约定」);用户若在邀约或后续页面 勾选了具体楼层 再派梯,对接侧通常走 UC-02;若 未勾选、由后台直接派梯,则多为 UC-01


2.2 唯一规范语义:替代(Replacement);禁止「求交」作为策略定义

禁止(历史·电梯侧 ∩) 唯一规范(组织 detail·替代)
逻辑 candidateallow_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.detailfloorList(§「业务约定」)。
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
}
// 无 TenantVisitorFloorPolicyDaoeffective 直接用于后续派梯

避免误读:上文「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 之前(约 L331332 访客分支内逐行处理 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_idsJSON Array → List<String>
    public List<String> getAllowZoneIds(List<String> orgIds);
}

4. 与星河湾并存:适用范围、优先级与伪代码

4.0 现网代码事实(走查结论,避免误实现)

方法 星河湾 40F / 6FxhwDefaultFloorId / xhwSixFloorId 租户访客策略(拟插入)
ImgPersonServiceImpl#detail 未实现:仅 listByImageIdfloorList / floorNames(见 §3.1 仅在 floorList 组装处P1 策略替代不要在本方法内照搬 listByPage 的 XHW 分支,除非产品明确要求「详情与访客分页默认完全一致」(属行为变更,需单独评审)。
ImgPersonServiceImpl#listByPageparam.getIsVisitor() 非空时) 已实现:在 OrgFloorMapper.listByOrgIds 非空且 isAcrossDay=0 时,按是否含 xhwId 覆盖 defaultChooseFloorfloorInfoList40F vs 6F)(见 §3.2 L331332 之间插入:若 P1 策略命中,则用策略结果替换 floorInfoList / 默认楼层并 跳过 原 XHW 块(L332–357);若未命中则保持现有星河湾逻辑。

详情 vs 分页的一致性:改造前已存在「detail 无 XHW、分页访客分支有 XHW」差异;若在 detail 仅加 策略替代、在 listByPage策略优先于 XHW,可能出现「同一被访人在详情 floorList 与分页默认展示仍不完全一致」。若需对齐,应在产品层明确是否要给 detail 增加与分页相同的默认楼层规则(接口字段不变,仅内部赋值变化)。

4.1 优先级总表(与现网 XHW 不冲突的前提)

优先级 条件 行为
P1 租户访客策略命中且启用 替代:① PersonService.detailfloorList = 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.2P1/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 修改阶段4effective 改为 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.detailPersonResult.floorList 获取权威清单(未用分页或其它接口顶替)
  • 有策略租户detail 返回的 floorList = allow_zone_ids(纯替代,非交集)
  • 有策略 + addVisitoreffective = floorList(不含交集过滤)
  • 无策略 + 星河湾:行为与现网 40F/6F 一致
  • 有策略 + 星河湾同时配置:策略优先,不触发 xhw 覆盖
  • 无策略时 detail vs 分页:确认是否接受「detail 仍无 XHW、分页访客分支仍有 40F/6F」的现网差异;若产品要求一致,需另立 story(见 §4.0)
  • UC-02(派梯接口):请求体传 floorIdseffective 以请求为准(与邀约页「必须 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#detaillistByPage 访客分支(§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)与 listByPageP1 跳过星河湾);补充现网 XHW 适用范围表;修正重复「## 4」章节编号(组织清单改为 §5,电梯 §6,顺延);标注 detail/分页一致性风险
0.6 2026-05-06 增加:文首「场景说明」— 访客邀请初始化与楼层清单、detail / listByPage 职责划分及与 UC-01 对齐说明
0.7 2026-05-06 增加:访客邀约端到端业务流图;代码侧 sequencedetail+策略)、flowchartaddVisitor 改造后、listByPage isVisitor);修正 §1 笔误「侧略」→「策略」
0.8 2026-05-06 明确:业务楼层清单 必须PersonService.detailPersonResult.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 的引用