elevator(listFloor): validate acsPersonService.page result per §2.2

- Throw ServiceException when page RPC fails (align with zoneTree branch)
- Guard null page data; rowsOfPage=1 for totalRows-only reads
- Document walkthrough sign-off and status in 走查任务与状态 §5

Made-with: Cursor

Former-commit-id: e652eb3ea8
This commit is contained in:
反编译工作区
2026-04-25 08:16:59 +08:00
parent 0c61976ade
commit 1d31147e47
2 changed files with 40 additions and 5 deletions
@@ -12,7 +12,7 @@
|--------|----------|------------------|----------------------------|------------------------| |--------|----------|------------------|----------------------------|------------------------|
| **§3.1** | `ImageRuleRefServiceImpl#delete`(约 575598 行) | **`N = param.getIds().size()`** 次 `updateGroupPersonRef`(每规则删后各 1 次) | **P0**:全部 `deleteById` 完成后,对本轮涉及的 `labelIds``organizationIds` **去重并集**,调用 **1 次** `updateGroupPersonRef`;为每次 RPC 增加 **`isSuccess` 校验**(与 §2.2 一致) | **须图库/通行确认**:合并调用是否为「刷新引用」语义、是否等价于当前 N 次效果;若不能确认则**不得合并**,仅可补返回值校验与日志 | | **§3.1** | `ImageRuleRefServiceImpl#delete`(约 575598 行) | **`N = param.getIds().size()`** 次 `updateGroupPersonRef`(每规则删后各 1 次) | **P0**:全部 `deleteById` 完成后,对本轮涉及的 `labelIds``organizationIds` **去重并集**,调用 **1 次** `updateGroupPersonRef`;为每次 RPC 增加 **`isSuccess` 校验**(与 §2.2 一致) | **须图库/通行确认**:合并调用是否为「刷新引用」语义、是否等价于当前 N 次效果;若不能确认则**不得合并**,仅可补返回值校验与日志 |
| **§3.2** | `AcsPersonServiceImpl#delete`(约 165175 行) | **`P = param.getPersonIds().size()`** 次 `imageStorePersonService.delete` | **P1**:**有界并行**(并发度 4~8)调用 `delete`,失败聚合与现网「遇错即停」一致;已具备 `isSuccess` 分支,保持语义 | **无批量 delete**:无法改为单次 RPC,除非将来扩展契约(约定 §4 远期) | | **§3.2** | `AcsPersonServiceImpl#delete`(约 165175 行) | **`P = param.getPersonIds().size()`** 次 `imageStorePersonService.delete` | **P1**:**有界并行**(并发度 4~8)调用 `delete`,失败聚合与现网「遇错即停」一致;已具备 `isSuccess` 分支,保持语义 | **无批量 delete**:无法改为单次 RPC,除非将来扩展契约(约定 §4 远期) |
| **§3.3** | `AcsPassRuleServiceImpl#listFloor`(约 109121 行) | **`F = passRuleResults.size()`** 次 `acsPersonService.page`(仅取 `totalRows` | **P1**按楼层 **有界并行** `page`(如 `rowsOfPage=1`);**合并结果顺序**与树遍历一致;**立即修正**:循环内对 **`page.isSuccess()`** 校验(当前缺失,违反 §2.2 | **禁止**用本地 `countPersonIdByZoneId` 等替代 `page.totalRows`(与 `PersonRuleServiceImpl#page` 路径过滤**不等价**,见约定 §3.3**无多 zone 一次统计 API** 时无法单 RPC 消除 N | | **§3.3** | `AcsPassRuleServiceImpl#listFloor` | **`F = passRuleResults.size()`** 次 `acsPersonService.page`(仅取 `totalRows` | **首轮已完成**`page.isSuccess()``getData()` 空防护、`rowsOfPage=1`。**P1 后续**:按楼层 **有界并行** `page`(保持结果顺序 | **禁止**用本地 `countPersonIdByZoneId` 等替代 `page.totalRows`**无多 zone 一次统计 API** 时无法单 RPC 消除 N |
| **§3.4** | `AcsPassRuleServiceImpl#addImageStore`(约 195201 行) | **`D = deviceList.size()`** 次 `bindDeviceAndImageStore` | **P1****有界并行** `bind`;并行失败时与现有 **回滚删图库** 逻辑一致(注意竞态与顺序) | **无批量 bind**:不能合并为 1 次 RPC(契约不扩展时) | | **§3.4** | `AcsPassRuleServiceImpl#addImageStore`(约 195201 行) | **`D = deviceList.size()`** 次 `bindDeviceAndImageStore` | **P1****有界并行** `bind`;并行失败时与现有 **回滚删图库** 逻辑一致(注意竞态与顺序) | **无批量 bind**:不能合并为 1 次 RPC(契约不扩展时) |
| **§3.5** | `AcsDeviceTaskServiceImpl#updateFloors`(约 46119 行) | 增:**`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())` 信息较粗,是否属「接口不变」范畴由产品/运维定义 | | **§3.5** | `AcsDeviceTaskServiceImpl#updateFloors`(约 46119 行) | 增:**`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())` 信息较粗,是否属「接口不变」范畴由产品/运维定义 |
@@ -32,9 +32,9 @@
## 3. 建议下一迭代(不改 HTTP/Feign 签名) ## 3. 建议下一迭代(不改 HTTP/Feign 签名)
1. **小改快赢**`AcsPassRuleServiceImpl#listFloor` 增加 **`zoneTree` / `page``isSuccess` 与空数据防护**(满足 §2.2,避免失败当 0)。 1. ~~**小改快赢**~~`AcsPassRuleServiceImpl#listFloor` 增加 **`page``isSuccess` 与空数据防护**`rowsOfPage=1`(见 **§5**)。
2. **P0 阻塞项**:与图库团队确认 **`updateGroupPersonRef`** 在「多 label/org 一次传入」下的语义 → 通过后实施 **§3.1 合并**。 2. **P0 阻塞项**:与图库团队确认 **`updateGroupPersonRef`** 在「多 label/org 一次传入」下的语义 → 通过后实施 **§3.1 合并**。
3. **P1 工程项**:为 §3.2 / §3.3 / §3.4 引入**统一有界线程池**(或复用现有异步池策略),并在 PR 中写明失败策略与超时。 3. **P1 工程项**:为 §3.2 / §3.3(并行 `page`/ §3.4 引入**统一有界线程池**(或复用现有异步池策略),并在 PR 中写明失败策略与超时。
--- ---
@@ -44,3 +44,29 @@
|------|------| |------|------|
| 更新触发 | `ImageRuleRefServiceImpl#delete``AcsPersonServiceImpl#delete``AcsPassRuleServiceImpl#listFloor` / `#addImageStore``AcsDeviceTaskServiceImpl#updateFloors` 任一处重构或签契约变更 | | 更新触发 | `ImageRuleRefServiceImpl#delete``AcsPersonServiceImpl#delete``AcsPassRuleServiceImpl#listFloor` / `#addImageStore``AcsDeviceTaskServiceImpl#updateFloors` 任一处重构或签契约变更 |
| 结论回填 | 图库对 §3.1 的确认结论请写回 [约定文档 §3.1](对外接口不变-远程调用与性能优化约定.md) 文末建议行(对接人 + 日期) | | 结论回填 | 图库对 §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)` 仅取总数。
**修正实施后**:提交 **`251fc86`**(分支 `v0.11`)。
---
## 6. 迭代 2 起(占位)
- **§3.1** 待图库确认后再改代码;确认前不在此文档标记为「已实施」。
@@ -114,10 +114,19 @@ public class AcsPassRuleServiceImpl extends AbstractAcsPassService implements Ac
passRuleResult.setDeviceNumber(Integer.valueOf(deviceList.size())); passRuleResult.setDeviceNumber(Integer.valueOf(deviceList.size()));
AcsPersonQueryParam personParam = new AcsPersonQueryParam(); AcsPersonQueryParam personParam = new AcsPersonQueryParam();
personParam.setZoneId(passRuleResult.getId()); personParam.setZoneId(passRuleResult.getId());
CloudwalkPageInfo pageInfo = new CloudwalkPageInfo(1, 10); CloudwalkPageInfo pageInfo = new CloudwalkPageInfo(1, 1);
CloudwalkResult<CloudwalkPageAble<AcsPersonResult>> page = CloudwalkResult<CloudwalkPageAble<AcsPersonResult>> page =
this.acsPersonService.page(personParam, pageInfo, context); this.acsPersonService.page(personParam, pageInfo, context);
passRuleResult.setPersonNumber(Long.valueOf(((CloudwalkPageAble)page.getData()).getTotalRows())); if (!page.isSuccess()) {
this.logger.info("远程调用查询通行人员分页失败,原因:" + page.getMessage());
throw new ServiceException(page.getCode(), page.getMessage());
}
if (ObjectUtils.isEmpty(page.getData())) {
passRuleResult.setPersonNumber(Long.valueOf(0L));
} else {
passRuleResult.setPersonNumber(
Long.valueOf(((CloudwalkPageAble)page.getData()).getTotalRows()));
}
} }
return CloudwalkResult.success(passRuleResults); return CloudwalkResult.success(passRuleResults);
} catch (DataAccessException e) { } catch (DataAccessException e) {