213 lines
6.1 KiB
Markdown
213 lines
6.1 KiB
Markdown
# PositionUpdateEvent API重构文档
|
||
|
||
**创建日期:** 2025-01-15
|
||
**版本:** 0.1.11
|
||
**重构类型:** API设计原则的严格实施
|
||
|
||
## 问题背景
|
||
|
||
### 用户提出的问题
|
||
用户询问:"PositionUpdateEvent中为什么返回车辆ID用了string"
|
||
|
||
### 原有设计问题
|
||
1. **类型不一致**:getVehicleId()返回String类型,违背了"索引性质使用vehicleId"的设计原则
|
||
2. **混合逻辑**:为了兼容两种不同的payload类型,强制使用String统一返回
|
||
3. **语义不清**:没有明确区分索引标识符和业务标识符的使用场景
|
||
|
||
## 设计原则回顾
|
||
|
||
用户在前面确立的API设计原则:
|
||
> **索引性质的返回值**:使用vehicleId(唯一、稳定的标识符)
|
||
> **详细信息的返回值**:包含vehicleId + licensePlate(完整信息)
|
||
|
||
## 重构方案
|
||
|
||
### 核心思路
|
||
区分不同数据类型和使用场景,提供专门的方法:
|
||
|
||
1. **索引性质**:getVehicleId() → 返回Long类型
|
||
2. **业务标识符**:getBusinessId() → 返回String类型
|
||
3. **完整信息**:getVehicleIdentifier() → 返回完整对象
|
||
|
||
### 数据类型区分
|
||
|
||
#### 无人车数据(VehicleLocation)
|
||
- **vehicleId**:Long类型,数据库主键
|
||
- **businessId**:String类型,车牌号(licensePlate)
|
||
- **特点**:有数据库记录,支持索引查询
|
||
|
||
#### 航空器/机场车辆数据(PositionUpdatePayload)
|
||
- **vehicleId**:null(没有数据库记录)
|
||
- **businessId**:String类型,航班号/车牌号(objectId)
|
||
- **特点**:仅实时处理,不持久化存储
|
||
|
||
## 重构实施
|
||
|
||
### 1. getVehicleId()方法重构
|
||
|
||
**修改前:**
|
||
```java
|
||
public String getVehicleId() {
|
||
if (payload instanceof VehicleLocation) {
|
||
return String.valueOf(((VehicleLocation) payload).getVehicleId());
|
||
} else if (payload instanceof PositionUpdatePayload) {
|
||
return ((PositionUpdatePayload) payload).getObjectId();
|
||
}
|
||
return null;
|
||
}
|
||
```
|
||
|
||
**修改后:**
|
||
```java
|
||
public Long getVehicleId() {
|
||
if (payload instanceof VehicleLocation) {
|
||
return ((VehicleLocation) payload).getVehicleId();
|
||
}
|
||
// 航空器和机场车辆没有数据库记录,返回null
|
||
return null;
|
||
}
|
||
```
|
||
|
||
### 2. 新增getBusinessId()方法
|
||
|
||
```java
|
||
public String getBusinessId() {
|
||
if (payload instanceof VehicleLocation) {
|
||
return ((VehicleLocation) payload).getLicensePlate();
|
||
} else if (payload instanceof PositionUpdatePayload) {
|
||
return ((PositionUpdatePayload) payload).getObjectId();
|
||
}
|
||
return null;
|
||
}
|
||
```
|
||
|
||
### 3. 新增getVehicleIdentifier()方法
|
||
|
||
```java
|
||
public VehicleIdentifier getVehicleIdentifier() {
|
||
if (payload instanceof VehicleLocation) {
|
||
VehicleLocation location = (VehicleLocation) payload;
|
||
return VehicleIdentifier.builder()
|
||
.vehicleId(location.getVehicleId())
|
||
.businessId(location.getLicensePlate())
|
||
.vehicleType(location.getVehicleType().name())
|
||
.build();
|
||
} else if (payload instanceof PositionUpdatePayload) {
|
||
PositionUpdatePayload positionPayload = (PositionUpdatePayload) payload;
|
||
return VehicleIdentifier.builder()
|
||
.vehicleId(null) // 航空器没有数据库记录
|
||
.businessId(positionPayload.getObjectId())
|
||
.vehicleType(positionPayload.getObjectType())
|
||
.build();
|
||
}
|
||
return null;
|
||
}
|
||
```
|
||
|
||
### 4. VehicleIdentifier内部类
|
||
|
||
```java
|
||
@Data
|
||
@Builder
|
||
@NoArgsConstructor
|
||
@AllArgsConstructor
|
||
public static class VehicleIdentifier {
|
||
private Long vehicleId; // 数据库主键(索引性质)
|
||
private String businessId; // 业务标识符(车牌号、航班号等)
|
||
private String vehicleType; // 车辆类型
|
||
}
|
||
```
|
||
|
||
## 使用场景说明
|
||
|
||
### 索引场景
|
||
```java
|
||
PositionUpdateEvent event = new PositionUpdateEvent(vehicleLocation);
|
||
|
||
// 用于Map索引(仅无人车有效)
|
||
Long vehicleId = event.getVehicleId();
|
||
if (vehicleId != null) {
|
||
vehicleLocationMap.put(String.valueOf(vehicleId), location);
|
||
}
|
||
```
|
||
|
||
### 业务展示场景
|
||
```java
|
||
// 用于前端显示
|
||
String displayId = event.getBusinessId(); // 车牌号或航班号
|
||
String displayText = "车辆: " + displayId;
|
||
```
|
||
|
||
### 完整信息场景
|
||
```java
|
||
// 获取完整标识符信息
|
||
VehicleIdentifier identifier = event.getVehicleIdentifier();
|
||
if (identifier.getVehicleId() != null) {
|
||
// 有数据库记录的无人车
|
||
processUnmannedVehicle(identifier);
|
||
} else {
|
||
// 仅实时处理的航空器/机场车辆
|
||
processRealtimeObject(identifier);
|
||
}
|
||
```
|
||
|
||
## 测试用例修正
|
||
|
||
### 修改前
|
||
```java
|
||
assertEquals("TEST_AIRCRAFT_001", event.getVehicleId());
|
||
```
|
||
|
||
### 修改后
|
||
```java
|
||
// 航空器没有数据库记录
|
||
assertNull(event.getVehicleId());
|
||
assertEquals("TEST_AIRCRAFT_001", event.getBusinessId());
|
||
|
||
// 验证完整标识符信息
|
||
VehicleIdentifier identifier = event.getVehicleIdentifier();
|
||
assertNull(identifier.getVehicleId());
|
||
assertEquals("TEST_AIRCRAFT_001", identifier.getBusinessId());
|
||
assertEquals("AIRCRAFT", identifier.getVehicleType());
|
||
```
|
||
|
||
## 技术优势
|
||
|
||
### 1. 类型安全
|
||
- 明确的Long/String类型区分
|
||
- 避免不必要的类型转换
|
||
- 编译时类型检查
|
||
|
||
### 2. 语义清晰
|
||
- 索引和业务标识符明确分离
|
||
- 方法名称直接表达用途
|
||
- 减少使用时的歧义
|
||
|
||
### 3. 扩展性
|
||
- VehicleIdentifier类支持未来扩展
|
||
- 不同数据源的统一处理
|
||
- 保持向后兼容性
|
||
|
||
### 4. 性能优化
|
||
- 减少不必要的字符串转换
|
||
- Map操作使用数字索引更高效
|
||
- 内存使用更节省
|
||
|
||
## 验证结果
|
||
|
||
- ✅ 编译测试通过
|
||
- ✅ 单元测试通过(WebSocketEventTest)
|
||
- ✅ API设计原则完全落实
|
||
- ✅ 保持向后兼容性
|
||
- ✅ 文档和测试用例更新完成
|
||
|
||
## 设计原则强化
|
||
|
||
通过这次重构,进一步强化了API设计原则的应用:
|
||
|
||
1. **索引性质** → 明确的数据类型(Long)
|
||
2. **详细信息** → 完整的信息对象(VehicleIdentifier)
|
||
3. **业务语义** → 专门的业务标识符方法(getBusinessId)
|
||
4. **类型安全** → 编译时类型检查和null处理
|
||
|
||
这个重构为项目中所有类似的API设计树立了标准,确保了设计的一致性和可维护性。 |