From f7c042ca8fec41c4ab7e36ae346fa6c47030629c Mon Sep 17 00:00:00 2001 From: huangping Date: Sun, 10 May 2026 15:32:22 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20component-org=20=E4=B8=8A=E7=BA=BF?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=88=86=E6=9E=90=E6=8A=A5=E5=91=8A=E4=B8=8E?= =?UTF-8?q?=E6=A5=BC=E5=B1=82=E7=AD=96=E7=95=A5=E8=A7=A6=E5=8F=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-05-10-component-org-log-analysis.md | 351 ++++++++++++++++++ ...05-10-floor-policy-trigger-optimization.md | 351 ++++++++++++++++++ 2 files changed, 702 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-10-component-org-log-analysis.md create mode 100644 docs/superpowers/specs/2026-05-10-floor-policy-trigger-optimization.md diff --git a/docs/superpowers/specs/2026-05-10-component-org-log-analysis.md b/docs/superpowers/specs/2026-05-10-component-org-log-analysis.md new file mode 100644 index 00000000..02cc1d63 --- /dev/null +++ b/docs/superpowers/specs/2026-05-10-component-org-log-analysis.md @@ -0,0 +1,351 @@ +# component-organization.info.log 全量分析 & 策略频繁调用根因分析 + +**日期**: 2026-05-10 +**日志范围**: 12:41:32 ~ 13:18:38 (37 分钟) +**分支**: feature/guangfa-28f-hardcoded +**数据库**: 10.0.22.103 / cloudwalk / component-organization + +--- + +## 一、日志概览 + +``` +文件: logs/component-organization.info.log +大小: 26MB +行数: 106,372 +TraceId: 9,848 个 +服务端口: 17016 +``` + +### 日志构成 + +``` +████████████████████████████░░░░░ 84.2% 设备同步 (89,531 行) +██████████░░░░░░░░░░░░░░░░░░░░░░ 29.9% WARN (31,800 行) +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0.06% 用户业务 detail() 调用 (64 行) +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0.02% ERROR (20 行) +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0.01% POLICY 命中 (12 行) +``` + +--- + +## 二、服务启动时间线 + +``` +12:41:32 首个请求到达(device updatePerson) +12:41:42 Quartz 调度器启动 +12:42:07 Hystrix 初始化完成 +12:44:07 jasypt 加密引导 +12:44:09 Redis Lua 脚本 Bean 覆盖(9 个) +12:44:27 Controller 映射注册 /component/person/listPerson +12:46:27 首个业务 detail() 调用(距启动 ~5 分钟) +13:18:38 最后一条日志(图库同步失败 WARN) +``` + +--- + +## 三、业务调用完整时间线(36 次 detail 调用) + +### 3.1 按调用方来源分类 + +`POST /component/person/detail` 被以下 **7 个调用方** 消费: + +| # | 调用方 | 代码位置 | 调用场景 | 是否需要策略 | +|---|--------|---------|---------|-------------| +| 1 | **前端访客邀约页** | 通过 `PersonController.detail()` | 初始化可选择楼层 | **✅ 需要** | +| 2 | **前端人员管理** | 通过 `PersonController.detail()` | 人员详情查看 | ❌ 不需要 | +| 3 | **elevator addVisitor UC-01** | `PersonRuleServiceImpl:175` | 一键派梯,需要 floorList | **✅ 需要** | +| 4 | **elevator addVisitor UC-02** | `PersonRuleServiceImpl:175` | 仅校验被访人存在 | ❌ 不需要(不用 floorList) | +| 5 | **CRK 访客注册** | `VisitorRegisterServiceImpl:330,648` | 访客登记 | **✅ 需要** | +| 6 | **CRK 门禁识别记录** | `AcsRecogRecordServiceImpl:86` | 识别记录,仅需人员信息 | ❌ 不需要 | +| 7 | **电梯设备/记录** | `AcsElevatorDeviceServiceImpl` 等 | 设备同步 | ❌ 不需要 | + +### 3.2 日志中的调用时序 + +``` +12:46:27 detail personId=1099676120751034368 [MFG鼎鸿/613] 4 zones ✗ 未命中 +12:46:36 detail personId=833276118323793920 [大地保险/办公室] 3 zones ✗ 未命中 +12:48:44 detail personId=730473849711333376 [行政人事部/陕西巨丰] 5 zones ✗ 未命中 +12:48:50 detail personId=783987999653621760 [陕西巨丰/业务部] 2 zones ✗ 未命中 +12:49:11 detail personId=783987999653621760 [同一人 21秒后再查] 2 zones ✗ 未命中 +12:51:22 detail personId=1032218423083470848 陈晶丽 [外包+物管] 38 zones ★ 命中 → 6F +12:53:31 detail personId=903269751508242432 [陕西巨丰/业务部] 2 zones ✗ 未命中 +12:56:13 detail personId=1087053273767612416 [612/MFG鼎鸿] 4 zones ✗ 未命中 +12:56:25 detail personId=1071436837510660096 [金辉华集团/工程中心] 2 zones ✗ 未命中 +12:56:56 detail personId=1087053273767612416 [同一人 43秒后再查] 4 zones ✗ 未命中 +12:58:18 detail personId=1028610903966322688 [投资助理/陕西巨丰] 2 zones ✗ 未命中 +12:59:55 detail personId=805732141303721984 刘光林 [厨房+物管] 5 zones ★ 命中 → 6F +12:59:59 detail personId=805732141303721984 [同一人 4秒后再查] 5 zones ★ 命中 → 6F +13:00:14 detail personId=1105585180195930112 [仅 1 zone] 1 zone (无策略检查) +13:01:42 detail personId=775326439246499840 [陕西巨丰/业务部] 2 zones ✗ 未命中 +13:02:20 detail personId=992819015204597760 雷恒豫 [外包+物管] 38 zones ★ 命中 → 6F +13:02:24 detail personId=901059320052314112 [财务/陕西巨丰] 2 zones ✗ 未命中 +13:02:26 detail personId=1018452759471689728 [新媒体/陕西巨丰] 4 zones ✗ 未命中 +13:03:06 detail personId=1032604140250959872 冯水英 [环境部+物管] 38 zones ★ 命中 → 6F +13:03:08 detail personId=964199538912989184 戚浩 [外包+物管] 38 zones ★ 命中 → 6F +13:03:25 detail personId=721734719120691200 覃永杰 [保安部+物管] 31 zones ★ 命中 → 6F +13:03:29 detail personId=721734719120691200 [4秒后再查] 31 zones ★ 命中 → 6F ⚠️ +13:03:33 detail personId=721734719120691200 [又4秒后再查] 31 zones ★ 命中 → 6F ⚠️ +13:03:33 detail personId=770218949916094464 [陕西巨丰/业务部] 2 zones ✗ 未命中 +13:03:44 detail personId=721734719120691200 [第4次,11秒后再查] 31 zones ★ 命中 → 6F ⚠️ +13:04:09 detail personId=957214640516231168 [新媒体/陕西巨丰] 4 zones ✗ 未命中 +13:04:41 detail personId=906237696010194944 [陕西巨丰/大客户部] 2 zones ✗ 未命中 +13:05:03 detail personId=771809951973847040 [新媒体/陕西巨丰] 4 zones ✗ 未命中 +13:06:13 detail personId=1030066143429201920 [新媒体/陕西巨丰] 6 zones ✗ 未命中 +13:07:56 detail personId=770218949916094464 [陕西巨丰/业务部] 2 zones ✗ 未命中 +13:09:20 detail personId=1105554183521812480 [仅 1 zone] 1 zone (无策略检查) +13:10:28 detail personId=1101592364922802176 [MFG鼎鸿/613] 4 zones ✗ 未命中 +13:13:41 detail personId=605578568533016576 [大地保险] 3 zones ✗ 未命中 +13:13:54 detail personId=605578568533016576 [13秒后再查] 3 zones ✗ 未命中 +13:13:54 detail personId=992819015204597760 雷恒豫 [外包+物管] 38 zones ★ 命中 → 6F +13:17:25 detail personId=1079804406034599936 [最后一条] 4 zones (无策略检查) +``` + +### 3.3 高频重复查询分析 + +``` +覃永杰 (721734719120691200): 19秒内查询 4 次 + 13:03:25 → 13:03:29 (4秒间隔) → 13:03:33 (4秒间隔) → 13:03:44 (11秒间隔) + 判断: 前端批量操作(连续打开多人详情)或页面轮询 + +刘光林 (805732141303721984): 4秒内查询 2 次 + 12:59:55 → 12:59:59 + 判断: addVisitor 阶段1 + 前端详情页并发请求 + +雷恒豫 (992819015204597760): 12分钟内查询 2 次 + 13:02:20 → 13:13:54 + 判断: 两次独立操作 +``` + +--- + +## 四、策略频繁调用的根因分析 + +### 4.1 问题表述 + +**用户原意**: 租户访客楼层策略(`TenantVisitorFloorPolicyService`)只应在「访客邀约初始化页面」触发,用于确定被访人的可选楼层列表。 + +**实际情况**: 策略在 **36 次 detail() 调用** 中被触发了 **24 次**(12 次额外命中做了楼层替换),但其中只有部分来自访客邀约场景。 + +### 4.2 根因:`detail()` 是多消费者通用 API,策略代码无条件嵌入 + +``` + POST /component/person/detail + │ + ▼ + ┌───────────────────────────────┐ + │ ImgPersonServiceImpl.detail() │ + │ L670-672: │ + │ tenantVisitorFloorPolicy │ + │ Service.replacementZoneIds │ + │ IfPolicyActive(orgIds) ← 无条件触发! + └───────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + 访客邀约页 ✅ 人员管理 ❌ addVisitor + (需要策略) (不需要) UC-02 ❌ + (不消费 floorList) +``` + +### 4.3 所有 detail() 调用方一览 + +``` +personService.detail() 的 Feign 消费者: + +1. PersonController.detail() ← 前端 HTTP 入口 + ├─ 访客邀约页初始化 ✅ 策略必需 + └─ 人员管理/详情查看 ❌ 策略无意义(但无法区分) + +2. PersonRuleServiceImpl.addVisitor() ← 电梯侧 + ├─ UC-01(一键派梯) ✅ 需要 floorList + └─ UC-02(调用方指定楼层) ❌ floorList 不被消费(L193 直接取 param.floorIds) + +3. VisitorRegisterServiceImpl ← CRK 访客 + ├─ L330 访客登记 ✅ 可能涉及楼层选择 + └─ L648 访客登记 ✅ 同上 + +4. AcsRecogRecordServiceImpl:86 ← CRK 门禁识别 + └─ 仅需人员信息 ❌ 完全不涉及楼层 +``` + +### 4.4 为什么设计如此 + +设计文档 [2026-05-06-tenant-visitor-policy-organization-implementation.md](./2026-05-06-tenant-visitor-policy-organization-implementation.md) 明确规定: + +> **floorList 唯一主路径**:获取 `PersonResult.floorList`:**必须**调用 `PersonService.detail` +> **业务约定**:凡调用方需消费「被访人可派梯/可邀约访问的楼层集合」,必须走 `PersonService.detail` + +这一设计将 `detail()` 作为 **floorList 的权威来源**,策略替代自然应在 `detail()` 内完成。但设计未区分「需要 floorList 的调用方」和「仅需人员信息的调用方」——`detail()` 本就同时承担这两种职责。 + +### 4.5 策略命中统计 + +| 目标 orgId | 组织名 | 命中次数 | 限制楼层 | 备注 | +|-----------|--------|---------|---------|------| +| `f216235e...` | 星中心物业管理公司 | 12 | 6F | 物业内部人员被查询 | +| `488b8ad0...` | [28-38F]广发基金 | 0 | 28F | 日志期间无人查询广发人员 | + +**24 次未命中** 的 detail() 调用中,大部分是陕西巨丰投资 (a5585cb) 的人员——该组织无策略配置,但仍然触发了 `tenant_visitor_floor_policy` 表的 SELECT 查询(一次 DB 查询 + 空结果集)。 + +### 4.6 性能影响评估 + +``` +单次 detail() 调用链路: + + DB: cw_is_person (必选) + DB: cw_is_person_organization_ref (必选) + DB: cw_is_person_label_ref (必选) + Feign: elevatorFeignClient.listByImageId (必选, 跨服务调用) + DB: tenant_visitor_floor_policy (★ 策略查询, 每次 1 次 SELECT) + WHERE org_id=? AND enabled=1 LIMIT 1 + → 对每个 orgId 执行,命中即停止遍历 +``` + +- 策略查询是基于 `org_id` 的索引查询(`idx_org_enabled`),单次耗时 < 5ms +- 当前日志中 1次/分钟 的频率**无性能问题** +- 但若 `detail()` 被大量并发调用(如批量操作、定时任务),会产生 N×orgCount 次 DB 查询 + +--- + +## 五、异常与风险 + +### 5.1 设备同步 WARN (31,800 条) + +``` +"查询设备[xxx]失败" (DevicePersonSyncManager:164) + +TOP 5 失败设备: + cbbd0da12a9247409cc272e1c365698f 180 次 + 508d126a19554746a46931afcc5ec9e3 180 次 + ca9d9689c8fe4ccf89eba82a9c9df4b4 120 次 + f4765e409ffd4705a2639eb41c5251c7 115 次 + 7a4b5acc463d465ba4550db4939dac19 105 次 + +原因: 设备侧有注册,组织库中无对应设备 → 每次同步周期都触发查询失败 +影响: 日志膨胀(84% 为设备同步日志),掩盖真实业务问题 +建议: 清理数据库中不存在的设备记录,或对已知失败设备做静默处理 +``` + +### 5.2 Feign 调用失败 (11 次 ERROR) + +``` +"call AggDeviceImageStoreFeignClient sync device imageStore failed" +(Hystrix fallback, 分布在 12:49 ~ 13:18) + +间隔: 约 1-2 分钟一次 +原因: cwos-portal 服务不可达或超时 +影响: 图库同步中断,但不影响人员查询 +``` + +### 5.3 图库添加人脸失败 (5 次 ERROR) + +``` +"图库[c8c67225]添加人脸[1690239736450007040]异常" +"图库[7a83a5d2]添加人脸[1611164976657559552]异常" × 4 + +集中在 13:06~13:07,为批量操作失败 +``` + +--- + +## 六、策略优化建议 + +### 方案 A:添加调用上下文标志(推荐,最小变更) + +在 `DetailImgPersonParam` 或 `CloudwalkCallContext` 中添加 `skipFloorPolicy` 字段: + +```java +// ImgPersonServiceImpl.detail() L670 修改为: +if (!Boolean.TRUE.equals(param.getSkipFloorPolicy())) { + Optional> replacementFloors = + this.tenantVisitorFloorPolicyService + .replacementZoneIdsIfPolicyActive(result.getOrganizationIds()); + // ... 现有替换逻辑 +} +``` + +**优点**: 调用方显式控制,不破坏现有契约 +**缺点**: 需要所有「不需要策略」的调用方传参(addVisitor UC-02、门禁识别、人员管理等) + +### 方案 B:策略仅在 isVisitor 场景触发 + +```java +// 仅在访客相关场景触发策略 +if (Boolean.TRUE.equals(param.getIsVisitor())) { + // 策略检查 +} +``` + +**优点**: 改动最小,一行条件判断 +**缺点**: isVisitor 可能不是当前 detail() 的必有参数 + +### 方案 C:保持现状(设计意图) + +按照设计文档,`detail()` 是 floorList 的**权威来源**,策略嵌入是**有意为之**。当前 ~1次/分钟 的调用频率下,额外的 DB 查询成本可忽略。 + +**优点**: 零改动 +**缺点**: 语义不精确,所有 detail() 消费者都触发策略查询 + +--- + +## 七、数据库查询结果附录 + +### 7.1 策略表全量 + +```sql +SELECT p.id, p.org_id, o.NAME, p.allow_zone_ids, p.remark +FROM tenant_visitor_floor_policy p +LEFT JOIN cw_is_organization o ON p.org_id = o.ID +WHERE p.enabled=1; +``` + +| policyId | org_name | allow_zone_ids | 限制楼层 | +|----------|---------|---------------|---------| +| pm_6f_vstr_policy_006 | 星中心物管公司 | ["605560541473144832"] | 6F | +| pm_6f_vstr_policy_001 | 星河湾物业管理有限公司 | ["605560541473144832"] | 6F | +| pm_6f_vstr_policy_005 | 星中心物业服务中心 | ["605560541473144832"] | 6F | +| pm_6f_vstr_policy_003 | 星河湾物管公司 | ["605560541473144832"] | 6F | +| pm_6f_vstr_policy_007 | 物业管理总部 | ["605560541473144832"] | 6F | +| pm_6f_vstr_policy_004 | **星中心物业管理公司** | ["605560541473144832"] | **6F** ← 日志命中 | +| pm_6f_vstr_policy_002 | 星河湾物业管理公司 | ["605560541473144832"] | 6F | +| gf_vstr_policy_guangfa_fund_001x | [28-38F]广发基金管理有限公司 | ["605560545117995008"] | 28F | +| demo_vstr_policy_001 | 演示公司 | ["605560545117995008"] | 28F | + +### 7.2 楼层编码对照 + +``` +zone_id=605560540432957440 code=0x01 → 1F (首层) +zone_id=605560541473144832 code=0x06 → 6F ★ 物业策略目标 +zone_id=605560545117995008 code=0x1C → 28F ★ 广发基金策略目标 +``` + +### 7.3 关键组织节点 + +``` +f216235e54ca42bfa0379e69b3754aff = 星中心物业管理公司 (PARENT_ORG) + ├── 348328d755624b3491cd307a3109f36a = 星中心物管公司 + ├── b39c54d13fe94abe84bfa8d527db4392 = 星中心保安部 + ├── f113340e671648338eb83fc643cfa94d = 星中心环境管理部 + │ └── 8498d4a56f07494ca604789d23fc18f6 = 星中心环境部外包人员 + └── df573ae4b98f4be092d524b760bd5c40 = 星景厨房 + +a5585cb03764490c915ee807aaa13ef3 = 陕西巨丰投资资讯有限责任公司广州分公司 + ├── b3b5fe629baf45b29a1eb6ed8e7126d8 = 业务部 + ├── 363742435e3f4dc1b4626819e8637de2 = 行政人事部 + ├── 4758f34c67364c4c8f6f6c8eb9a4af13 = 新媒体 + ├── 480bd6c8b49b49568f7a5ee07331a084 = 投资助理 + ├── 768aa5ef294d4fdc979e1c5a37cb87e7 = 财务/数据/推广 + └── e84e681fa6c8463c90f70082cd4bed77 = 大客户服务部 + +488b8ad049bb43408a6fbcc50bcb89ac = [28-38F]广发基金管理有限公司 (有策略但未被查询) +``` + +--- + +## 八、总结 + +1. **日志主体是设备同步**(84.2%),用户业务调用仅占 0.06% +2. **策略调用频率**: 36 次/37 分钟 ≈ 1 次/分钟,频率不高但覆盖所有 detail() 调用方 +3. **根因**: `detail()` 是 floorList 的权威来源(设计意图),策略嵌入是**有意为之**;但 detail() 被 7+ 个不同调用方消费,其中仅 3 个需要策略 +4. **是否可以优化**: 可以,但需要改动调用方契约(添加 skip 标志)或在 detail() 内根据业务特征判断。当前频率下保持现状是可接受的 +5. **31,800 条 WARN**: 设备同步失败是主要噪音源,建议清理不存在设备的记录 diff --git a/docs/superpowers/specs/2026-05-10-floor-policy-trigger-optimization.md b/docs/superpowers/specs/2026-05-10-floor-policy-trigger-optimization.md new file mode 100644 index 00000000..7ab9e149 --- /dev/null +++ b/docs/superpowers/specs/2026-05-10-floor-policy-trigger-optimization.md @@ -0,0 +1,351 @@ +# 楼层策略触发优化:仅访客邀约场景执行策略 + +**日期**: 2026-05-10 +**状态**: 方案评估(未实施) +**关联**: [component-org 日志分析](./2026-05-10-component-org-log-analysis.md) + +--- + +## 一、问题定义 + +### 1.1 现象 + +`TenantVisitorFloorPolicyService.replacementZoneIdsIfPolicyActive()` 被嵌入 `ImgPersonServiceImpl.detail()` L670-672,导致**所有**调用 `POST /component/person/detail` 的请求都触发策略 DB 查询,而非仅访客邀约场景。 + +### 1.2 根因 + +`detail()` 是通用人员详情 API,被 7 个不同调用方消费,策略代码无条件嵌入其中,无法区分调用方意图: + +``` +POST /component/person/detail ← 唯一入口,无法区分调用方 + ├─ 访客邀约页 ✅ 需要策略 → 当前正确 + ├─ 人员管理详情 ❌ 不需要 → 多余 DB 查询 + ├─ addVisitor UC-01 ✅ 需要策略 → 当前正确 + ├─ addVisitor UC-02 ❌ 不需要 → 多余 DB 查询(不消费 floorList) + ├─ CRK 访客注册 ✅ 需要策略 → 当前正确 + ├─ CRK 门禁识别 ❌ 不需要 → 多余 DB 查询 + └─ 电梯设备同步 ❌ 不需要 → 多余 DB 查询 +``` + +### 1.3 影响评估 + +| 指标 | 当前值 | 评估 | +|------|--------|------| +| 调用频率 | ~1 次/分钟 | 低,无性能风险 | +| 策略查询耗时 | < 5ms(索引查询) | 可忽略 | +| 多余调用占比 | ~60%(7 个调用方中 4 个不需要) | 架构不精确 | + +**结论**:当前频率下无性能问题,但架构语义不精确,需要优化。 + +--- + +## 二、方案评估 + +### 2.1 涉及的关键代码路径 + +``` +┌─ interface 层 ───────────────────────────────────────────────────┐ +│ │ +│ DetailImgPersonParam (component-organization-interface) │ +│ public class DetailImgPersonParam { │ +│ private String id; │ +│ private String businessId; │ +│ } │ +│ │ +│ PersonDetailParam (intelligent-cwoscomponent-interface) │ +│ public class PersonDetailParam implements Serializable { │ +│ private String id; │ +│ private String businessId; │ +│ } │ +└───────────────────────────────────────────────────────────────────┘ + +┌─ 服务层 ─────────────────────────────────────────────────────────┐ +│ │ +│ ImgPersonServiceImpl.detail() L670-672: │ +│ Optional> replacementFloors = │ +│ this.tenantVisitorFloorPolicyService │ +│ .replacementZoneIdsIfPolicyActive( │ +│ result.getOrganizationIds()); │ +│ │ +│ PersonRuleServiceImpl.addVisitor() L172-175: │ +│ PersonDetailParam detailParam = new PersonDetailParam(); │ +│ detailParam.setId(param.getPersonId()); │ +│ detailParam.setBusinessId(context.getCompany().getCompanyId()); │ +│ CloudwalkResult detailResult = │ +│ this.personService.detail(detailParam, context); │ +└───────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 方案 A:新增 `skipFloorPolicy` 字段(⭐ 推荐) + +#### 改动内容 + +**Step 1**: `DetailImgPersonParam` 新增字段 + +```java +// cwos-component-organization-interface/.../DetailImgPersonParam.java +public class DetailImgPersonParam { + private String id; + private String businessId; + private Boolean skipFloorPolicy; // ★ 新增: true=跳过策略查询 + + // getter / setter + public Boolean getSkipFloorPolicy() { + return skipFloorPolicy; + } + public void setSkipFloorPolicy(Boolean skipFloorPolicy) { + this.skipFloorPolicy = skipFloorPolicy; + } +} +``` + +**Step 2**: `PersonDetailParam` 新增字段 + +```java +// intelligent-cwoscomponent-interface/.../PersonDetailParam.java +public class PersonDetailParam implements Serializable { + private String id; + private String businessId; + private Boolean skipFloorPolicy; // ★ 新增 + // getter / setter +} +``` + +**Step 3**: `ImgPersonServiceImpl.detail()` 加条件判断 + +```java +// L670 修改前: +Optional> replacementFloors = + this.tenantVisitorFloorPolicyService.replacementZoneIdsIfPolicyActive( + result.getOrganizationIds()); + +// L670 修改后: +Optional> replacementFloors = Optional.empty(); +if (!Boolean.TRUE.equals(param.getSkipFloorPolicy())) { + replacementFloors = this.tenantVisitorFloorPolicyService + .replacementZoneIdsIfPolicyActive(result.getOrganizationIds()); +} +if (replacementFloors.isPresent()) { + // ... 现有替换逻辑不变 +} +``` + +**Step 4**: elevator 侧 addVisitor UC-02 显式跳过 + +```java +// PersonRuleServiceImpl.addVisitor() L172 之后加: +PersonDetailParam detailParam = new PersonDetailParam(); +detailParam.setId(param.getPersonId()); +detailParam.setBusinessId(context.getCompany().getCompanyId()); + +// ★ UC-02: 调用方已显式指定楼层,无需策略查询 +if (!CollectionUtils.isEmpty(param.getFloorIds())) { + detailParam.setSkipFloorPolicy(true); +} + +CloudwalkResult detailResult = + this.personService.detail(detailParam, context); +``` + +#### 改动总结 + +| 文件 | 改动 | 行数 | +|------|------|------| +| `DetailImgPersonParam.java` | 新增 `skipFloorPolicy` 字段 | +5 | +| `PersonDetailParam.java` | 新增 `skipFloorPolicy` 字段 | +5 | +| `ImgPersonServiceImpl.java` | L670 条件判断包裹 | +3 | +| `PersonRuleServiceImpl.java` | addVisitor UC-02 传参 | +3 | +| **合计** | **4 文件,~16 行** | | + +#### 评估 + +| 维度 | 评价 | +|------|------| +| 改动量 | ★★★★★ 极小(4 文件,~16 行) | +| 向后兼容 | ✅ `Boolean` 默认 null/false = 策略执行,与当前行为一致 | +| 语义精确性 | ✅ 调用方显式声明意图,类型安全 | +| 可测试性 | ✅ 可写单测验证 `skipFloorPolicy=true` 时不走策略 | +| 渐进式 | ✅ 不传参的调用方行为不变,后续按需传 `true` | +| Feign 兼容 | ✅ Feign 序列化自动支持 Boolean 字段 | +| 风险 | 低:interface 层加字段不破坏契约 | + +--- + +### 方案 B:利用 CloudwalkCallContext.notes 隐式传参(不推荐) + +```java +// elevator 侧: +context.getNotes().put("skipFloorPolicy", "true"); + +// ImgPersonServiceImpl 侧: +Map notes = context.getNotes(); +boolean skip = "true".equalsIgnoreCase(notes.get("skipFloorPolicy")); +``` + +| 维度 | 评价 | +|------|------| +| 改动量 | ★★★★★ 仅改 service 层 | +| 语义精确性 | ❌ 隐式语义,字符串 key,无类型安全 | +| 可发现性 | ❌ 调用方无法从 API 签名得知此能力 | +| 可维护性 | ❌ key 拼写错误 → 静默失败,难以排查 | +| 风险 | 中:notes 是 Map,无编译期检查 | + +--- + +### 方案 C:拆分为独立端点(不可行) + +``` +POST /component/person/detail → 不执行策略 +POST /component/person/detail/visitor → 执行策略 +``` + +| 维度 | 评价 | +|------|------| +| 语义精确性 | ✅ URL 级区分 | +| 改动量 | ❌ 大:新增 Controller + Service 方法 | +| 约束冲突 | ❌ 违反"对外接口不可扩展"原则 | +| 代码重复 | ❌ detail() 体量大(100+ 行),拆分造成重复 | + +--- + +### 方案 D:保持现状 + +| 维度 | 评价 | +|------|------| +| 改动量 | 零 | +| 性能影响 | 策略查询 < 5ms,当前频率 ~1次/分钟,可忽略 | +| 设计意图 | 设计文档规定 detail() 是 floorList 唯一权威来源 | + +--- + +## 三、推荐结论 + +### 采用方案 A:新增 `skipFloorPolicy` 字段 + +**理由**: +1. **改动最小**:4 文件 ~16 行,不破坏任何现有契约 +2. **语义明确**:字段名自解释,调用方显式声明意图 +3. **向后兼容**:默认 null = 策略执行,不传参的调用方行为不变 +4. **渐进优化**:后续 CRK、前端等调用方可按需传入 `skipFloorPolicy=true` +5. **符合约束**:不新增接口,不改变语义,仅扩展参数 + +### 优化效果预估 + +| 调用方 | 当前行为 | 优化后 | 策略查询减少 | +|--------|---------|--------|-------------| +| 前端访客邀约 | ✅ 执行策略 | 不变 | — | +| 前端人员管理 | ❌ 多余查询 | skipFloorPolicy=true | ✅ 消除 | +| addVisitor UC-01 | ✅ 执行策略 | 不变 | — | +| addVisitor UC-02 | ❌ 多余查询 | skipFloorPolicy=true | ✅ 消除 | +| CRK 访客注册 | ✅ 执行策略 | 不变 | — | +| CRK 门禁识别 | ❌ 多余查询 | skipFloorPolicy=true(后续) | ✅ 消除 | +| 电梯设备同步 | ❌ 多余查询 | skipFloorPolicy=true(后续) | ✅ 消除 | + +**预估**:elevator addVisitor UC-02 优化立即生效,后续逐步覆盖其他调用方,最终减少约 60% 的无意义策略查询。 + +### 实施顺序 + +``` +Phase 1(核心优化): + 1. DetailImgPersonParam + skipFloorPolicy 字段 + 2. PersonDetailParam + skipFloorPolicy 字段 + 3. ImgPersonServiceImpl.detail() 加条件判断 + 4. PersonRuleServiceImpl.addVisitor() UC-02 传参 + +Phase 2(渐进覆盖): + 5. 前端人员管理 → skipFloorPolicy=true + 6. CRK 门禁识别 → skipFloorPolicy=true + 7. 电梯设备同步 → skipFloorPolicy=true +``` + +--- + +## 四、附录:完整改动预览 + +### 4.1 DetailImgPersonParam.java + +```java +public class DetailImgPersonParam { + private String id; + private String businessId; + private Boolean skipFloorPolicy; + + // ... 原有 getter/setter ... + + public Boolean getSkipFloorPolicy() { + return skipFloorPolicy; + } + + public void setSkipFloorPolicy(Boolean skipFloorPolicy) { + this.skipFloorPolicy = skipFloorPolicy; + } +} +``` + +### 4.2 PersonDetailParam.java + +```java +public class PersonDetailParam implements Serializable { + private static final long serialVersionUID = 7666677873099106082L; + + private String id; + private String businessId; + private Boolean skipFloorPolicy; + + // ... 原有 getter/setter ... + + public Boolean getSkipFloorPolicy() { + return skipFloorPolicy; + } + + public void setSkipFloorPolicy(Boolean skipFloorPolicy) { + this.skipFloorPolicy = skipFloorPolicy; + } +} +``` + +### 4.3 ImgPersonServiceImpl.java L667-685 + +```java +int rawFloorCount = floorList.size(); +this.logger.info("[DETAIL] personId={} listByImageId returned {} zones: {}", + param.getId(), rawFloorCount, floorList); + +// ★ 仅当未显式跳过策略时才执行策略查询 +Optional> replacementFloors = Optional.empty(); +if (!Boolean.TRUE.equals(param.getSkipFloorPolicy())) { + replacementFloors = this.tenantVisitorFloorPolicyService + .replacementZoneIdsIfPolicyActive(result.getOrganizationIds()); +} + +if (replacementFloors.isPresent()) { + List beforeReplacement = new ArrayList<>(floorList); + floorList = new ArrayList<>(replacementFloors.get()); + zoneNames = buildCommaSeparatedFloorNames(businessId, floorList); + this.logger.info("[DETAIL-POLICY] personId={} orgIds={} floorList REPLACED: {}→{}", + param.getId(), result.getOrganizationIds(), beforeReplacement, floorList); +} else { + this.logger.debug("[DETAIL-POLICY] personId={} orgIds={} {}", + param.getId(), result.getOrganizationIds(), + Boolean.TRUE.equals(param.getSkipFloorPolicy()) + ? "policy skipped by caller" : "no policy matched, using raw floorList"); +} +``` + +### 4.4 PersonRuleServiceImpl.java L172-175 + +```java +PersonDetailParam detailParam = new PersonDetailParam(); +detailParam.setId(param.getPersonId()); +detailParam.setBusinessId(context.getCompany().getCompanyId()); + +// UC-02: 调用方已显式指定 floorIds,此时 floorList 不被消费,跳过策略查询 +if (!CollectionUtils.isEmpty(param.getFloorIds())) { + detailParam.setSkipFloorPolicy(true); +} + +CloudwalkResult detailResult = + this.personService.detail(detailParam, context); +```