# 楼层策略触发优化:仅访客邀约场景执行策略 **日期**: 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); ```