mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 16:30:29 +08:00
352 lines
13 KiB
Markdown
352 lines
13 KiB
Markdown
# 楼层策略触发优化:仅访客邀约场景执行策略
|
||
|
||
**日期**: 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);
|
||
```
|