382 lines
12 KiB
Markdown
382 lines
12 KiB
Markdown
# 设计文档
|
||
|
||
## 概述
|
||
|
||
红绿灯IP地址增强功能旨在改进现有的红绿灯信号处理系统,使其能够正确解析包含IP地址和端口信息的红绿灯消息格式,并相应地调整数据库结构以支持更灵活的设备管理。
|
||
|
||
根据实际的红绿灯消息格式:`('36.113.38.178', 56930) - {"DI-01":0,"DI-02":0,"DI-11":1,...}`,系统需要修改信号解析器以提取网络地址信息,并更新数据库表结构使设备ID字段变为可选。
|
||
|
||
## 架构
|
||
|
||
### 整体架构图
|
||
|
||
```mermaid
|
||
graph TB
|
||
A[红绿灯硬件] -->|TCP消息<br/>格式: ('IP', port) - {DI数据}| B[TrafficLightTcpServer]
|
||
B --> C[TrafficLightDataCollector]
|
||
C --> D[DataProcessingService]
|
||
D --> E[TrafficLightSignalParser - 增强版]
|
||
E --> F[TrafficLightStatus - 包含IP/端口]
|
||
F --> G[TrafficLightService - 支持IP查找]
|
||
G --> H[数据库 - 更新表结构]
|
||
F --> I[WebSocketMessageBroadcaster]
|
||
I -->|WebSocket| J[前端客户端]
|
||
|
||
subgraph "修改的组件"
|
||
E
|
||
F
|
||
G
|
||
H
|
||
end
|
||
|
||
subgraph "现有组件(无需修改)"
|
||
B
|
||
C
|
||
D
|
||
I
|
||
end
|
||
```
|
||
|
||
### 数据流程
|
||
|
||
1. **消息接收**: TCP服务器接收格式为`('IP', port) - {DI数据}`的红绿灯消息
|
||
2. **消息解析**: 增强的信号解析器提取IP地址、端口号和DI信号数据
|
||
3. **设备识别**: 系统使用IP地址和端口信息识别或创建设备记录
|
||
4. **数据处理**: 处理DI信号并更新设备状态
|
||
5. **状态广播**: 通过WebSocket广播红绿灯状态更新
|
||
|
||
## 组件和接口
|
||
|
||
### 1. TrafficLightSignalParser (增强版)
|
||
|
||
**职责**: 解析包含IP地址和端口信息的红绿灯消息格式
|
||
|
||
**接口设计**:
|
||
```java
|
||
@Component
|
||
public class TrafficLightSignalParser {
|
||
// 解析包含IP和端口信息的原始消息
|
||
public TrafficLightStatus parseSignalWithAddress(String rawMessage);
|
||
|
||
// 提取IP地址信息
|
||
private String extractIpAddress(String rawMessage);
|
||
|
||
// 提取端口信息
|
||
private Integer extractPort(String rawMessage);
|
||
|
||
// 提取DI信号数据
|
||
private String extractDiData(String rawMessage);
|
||
|
||
// 验证消息格式
|
||
public boolean isValidMessageFormat(String rawMessage);
|
||
}
|
||
```
|
||
|
||
**消息格式解析逻辑**:
|
||
```java
|
||
// 输入格式: ('36.113.38.178', 56930) - {"DI-01":0,"DI-02":0,"DI-11":1,...}
|
||
// 解析步骤:
|
||
// 1. 使用正则表达式匹配 ('IP', port) 部分
|
||
// 2. 提取IP地址字符串
|
||
// 3. 提取端口号整数
|
||
// 4. 提取 - 后面的JSON数据部分
|
||
// 5. 解析DI信号数据
|
||
```
|
||
|
||
### 2. TrafficLightStatus (增强版)
|
||
|
||
**职责**: 包含IP地址和端口信息的红绿灯状态数据模型
|
||
|
||
**数据模型**:
|
||
```java
|
||
public class TrafficLightStatus {
|
||
private String ipAddress; // 设备IP地址
|
||
private Integer port; // 设备端口号
|
||
private String deviceId; // 设备ID(可选)
|
||
private String intersectionId; // 路口ID
|
||
private SignalState nsStatus; // 南北方向状态
|
||
private SignalState ewStatus; // 东西方向状态
|
||
private long timestamp; // 信号时间戳
|
||
private String rawSignal; // 原始信号数据
|
||
|
||
// 生成设备唯一标识符(当deviceId为空时使用)
|
||
public String generateDeviceIdentifier() {
|
||
return ipAddress + ":" + port;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. TrafficLight实体类 (修改版)
|
||
|
||
**职责**: 支持IP地址和端口信息存储的设备实体
|
||
|
||
**实体设计**:
|
||
```java
|
||
@Entity
|
||
@Table(name = "traffic_lights")
|
||
public class TrafficLight {
|
||
@Id
|
||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||
private Long id; // 主键ID
|
||
|
||
@Column(name = "device_id") // 设备ID改为可选
|
||
private String deviceId;
|
||
|
||
@Column(name = "ip_address", nullable = false) // 新增:IP地址字段
|
||
private String ipAddress;
|
||
|
||
@Column(name = "port") // 新增:端口字段
|
||
private Integer port;
|
||
|
||
@Column(name = "device_name", nullable = false)
|
||
private String deviceName;
|
||
|
||
@Column(name = "intersection_id", nullable = false)
|
||
private String intersectionId;
|
||
|
||
@Column(name = "device_type")
|
||
private String deviceType = "STANDARD";
|
||
|
||
@Column(name = "is_online")
|
||
private Boolean isOnline = false;
|
||
|
||
@Column(name = "last_heartbeat")
|
||
private LocalDateTime lastHeartbeat;
|
||
|
||
@Column(name = "is_active")
|
||
private Boolean isActive = true;
|
||
|
||
@Column(name = "created_time")
|
||
private LocalDateTime createdTime;
|
||
|
||
@Column(name = "updated_time")
|
||
private LocalDateTime updatedTime;
|
||
|
||
// 添加唯一约束:IP地址和端口组合必须唯一
|
||
// 在数据库层面通过复合唯一索引实现
|
||
}
|
||
```
|
||
|
||
### 4. TrafficLightRepository (增强版)
|
||
|
||
**职责**: 支持基于IP地址和端口查询的数据访问层
|
||
|
||
**接口设计**:
|
||
```java
|
||
@Repository
|
||
public interface TrafficLightRepository extends JpaRepository<TrafficLight, Long> {
|
||
// 现有方法...
|
||
|
||
// 根据IP地址和端口查找设备
|
||
Optional<TrafficLight> findByIpAddressAndPort(String ipAddress, Integer port);
|
||
|
||
// 根据IP地址查找设备列表
|
||
List<TrafficLight> findByIpAddress(String ipAddress);
|
||
|
||
// 根据设备ID查找(保持兼容性)
|
||
Optional<TrafficLight> findByDeviceId(String deviceId);
|
||
|
||
// 检查IP地址和端口组合是否已存在
|
||
boolean existsByIpAddressAndPort(String ipAddress, Integer port);
|
||
}
|
||
```
|
||
|
||
### 5. TrafficLightService (增强版)
|
||
|
||
**职责**: 支持基于IP地址的设备管理服务
|
||
|
||
**接口设计**:
|
||
```java
|
||
@Service
|
||
public class TrafficLightService {
|
||
// 现有方法...
|
||
|
||
// 根据IP地址和端口获取或创建设备
|
||
public TrafficLight getOrCreateDeviceByAddress(String ipAddress, Integer port, String intersectionId);
|
||
|
||
// 根据IP地址和端口查找设备
|
||
public Optional<TrafficLight> findDeviceByAddress(String ipAddress, Integer port);
|
||
|
||
// 更新设备心跳(基于IP地址和端口)
|
||
public void updateDeviceHeartbeatByAddress(String ipAddress, Integer port);
|
||
|
||
// 生成默认设备名称
|
||
private String generateDefaultDeviceName(String ipAddress, Integer port) {
|
||
return "TrafficLight_" + ipAddress.replace(".", "_") + "_" + port;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6. DataProcessingService (修改版)
|
||
|
||
**职责**: 处理包含IP地址信息的红绿灯信号
|
||
|
||
**接口修改**:
|
||
```java
|
||
@Service
|
||
public class DataProcessingService {
|
||
// 现有方法...
|
||
|
||
// 修改:处理包含IP地址信息的红绿灯信号
|
||
public void processTrafficLightSignal(String rawMessage) {
|
||
try {
|
||
// 使用增强的解析器解析消息
|
||
TrafficLightStatus status = trafficLightSignalParser.parseSignalWithAddress(rawMessage);
|
||
|
||
// 根据IP地址和端口获取或创建设备
|
||
TrafficLight device = trafficLightService.getOrCreateDeviceByAddress(
|
||
status.getIpAddress(),
|
||
status.getPort(),
|
||
status.getIntersectionId()
|
||
);
|
||
|
||
// 更新设备心跳
|
||
trafficLightService.updateDeviceHeartbeatByAddress(
|
||
status.getIpAddress(),
|
||
status.getPort()
|
||
);
|
||
|
||
// 创建WebSocket消息载荷
|
||
TrafficLightStatusPayload payload = createTrafficLightPayload(status, device);
|
||
|
||
// 发布状态变更事件
|
||
publishTrafficLightStatusEvent(payload);
|
||
|
||
} catch (Exception e) {
|
||
log.error("处理红绿灯信号失败: {}", rawMessage, e);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 数据模型
|
||
|
||
### 原始消息格式
|
||
```
|
||
输入: ('36.113.38.178', 56930) - {"DI-01":0,"DI-02":0,"DI-11":1,"DI-12":0,"DI-13":0,"DI-14":0,"DI-15":0,"DI-16":1,"DI-17":0,"DI-18":0}
|
||
|
||
解析结果:
|
||
- IP地址: "36.113.38.178"
|
||
- 端口: 56930
|
||
- DI数据: {"DI-01":0,"DI-02":0,"DI-11":1,"DI-12":0,"DI-13":0,"DI-14":0,"DI-15":0,"DI-16":1,"DI-17":0,"DI-18":0}
|
||
```
|
||
|
||
### 数据库表结构变更
|
||
|
||
#### 修改traffic_lights表
|
||
```sql
|
||
-- 添加新字段
|
||
ALTER TABLE traffic_lights
|
||
ADD COLUMN ip_address VARCHAR(45) NOT NULL DEFAULT '0.0.0.0',
|
||
ADD COLUMN port INTEGER;
|
||
|
||
-- 修改device_id字段为可选
|
||
ALTER TABLE traffic_lights
|
||
ALTER COLUMN device_id DROP NOT NULL;
|
||
|
||
-- 添加唯一约束:IP地址和端口组合必须唯一
|
||
CREATE UNIQUE INDEX idx_traffic_light_ip_port
|
||
ON traffic_lights(ip_address, port);
|
||
|
||
-- 添加IP地址索引
|
||
CREATE INDEX idx_traffic_light_ip
|
||
ON traffic_lights(ip_address);
|
||
```
|
||
|
||
### WebSocket消息格式 (保持不变)
|
||
```json
|
||
{
|
||
"type": "intersection_traffic_light_status",
|
||
"timestamp": 1704067200000000,
|
||
"payload": {
|
||
"intersection_id": "INTERSECTION_001",
|
||
"device_id": "36.113.38.178:56930", // 当设备ID为空时使用IP:端口
|
||
"ip_address": "36.113.38.178", // 新增字段
|
||
"port": 56930, // 新增字段
|
||
"position": {
|
||
"latitude": 39.9042,
|
||
"longitude": 116.4074
|
||
},
|
||
"ns_status": "red",
|
||
"ew_status": "green",
|
||
"timestamp": 1704067200000000
|
||
}
|
||
}
|
||
```
|
||
|
||
## 错误处理
|
||
|
||
### 1. 消息格式解析错误
|
||
- **格式不匹配**: 当消息不符合`('IP', port) - {JSON}`格式时,记录错误并跳过
|
||
- **IP地址无效**: 验证IP地址格式,无效时使用默认值并记录警告
|
||
- **端口号无效**: 验证端口号范围,无效时使用默认值
|
||
- **JSON解析失败**: 记录原始数据,跳过当前消息
|
||
|
||
### 2. 数据库操作错误
|
||
- **IP端口重复**: 当IP地址和端口组合已存在时,更新现有记录而不是创建新记录
|
||
- **约束违反**: 处理数据库约束违反,提供清晰的错误信息
|
||
- **连接失败**: 数据库连接失败时,缓存数据并重试
|
||
|
||
### 3. 设备管理错误
|
||
- **设备创建失败**: 记录错误详情,使用临时标识符继续处理
|
||
- **心跳更新失败**: 记录警告,不影响信号处理流程
|
||
|
||
## 测试策略
|
||
|
||
### 1. 单元测试
|
||
- **消息解析测试**: 测试各种消息格式的解析结果
|
||
- **IP地址提取测试**: 验证IP地址和端口的正确提取
|
||
- **设备查找测试**: 测试基于IP地址和端口的设备查找功能
|
||
|
||
### 2. 集成测试
|
||
- **数据库迁移测试**: 验证表结构变更的正确性
|
||
- **端到端测试**: 从消息接收到WebSocket广播的完整流程测试
|
||
- **错误处理测试**: 测试各种异常情况的处理
|
||
|
||
### 3. 兼容性测试
|
||
- **现有数据兼容性**: 确保现有设备记录在升级后仍能正常工作
|
||
- **API兼容性**: 验证现有API调用不受影响
|
||
|
||
## 配置管理
|
||
|
||
### 应用配置文件 (application.yml)
|
||
```yaml
|
||
traffic:
|
||
light:
|
||
parsing:
|
||
# 消息格式配置
|
||
message-format-regex: "\\('([^']+)',\\s*(\\d+)\\)\\s*-\\s*(\\{.*\\})"
|
||
default-ip: "0.0.0.0"
|
||
default-port: 8082
|
||
|
||
device:
|
||
# 设备管理配置
|
||
auto-create-device: true
|
||
default-intersection-id: "DEFAULT_INTERSECTION"
|
||
device-name-prefix: "TrafficLight_"
|
||
```
|
||
|
||
### 数据库迁移脚本
|
||
```sql
|
||
-- V1.1__add_ip_port_to_traffic_lights.sql
|
||
-- 添加IP地址和端口字段
|
||
ALTER TABLE traffic_lights
|
||
ADD COLUMN ip_address VARCHAR(45) NOT NULL DEFAULT '0.0.0.0',
|
||
ADD COLUMN port INTEGER;
|
||
|
||
-- 修改device_id为可选
|
||
ALTER TABLE traffic_lights
|
||
ALTER COLUMN device_id DROP NOT NULL;
|
||
|
||
-- 为现有记录设置默认IP地址
|
||
UPDATE traffic_lights
|
||
SET ip_address = '0.0.0.0', port = 8082
|
||
WHERE ip_address IS NULL;
|
||
|
||
-- 添加唯一约束和索引
|
||
CREATE UNIQUE INDEX idx_traffic_light_ip_port
|
||
ON traffic_lights(ip_address, port);
|
||
|
||
CREATE INDEX idx_traffic_light_ip
|
||
ON traffic_lights(ip_address);
|
||
``` |