# WebSocket消息广播修复工作记录

**日期:** 2025年1月16日  
**任务:** 修复WebSocketMessageBroadcaster.java发送JSON格式消息的问题  
**目标:** 实现向前端发送完整的JSON格式消息，包含消息类型、时间戳、消息ID等元数据

## 问题分析

### 新版本 vs 旧版本差异对比

| 方面 | 新版本（当前QAUP项目） | 旧版本（备份项目） |
|------|----------------------|------------------|
| **WebSocket实现** | 原生WebSocket + CollisionWebSocketHandler | Spring WebSocket STOMP + SimpMessagingTemplate |
| **消息发送方式** | `collisionWebSocketHandler.broadcastMessage()` | `messagingTemplate.convertAndSend()` |
| **发送内容** | ❌ 只发送 `message.getPayload().toString()` | ✅ 发送完整的 `UniversalMessage` 对象 |
| **连接管理** | ✅ 有 `getOnlineCount()` 方法 | ❌ 没有连接数管理 |
| **主题管理** | 使用原生WebSocket，无主题概念 | 使用STOMP主题 `/topic/realtime` |

### 核心问题
新版本的 `WebSocketMessageBroadcaster.java` 只发送了 `message.getPayload().toString()`，导致前端接收到的消息**缺少重要信息**：
- ❌ 消息类型 (`type`)
- ❌ 时间戳 (`timestamp`) 
- ❌ 消息ID (`messageId`)

前端无法根据消息类型进行正确的路由和处理。

## 解决方案

### 1. 添加JSON序列化支持
在 `WebSocketMessageBroadcaster.java` 中：
- 添加 `ObjectMapper` 依赖注入
- 导入 Jackson JSON 处理库

### 2. 修复消息发送逻辑
将：
```java
// 错误的方式 - 只发送payload
this.collisionWebSocketHandler.broadcastMessage(message.getPayload().toString());
```

修改为：
```java
// 正确的方式 - 发送完整的JSON消息
String jsonMessage = objectMapper.writeValueAsString(message);
this.collisionWebSocketHandler.broadcastMessage(jsonMessage);
```

### 3. 前端接收格式
修复后，前端将接收到如下格式的完整JSON消息：
```json
{
  "type": "position_update",
  "timestamp": 1705401234567890,
  "messageId": "uuid-string",
  "payload": {
    "object_id": "vehicle_001",
    "object_type": "vehicle",
    "position": {"x": 100.5, "y": 200.3},
    "heading": 45.0,
    "speed": 15.2
  }
}
```

## 代码修改记录

### 文件：`qaup-collision/src/main/java/com/qaup/collision/websocket/broadcaster/WebSocketMessageBroadcaster.java`

1. **添加导入**：
   ```java
   import com.fasterxml.jackson.databind.ObjectMapper;
   ```

2. **添加依赖注入**：
   ```java
   private final ObjectMapper objectMapper;
   
   @Autowired
   public WebSocketMessageBroadcaster(MessageCacheService messageCacheService, 
                                    CollisionWebSocketHandler collisionWebSocketHandler, 
                                    ObjectMapper objectMapper) {
       // ...
       this.objectMapper = objectMapper;
   }
   ```

3. **修复broadcastMessage方法**：
   ```java
   private void broadcastMessage(UniversalMessage<?> message) {
       try {
           // 使用Jackson ObjectMapper将UniversalMessage序列化为JSON字符串
           String jsonMessage = objectMapper.writeValueAsString(message);
           this.collisionWebSocketHandler.broadcastMessage(jsonMessage);
           
           messageCacheService.cacheMessage(message);
       } catch (Exception e) {
           System.err.println("Failed to broadcast message via native WebSocket: " + e.getMessage());
           e.printStackTrace();
       }
   }
   ```

## 预期效果

修复后的系统应该能够：
1. ✅ 向前端发送完整的JSON格式消息
2. ✅ 前端可以根据消息类型进行正确路由
3. ✅ 前端可以获取时间戳进行时序处理
4. ✅ 前端可以使用消息ID进行去重等操作

## 🔧 **额外发现和修复：时间戳一致性问题**

### 问题诊断
用户反馈发现外层时间戳和payload中的时间戳不一致：
- 外层时间戳：721736352917（相对时间）
- payload时间戳：1751939868396000（绝对时间）

