Files
starRiverProperty/docs/superpowers/specs/2026-05-10-floor-policy-trigger-optimization.md
T

13 KiB
Raw Blame History

楼层策略触发优化:仅访客邀约场景执行策略

日期: 2026-05-10 状态: 方案评估(未实施) 关联: component-org 日志分析


一、问题定义

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<List<String>> replacementFloors =                       │
│      this.tenantVisitorFloorPolicyService                         │
│          .replacementZoneIdsIfPolicyActive(                        │
│              result.getOrganizationIds());                        │
│                                                                   │
│  PersonRuleServiceImpl.addVisitor() L172-175:                     │
│  PersonDetailParam detailParam = new PersonDetailParam();         │
│  detailParam.setId(param.getPersonId());                          │
│  detailParam.setBusinessId(context.getCompany().getCompanyId());  │
│  CloudwalkResult<PersonResult> detailResult =                     │
│      this.personService.detail(detailParam, context);             │
└───────────────────────────────────────────────────────────────────┘

方案 A:新增 skipFloorPolicy 字段( 推荐)

改动内容

Step 1: DetailImgPersonParam 新增字段

// 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 新增字段

// 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() 加条件判断

// L670 修改前:
Optional<List<String>> replacementFloors =
    this.tenantVisitorFloorPolicyService.replacementZoneIdsIfPolicyActive(
        result.getOrganizationIds());

// L670 修改后:
Optional<List<String>> replacementFloors = Optional.empty();
if (!Boolean.TRUE.equals(param.getSkipFloorPolicy())) {
    replacementFloors = this.tenantVisitorFloorPolicyService
        .replacementZoneIdsIfPolicyActive(result.getOrganizationIds());
}
if (replacementFloors.isPresent()) {
    // ... 现有替换逻辑不变
}

Step 4: elevator 侧 addVisitor UC-02 显式跳过

// 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<PersonResult> 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 隐式传参(不推荐)

// elevator 侧:
context.getNotes().put("skipFloorPolicy", "true");

// ImgPersonServiceImpl 侧:
Map<String, String> notes = context.getNotes();
boolean skip = "true".equalsIgnoreCase(notes.get("skipFloorPolicy"));
维度 评价
改动量 ★★★★★ 仅改 service 层
语义精确性 隐式语义,字符串 key,无类型安全
可发现性 调用方无法从 API 签名得知此能力
可维护性 key 拼写错误 → 静默失败,难以排查
风险 中:notes 是 Map<String,String>,无编译期检查

方案 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

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

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

int rawFloorCount = floorList.size();
this.logger.info("[DETAIL] personId={} listByImageId returned {} zones: {}",
    param.getId(), rawFloorCount, floorList);

// ★ 仅当未显式跳过策略时才执行策略查询
Optional<List<String>> replacementFloors = Optional.empty();
if (!Boolean.TRUE.equals(param.getSkipFloorPolicy())) {
    replacementFloors = this.tenantVisitorFloorPolicyService
        .replacementZoneIdsIfPolicyActive(result.getOrganizationIds());
}

if (replacementFloors.isPresent()) {
    List<String> 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

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<PersonResult> detailResult =
    this.personService.detail(detailParam, context);