mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
13 KiB
13 KiB
楼层策略触发优化:仅访客邀约场景执行策略
日期: 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 字段
理由:
- 改动最小:4 文件 ~16 行,不破坏任何现有契约
- 语义明确:字段名自解释,调用方显式声明意图
- 向后兼容:默认 null = 策略执行,不传参的调用方行为不变
- 渐进优化:后续 CRK、前端等调用方可按需传入
skipFloorPolicy=true - 符合约束:不新增接口,不改变语义,仅扩展参数
优化效果预估
| 调用方 | 当前行为 | 优化后 | 策略查询减少 |
|---|---|---|---|
| 前端访客邀约 | ✅ 执行策略 | 不变 | — |
| 前端人员管理 | ❌ 多余查询 | 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);