# 对外接口不变:走查任务与状态 > **依据**:[对外接口不变-远程调用与性能优化约定](对外接口不变-远程调用与性能优化约定.md)(§2 总原则、§3 场景、§4 优先级)。 > **走查代码根**:`maven-cw-elevator-application/cw-elevator-application-service`(2026-04-24 静态走查)。 > **说明**:下表「子任务数」指**与约定相关的 RPC/可优化循环次数上界**(随运行时数据规模变化);**状态**表示在**不扩展 Feign/HTTP 契约**前提下是否建议动代码。 --- ## 1. 总览表(子任务量 + 可修正 / 不可修正) | 约定 § | 代码锚点 | 子任务数(上界) | 下一可修正动作(建议顺序) | 不可修正或须前置确认 | |--------|----------|------------------|----------------------------|------------------------| | **§3.1** | `ImageRuleRefServiceImpl#delete`(约 575~598 行) | **`N = param.getIds().size()`** 次 `updateGroupPersonRef`(每规则删后各 1 次) | **P0**:全部 `deleteById` 完成后,对本轮涉及的 `labelIds`、`organizationIds` **去重并集**,调用 **1 次** `updateGroupPersonRef`;为每次 RPC 增加 **`isSuccess` 校验**(与 §2.2 一致) | **须图库/通行确认**:合并调用是否为「刷新引用」语义、是否等价于当前 N 次效果;若不能确认则**不得合并**,仅可补返回值校验与日志(**工作区走查与方案审核见 §6**) | | **§3.2** | `AcsPersonServiceImpl#delete` | **`P = param.getPersonIds().size()`** 次 `imageStorePersonService.delete` | **P1 首轮已实施**(**§8**):`elevatorRemoteBoundedExecutor` 按批 `invokeAll`(默认并发 **6**),子线程 **`FeignThreadLocalUtil.callWithContext`**;遇失败**整批后**即返回 `CloudwalkResult.fail`(与原先顺序循环遇错即停一致,**非**单条失败即取消同批其它在途 RPC) | **无批量 delete**;同批内并行语义见 **§8.1** | | **§3.3** | `AcsPassRuleServiceImpl#listFloor` | **`F = passRuleResults.size()`** 次 `acsPersonService.page` | **P1 首轮已实施**(**§8**):楼层 `page` 有界并行(默认 **6**),`personTotals[]` 按下标写回,**列表顺序不变** | **禁止**本地 count 替代 `totalRows`;RPC 上界仍为 **F** | | **§3.4** | `AcsPassRuleServiceImpl#addImageStore` 内设备绑图库 | **`D = deviceList.size()`** 次 `bindDeviceAndImageStore` | **P1 首轮已实施**(**§8**):`bind` 有界并行;失败仍 **`rollbackImageStoreAfterBindFailure`** 后抛 `ServiceException`(与同批已绑设备竞态与顺序循环**同类**) | **无批量 bind** | | **§3.5** | `AcsDeviceTaskServiceImpl#updateFloors`(约 46~119 行) | 增:**`A = addFloors.size()`** 次 `personRuleService.add` 或 `imageRuleRefService.addOnlyRule`;删:**`D = delFloorIds.size()`** 次 `personRuleService.delete` / `imageRuleRefService.delete` / DAO | **P1**:删层在 §3.1 落地后可减少「内层 refresh」放大;可对**楼层维度**做有界并行(与限流/异步线程池策略一致) | **`@Async("updateFloorsExecutor")`** 下线程池与背压须单独评估;错误现为 `throw new ServiceException(e.getMessage())` 信息较粗,是否属「接口不变」范畴由产品/运维定义 | --- ## 2. 数量小结(便于排期) | 类型 | 计数符号 | 含义 | |------|----------|------| | **§3.1 可合并 RPC** | 由 N 降为 **1**(在语义确认后) | 规则批量删除场景收益最大 | | **§3.2 并行度** | P | 人员多 ID 删除 | | **§3.3 并行度** | F | 楼层列表人数统计 | | **§3.4 并行度** | D | 设备绑图库 | | **§3.5** | A + D | 异步任务按层调用 | --- ## 3. 迭代规划与下一迭代范围(不改 HTTP/Feign 签名) ### 冻结范围(2026-04-25) | 项 | 说明 | |----|------| | **约定 §3.1 全范围** | 未取得图库/通行对 `updateGroupPersonRef` 语义确认前,**不进行**与该约定相关的**任何**代码修正与优化(含 **合并 N→1** 及 §6.4 所述**仅 `isSuccess` 校验**小步),避免在无确认期分散实现与回滚成本。 | | **恢复条件** | 图库书面或接口说明确认 + 在 [约定文档 §3.1](对外接口不变-远程调用与性能优化约定.md) 文末回填对接人、日期;再按 §1 表拆分 PR(合并与返回值校验可分步)。 | ### 迭代 3(**§3.5 `updateFloors`**) | 字段 | 内容 | |------|------| | **状态** | **走查与首轮修正已完成**(见 **§7**):`getById` 空防护、步骤级 `CloudwalkResult` 校验、`keepAliveSeconds` 绑定线程池。 | | **约定锚点** | **§3.5** `AcsDeviceTaskServiceImpl#updateFloors` | | **暂缓项** | 楼层有界并行、`AbortPolicy` 与 `catch` 语义、删楼 `ruleMap` 缺键等见 **§7.3**。 | ### 迭代 4(**P1:§3.2 / §3.3 / §3.4 + 统一有界池**) | 字段 | 内容 | |------|------| | **状态** | **已实施**(实现说明与语义边界见 **§8**)。 | | **线程池 Bean** | `elevatorRemoteBoundedExecutor`(`ElevatorRemoteIoExecutorConfig`),配置前缀 **`ninca.elevator.remote-io.pool`**(默认 core=max=**6**,queue=512,`CallerRunsPolicy`)。**未**与 `updateFloorsExecutor` 合并,避免异步任务与同步 RPC 抢同池。 | | **公共能力** | `FeignThreadLocalUtil.callWithContext`(`cw-elevator-application-common`):子线程执行 Feign 前绑定/恢复 ThreadLocal 请求头。 | ### 迭代 5 及以后(可选深化) - **§3.5**:`updateFloors` 内楼层循环有界并行(仍受 §3.1 内层放大约束)。 - **调参 / 观测**:`ninca.elevator.remote-io.pool` 按环境压测调整;必要时为并行批增加指标日志。 **已完成回顾**:迭代 1 — **§5**;迭代 2 — **§6**(§3.1 冻结);迭代 3 — **§7**;迭代 4 — **§8**。 --- ## 4. 文档维护 | 项目 | 内容 | |------|------| | 更新触发 | `ImageRuleRefServiceImpl#delete`、`AcsPersonServiceImpl#delete`、`AcsPassRuleServiceImpl#listFloor` / `#addImageStore`、`AcsDeviceTaskServiceImpl#updateFloors` 任一处重构或签契约变更 | | 结论回填 | 图库对 §3.1 的确认结论请写回 [约定文档 §3.1](对外接口不变-远程调用与性能优化约定.md) 文末建议行(对接人 + 日期) | --- ## 5. 迭代 1:`AcsPassRuleServiceImpl#listFloor` 走查结论(§3.3 / §2.2) **走查日期**:2026-04-25 **代码位置**:`maven-cw-elevator-application/.../passrule/impl/AcsPassRuleServiceImpl.java` 方法 `listFloor`。 | 检查项 | 现状 | 结论 | |--------|------|------| | `zoneService.tree` 返回值 | 已校验 `zoneTree.isSuccess()`,失败抛 `ServiceException` | **通过** | | `acsPersonService.page` 返回值 | 循环内**未**校验 `page.isSuccess()`,直接 `page.getData()` 取 `totalRows` | **不通过**:违反约定 **§2.2**;Feign 失败时 `getData()` 可能为 null,存在 **NPE** 风险,且可能把失败误当「0 人」 | | `page.getData()` 空指针 | 未防护 | **不通过**:与上项合并修正 | | `rowsOfPage` | 当前为 `10`,仅使用 `totalRows` | **建议**:改为 **`1`**(约定 §3.3:仅取总数,略减负载),**不改变** HTTP 响应字段 | **评估结论(是否允许进入代码修正)**:**通过进入修正** — 仅补充与 `zoneTree` 分支一致的失败处理及空数据防护,**不**改变对外 JSON 字段语义;`rowsOfPage=1` 与现逻辑(只读 `totalRows`)等价。 **代码修正(已应用,2026-04-25)**:`AcsPassRuleServiceImpl#listFloor` — 增加 `page.isSuccess()` 失败抛错、`page.getData()` 为空时 `personNumber=0`、`CloudwalkPageInfo(1,1)` 仅取总数。 **修正实施后**:提交 **`e652eb3`**(分支 `v0.11`)。 --- ## 6. 迭代 2:`ImageRuleRefServiceImpl#delete` 与 §3.1 方案走查(仅评估,未改代码) **走查日期**:2026-04-25 **目标**:在全工作区定位**图库(intelligent 图库人员服务)**与**通行规则(电梯应用本地)**相关代码,审核「合并 `updateGroupPersonRef`」方案是否需图库侧语义确认后方可实施。 ### 6.1 图库 / 通行相关代码位置(工作区) | 层级 | 路径/符号 | 职责 | |------|-----------|------| | **Feign 契约** | `maven-intelligent-cwoscomponent/intelligent-cwoscomponent-rest/.../feign/ImageStorePersonFeignClient.java` | `POST .../updateGroupPersonRef`,请求体 `UpdateGroupPersonRefParam` | | **DTO** | `maven-intelligent-cwoscomponent/intelligent-cwoscomponent-interface/.../param/UpdateGroupPersonRefParam.java` | `businessId`、`imageStoreId`、`personIds`、`labelIds`、`organizationIds`(可同时只填部分字段) | | **客户端封装** | `.../service/RestImageStorePersonServiceImpl.java` | 透传 Feign | | **电梯侧 §3.1 锚点** | `maven-cw-elevator-application/.../passrule/impl/ImageRuleRefServiceImpl.java` 方法 **`delete`**(约 571~605 行) | 按 `param.getIds()` 循环:`listByParentRule` → 收集子规则 `includeLabels` / `includeOrganizations` → **`deleteById`** → **`imageStorePersonService.updateGroupPersonRef`**(每删一条父规则 1 次 RPC) | | **同文件其它 `updateGroupPersonRef`** | `addOnlyRule`(约 434~439)、`update` 内分支(约 556~562) | 新增/编辑规则后刷新;**同样未校验** `CloudwalkResult.isSuccess()` | | **人员通行规则** | `maven-cw-elevator-application/.../person/impl/PersonRuleServiceImpl.java` | `add` / `addVisitor` / `delete` 末尾各 1 次 `updateGroupPersonRef`,入参以 **`personIds`** 为主(与 `delete` 以 **label/org** 为主不同) | | **HTTP 入口** | `cw-elevator-application-web/.../AcsPassRuleController.java` | `imageRuleRefService.delete` | | **异步任务调用** | `AcsDeviceTaskServiceImpl#updateFloors` | 删楼层时多构造 `deleteParam.setIds(Collections.singletonList(ruleId))` **逐层**调 `imageRuleRefService.delete`;单次 `delete` 内仍可能 1 次或多次 `updateGroupPersonRef`(视该层 `ids` 数量) | **说明**:本仓库**无** intelligent 图库服务端的 `updateGroupPersonRef` 业务实现源码,仅能依据 DTO 与调用方推断语义;**与图库/通行团队确认**仍属 §3.1 前置条件。 ### 6.2 当前 `delete` 行为摘要(与合并相关) - `imageStoreId` 来自 **`deviceImageStoreDao.getByBuildingId(param.getParentId())`**,整次批量删除共用同一图库。 - 对每个待删父规则 `id`:先读**该父规则下子规则**的 label/org(子行若带 label 则 `continue`,**同一子行不会同时写入 org**,与数据模型一致),再删父规则,再带着**本轮** `includeLabels` / `includeOrganizations` 调图库刷新。 - **`updateGroupPersonRef` 的返回值未做 `isSuccess` 校验**(违反约定 §2.2;与 `getImageStorePerson` 等分支不一致)。 ### 6.3 约定中的「合并」方案审核 | 维度 | 结论 | |------|------| | **与现网 N 次调用的等价性** | 若图库侧语义为:在**给定 `imageStoreId`** 下,对传入的 **labelIds / organizationIds 集合**做**增量刷新或按维度重算引用**(各维度独立、与顺序无关),则「删库前汇总所有待删父规则的子维度 → **去重并集** → 删库完成后 **1 次** `updateGroupPersonRef`」与「每删一条父规则刷新其维度子集」在**最终一致**上通常等价。 | | **必须向图库确认的风险** | 若远端实现为「以本次入参**覆盖/裁剪**图库可见范围」或依赖**调用顺序**产生副作用,则合并后的**单次并集**与 N 次**子集递进**可能不等价。约定文档 §3.1 所述「非破坏性刷新」即针对此。 | | **`personIds` 与 `labelIds`/`organizationIds` 混用** | `PersonRuleServiceImpl` 走 `personIds` 路径;`ImageRuleRefServiceImpl#delete` 走 label/org。合并方案**不改变** `delete` 仅设 label/org 的现状;但若图库服务在**未传 `personIds`** 时对空列表有特殊含义,仍须一并确认。 | | **空列表仍 RPC** | 当前循环在子规则为空时仍调用 `updateGroupPersonRef`(两列表皆空)。合并后是否**跳过空并集**可减少无效 RPC,但属于**行为微调**,若图库依赖「空刷」触发全量重算,须图库确认后再定。 | | **不合并时的安全增量** | 在未获图库书面确认前,**仅**可为每次 `updateGroupPersonRef` 增加 **`isSuccess` 校验 + 失败抛 `ServiceException`**(及可选日志),**不改变** RPC 次数;与约定「退化为循环调用 + 返回值校验」一致。 | ### 6.4 评估结论(是否允许进入 §3.1「合并」类代码修正) - **合并 N→1**:**不允许在图库/通行确认前实施** — 与 §1 表及约定 §3.1 前置条件一致。 - **仅返回值校验(及可选空并集跳过,若产品同意)**:**允许作为独立小步** — 不依赖远端语义新假设,符合 §2.2。 **图库确认建议提问(可复制)**:「对同一 `imageStoreId`,`updateGroupPersonRef` 在仅设置 `labelIds`、`organizationIds`(`personIds` 为空)时,是否为**按这些维度刷新人员引用**且**不会**将图库维度裁剪为仅等于本次入参?多次调用子集与单次调用**并集**是否在业务上等价?」 **实施后回填**:确认结论、对接人、日期写入 [约定文档 §3.1 文末](对外接口不变-远程调用与性能优化约定.md)(见约定 §5)。 **排期决策(2026-04-25)**:在取得图库确认前,**冻结**约定 **§3.1** 相关全部代码变更;下一迭代转 **§3.5**(见上文 **「迭代 3」**)。 --- ## 7. 迭代 3:`AcsDeviceTaskServiceImpl#updateFloors` 走查结论(§3.5) **走查日期**:2026-04-25 **代码位置**:`cw-elevator-application-service/.../device/impl/AcsDeviceTaskServiceImpl.java` 方法 `updateFloors`;线程池 `.../common/UpdateFloorsTaskExecutor.java`;配置 `UpdateFloorsPoolProperties`(`ninca.update.floor.pool.*`,默认 core=3、max=5、queue=100、`AbortPolicy`)。 ### 7.1 调用链与 RPC 上界(与 §1 表对齐) | 分支 | 行为 | 上界 | |------|------|------| | 增楼层 | `personRuleService.add` **或** `imageRuleRefService.addOnlyRule`,成功后 `updateBingDevices` | `addFloors.size()` | | 删楼层 | `personRuleService.delete` **或** `imageRuleRefService.delete`(单 id)**或** 仅 DAO `deleteByOrgAndLabel`,成功后 `updateBingDevices` | `delFloorIds.size()` | | 内层放大 | `imageRuleRefService.delete` 仍受约定 **§3.1** 冻结影响(`updateGroupPersonRef` 多次);本迭代**未**改该内层。 | 不变 | **入口**:`AcsElevatorDeviceServiceImpl#bindingFloors` / `#bindingPerson` 在插入任务行后**同步**调用 `updateFloors`;方法带 `@Async`,实际在 **`updateFloorsExecutor`** 线程执行;HTTP 已返回 `taskId` 后,**异步内失败不会回写该 HTTP 响应**(现网行为保持;运维依赖任务进度与日志)。 ### 7.2 检查项与结论 | 检查项 | 现状(走查时) | 结论 | |--------|----------------|------| | `acsDeviceTaskDao.getById` | 未判空即 `task.getIsStop()`,存在 **NPE** 风险(数据异常或竞态) | **不通过** → 已修正:空则记录并 `ServiceException` | | `personRuleService.add/delete`、`imageRuleRefService.addOnlyRule/delete` 返回值 | 未校验 `CloudwalkResult.isSuccess()`,失败时仍 **`updateBingDevices`**,进度与真实绑定不一致 | **不通过**(违反 §2.2)→ 已修正:统一 `requireTaskStepSuccess`,失败抛错且**不**递增 | | `catch` 仅 `ServiceException(e.getMessage())` | 丢失根因类型与栈信息到调用方;异步场景仅日志含 `{}` 与异常 | **记录**:是否改为 `ServiceException(code, msg)` 或 `initCause` 属产品/运维范围,**本轮不改** | | 线程池 `keepAliveSeconds` | `UpdateFloorsPoolProperties` 有字段,**Bean 未 `setKeepAliveSeconds`**,配置项无效 | **缺陷** → 已在 `UpdateFloorsTaskExecutor` 绑定 | | `RejectedExecutionHandler` | `AbortPolicy`,队列满时拒绝提交 | **记录**:与背压策略相关,**本轮不改**(须与运维对齐) | | 删楼 `ruleMap.get(delFloorId)` | 若 `listZoneInfoByIds` 未覆盖某 `delFloorId` 可能 **null** 拼接 `ruleName` | **记录**:数据正常时风险低;**本轮未改**(可后续与 DAO 对齐) | ### 7.3 评估结论(是否允许进入代码修正) - **允许并已实施(本轮)**:`task` 空指针防护;对 **`personRuleService.add` / `delete`**、**`imageRuleRefService.addOnlyRule` / `delete`** 的 **`CloudwalkResult` 成功校验**(约定 §2.2);`updateFloorsExecutor` 绑定 **`keepAliveSeconds`**。 - **暂缓(须单独评审)**:按楼层 **有界并行**、拒绝策略、`catch` 异常语义增强、`ruleMap` 缺键防护。 **修正实施后**:提交 **`0ddeedc`**(分支 `v0.11`)。 --- ## 8. 迭代 4:P1 有界并行(§3.2 / §3.3 / §3.4) **实施日期**:2026-04-25 ### 8.1 行为与约定对齐说明 | 项 | 说明 | |----|------| | **并发度** | 代码常量与默认池 **`corePoolSize=maxPoolSize=6`**(约定 4~8 区间内),可通过 **`ninca.elevator.remote-io.pool.core-pool-size` / `max-pool-size`** 覆盖。 | | **§3.2 `delete`** | 多 `personId` 时按批 `ThreadPoolExecutor.invokeAll`;单 ID 仍走主线程(无 Feign 子线程问题)。失败时返回 **`76260407`** 风格 `CloudwalkResult.fail`,与改造前**一致**;**同批内**若某 RPC 失败,`invokeAll` 仍会等本批其它任务结束后再统一 `get()` 抛出/返回,与**严格单线程「第一条失败即不再发起后续」**在「已发起请求数」上略有差异,属典型有界并行取舍。 | | **§3.3 `listFloor`** | 设备数仍顺序 DAO;`acsPersonService.page` 按批并行,结果写入 `personTotals[idx]` 后顺序 `setPersonNumber`,**响应楼层顺序不变**。 | | **§3.4 `addImageStore`** | `bindAppImageStoreDevice` 仍顺序执行;仅 **`bindDeviceAndImageStore`** 按批并行;任一批次中失败则 **`rollbackImageStoreAfterBindFailure`**(抽方法)后抛 `ServiceException`,与原先 try/catch 回滚路径一致。 | | **Feign ThreadLocal** | 所有子线程 RPC 经 **`FeignThreadLocalUtil.callWithContext`**,避免池化线程串请求头。 | **实施后提交**:**`fe571aa`**(分支 `v0.11`)。