docs: component-org 上线日志分析报告与楼层策略触发优化方案

This commit is contained in:
2026-05-10 15:32:22 +08:00
parent 203005aafb
commit f7c042ca8f
2 changed files with 702 additions and 0 deletions
@@ -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<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` 新增字段
```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<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 显式跳过
```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<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 隐式传参(不推荐)
```java
// 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
```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<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
```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<PersonResult> detailResult =
this.personService.detail(detailParam, context);
```