### 根本原因
不同的WebSocket事件类使用了不同的时间戳生成方式：
- `PositionUpdateEvent`: 使用 `System.nanoTime() / 1000` → **相对时间**（从系统启动开始）
- `CollisionWarningEvent`: 使用 `Instant.now().toEpochMilli() * 1000` → **绝对时间**（Unix时间戳）

### 修复方案
统一所有WebSocket事件和UniversalMessage便捷方法使用绝对时间戳：
```java
// 修改前（相对时间）
this.timestamp = System.nanoTime() / 1000;

// 修改后（绝对时间）
this.timestamp = java.time.Instant.now().toEpochMilli() * 1000;
```

### 修复文件
1. `PositionUpdateEvent.java` - 两个构造函数
2. `VehicleCommandEvent.java` - 构造函数
3. `RuleViolationWebSocketEvent.java` - builder方法
4. `RuleStateChangeWebSocketEvent.java` - builder方法
5. `RuleExecutionStatusWebSocketEvent.java` - builder方法
6. `UniversalMessage.java` - 所有9个便捷方法

## 测试验证

已完成：
1. ✅ 统一JSON序列化使用ObjectMapper
2. ✅ 修复时间戳一致性问题
3. ✅ 确保所有WebSocket事件使用相同的时间戳格式

## ✅ **前端页面修复：解决"undefined"显示问题**

### 问题诊断
用户测试发现前端显示 "位置更新: undefined"，分析原因：
- 前端代码使用 `message.payload.vehicleId` 访问车辆ID
- 但 `PositionUpdatePayload` 实际字段名是 `object_id`
- 字段名不匹配导致返回 `undefined`

### 修复方案
更新 `test_websocket.html` 中的 `handleCollisionMessage` 函数：
```javascript
// 修复前
log('collisionLog', `位置更新: ${message.payload.vehicleId}`, 'info');

// 修复后
const objectId = message.payload?.object_id || '未知';
const objectType = message.payload?.object_type || '';
log('collisionLog', `位置更新: ${objectId} (${objectType})`, 'info');
```

### 增强功能
同时完善了对其他消息类型的处理：
- `rule_violation` - 规则违规事件
- `rule_execution_status` - 规则执行状态
- `rule_state_change` - 规则状态变更  
- `vehicle_command` - 车辆指令
- `traffic_light_status` - 红绿灯状态
- 添加了安全的空值检查（`?.` 操作符）

## 测试验证

已完成：
1. ✅ 统一JSON序列化使用ObjectMapper
2. ✅ 修复时间戳一致性问题
3. ✅ 确保所有WebSocket事件使用相同的时间戳格式
4. ✅ 修复前端页面字段名不匹配导致的"undefined"问题

## 🔧 **前端编译修复：解决缺失模块问题**

### 问题诊断
前端编译失败，错误信息：
```
Failed to compile.
./src/utils/index.js
Module not found: Error: Can't resolve './qaup' in '/Users/tianjianyong/apps/Company/QAUP-Management/qaup-ui/src/utils'
```

### 根本原因
- `utils/index.js` 和 `main.js` 都尝试从 `./qaup` 导入函数
- 但 `qaup.js` 文件不存在
- 实际函数定义在 `ruoyi.js` 中

### 解决方案
创建 `qaup-ui/src/utils/qaup.js` 文件，重新导出 `ruoyi.js` 中的函数：
```javascript
export {
  parseTime,
  resetForm,
  addDateRange,
  selectDictLabel,
  selectDictLabels,
  handleTree,
  sprintf,
  parseStrEmpty,
  mergeRecursive,
  tansParams,
  getNormalPath,
  blobValidate
} from './ruoyi'
```

## 最终验证

已完成全部修复：
1. ✅ 统一JSON序列化使用ObjectMapper
2. ✅ 修复时间戳一致性问题
3. ✅ 确保所有WebSocket事件使用相同的时间戳格式
4. ✅ 修复前端页面字段名不匹配导致的"undefined"问题
5. ✅ 修复前端编译错误，创建缺失的qaup.js模块

最终测试：
1. 重新启动WebSocket服务
2. 前端编译成功
3. 连接前端客户端测试
4. 验证完整JSON消息和前端正确显示

## 技术要点

- **JSON序列化**: 使用Jackson ObjectMapper确保正确的JSON格式输出
- **消息完整性**: 保持UniversalMessage的完整结构，包含type、timestamp、messageId、payload
- **兼容性**: 与前端JSON消息格式保持一致
- **错误处理**: 添加JSON序列化异常处理

---
**状态:** 代码修复完成，待测试验证 