# 购物车实时通信使用说明书(SSE + WebSocket)
## 📌 重要说明
**SSE(Server-Sent Events)是单向通信**,只能从服务器推送到客户端,**不能从客户端发送消息到服务器**。
如果您需要前端向服务器发送购物车变化,有两种方案:
1. **方案一:SSE + HTTP API(推荐)**
- 前端通过HTTP API更新购物车
- 后端通过SSE推送更新给所有连接的客户端
- 简单、稳定、兼容性好
2. **方案二:WebSocket(双向通信)**
- 前端可以直接通过WebSocket发送购物车变化
- 后端通过WebSocket推送更新给所有连接的客户端
- 支持双向实时通信
**本系统同时支持两种方案,您可以根据需求选择使用。**
---
# 一、SSE(Server-Sent Events)使用说明
## 一、SSE简介
### 1.1 什么是SSE
SSE(Server-Sent Events)是一种服务器推送技术,允许服务器主动向客户端推送数据。与WebSocket相比,SSE更简单、更轻量,特别适合单向数据推送场景。
### 1.2 适用场景
- ✅ 实时购物车更新(多人点餐场景)
- ✅ 实时通知推送
- ✅ 实时数据监控
- ✅ 服务器主动推送消息
### 1.3 技术特点
- **单向通信**:服务器 → 客户端
- **基于HTTP**:使用标准HTTP协议,无需额外协议
- **自动重连**:浏览器自动处理连接断开和重连
- **简单易用**:API简单,易于集成
---
## 二、后端实现说明
### 2.1 接口地址
```
GET /api/store/order/sse/{tableId}
Content-Type: text/event-stream
```
**参数说明:**
- `tableId`:桌号ID(路径参数,必填)
**响应类型:** `text/event-stream`
### 2.2 后端实现代码
#### 2.2.1 Controller层
```java
@ApiOperation(value = "建立SSE连接", notes = "建立SSE连接,用于接收购物车更新消息")
@GetMapping(value = "/sse/{tableId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter createSseConnection(
@ApiParam(value = "桌号ID", required = true) @PathVariable Integer tableId
) {
log.info("建立SSE连接, tableId={}", tableId);
return sseService.createConnection(tableId);
}
```
#### 2.2.2 Service层
**接口定义:**
```java
public interface SseService {
/**
* 创建SSE连接
* @param tableId 桌号ID
* @return SSE连接对象
*/
SseEmitter createConnection(Integer tableId);
/**
* 推送购物车更新消息
* @param tableId 桌号ID
* @param message 消息内容
*/
void pushCartUpdate(Integer tableId, Object message);
/**
* 关闭SSE连接
* @param tableId 桌号ID
*/
void closeConnection(Integer tableId);
}
```
#### 2.2.3 推送消息示例
在购物车操作后推送更新:
```java
// 添加商品到购物车后
CartDTO cart = cartService.addItem(dto);
sseService.pushCartUpdate(dto.getTableId(), cart);
// 更新购物车商品数量后
CartDTO cart = cartService.updateQuantity(tableId, cuisineId, quantity);
sseService.pushCartUpdate(tableId, cart);
// 删除购物车商品后
CartDTO cart = cartService.removeItem(tableId, cuisineId);
sseService.pushCartUpdate(tableId, cart);
// 清空购物车后
cartService.clearCart(tableId);
sseService.pushCartUpdate(tableId, new CartDTO());
```
### 2.3 消息事件类型
后端会发送以下类型的事件:
| 事件名称 | 说明 | 数据格式 |
|---------|------|---------|
| `connected` | 连接成功 | 字符串:"连接成功" |
| `cart_update` | 购物车更新 | JSON格式的CartDTO对象 |
| `heartbeat` | 心跳消息 | 字符串:"ping" |
### 2.4 连接特性
- **超时时间**:30分钟
- **心跳间隔**:每30秒发送一次心跳
- **多连接支持**:一个桌号可以同时有多个SSE连接(支持多人点餐)
---
## 线上环境部署说明(SSE 必读)
项目发到线上时,SSE 长连接需要以下配置,否则容易出现连接被提前断开、收不到推送等问题。
### 1. 网关(Spring Cloud Gateway)
- **问题**:网关对后端响应有默认或全局 `response-timeout`,会主动断开长时间无“完成”的响应,导致 SSE 长连接被踢掉。
- **做法**:对 SSE 路径**单独配一条路由**,并关闭该路由的响应超时(`response-timeout: -1`),且该路由需放在点餐通用路由**之前**,保证先匹配 SSE。
- **配置示例**(加入 Nacos 中 `alien-gateway` 的网关路由配置,如 `alien-gateway.yml`):
```yaml
# 此条 SSE 路由必须放在 aliendining 通用路由前面
- id: aliendining-sse
uri: http://${route_or_local_ip}:30014 # 与现有 aliendining 保持一致
predicates:
- Path=/aliendining/store/order/sse/**
filters:
- StripPrefix=1
metadata:
response-timeout: -1 # -1 表示不超时,避免 SSE 被网关提前断开
```
- 若使用 **lb 负载均衡**(如 `lb://alien-dining`),同样给上述 SSE 路由加上 `metadata.response-timeout: -1` 即可。
### 2. Nginx / 反向代理(若存在)
若网关前还有 Nginx(或其它反向代理),需避免对 SSE 做缓冲并拉长超时:
- **关闭对 event-stream 的缓冲**:
`proxy_buffering off;`(或对 `location ~ /aliendining/store/order/sse` 单独关闭)
- **拉长读超时**:
`proxy_read_timeout` 建议 ≥ 30 分钟(如 `1800s`),或略大于业务侧 SSE 超时(当前为 30 分钟)。
- **可选**:
`proxy_connect_timeout`、`proxy_send_timeout` 也可适当调大,避免代理层先断连。
### 3. 多实例部署(alien-dining 多节点)
- 当前 SSE 连接是**按实例内存**维护的(每个实例一份 `ConcurrentHashMap`)。同一桌号若被负载均衡到不同实例,只有“写操作发生的那台实例”上的 SSE 连接会收到推送,其它实例上的同桌连接收不到。
- **建议**:
- **单实例**:无需改代码,按上面 1、2 配置即可。
- **多实例**:要么对该 SSE 路径做**会话保持**(同一桌号固定到同一实例),要么后续改造为基于 **Redis 等中间件** 的跨实例广播(需改 `SseServiceImpl` 与 Nacos/配置)。
### 4. 前端线上地址
- 线上建立 SSE 时,请使用**经过网关的完整路径**,例如:
`https://你的域名/aliendining/store/order/sse/{tableId}`
具体以你们网关的 `Path` 与 `StripPrefix` 为准,保证最终能路由到 `alien-dining` 的 `/store/order/sse/{tableId}`。
- 若网关或 Nacos 中给点餐服务配了统一前缀(如 `/api`),则 SSE 地址中也要带上该前缀。
### 5. 小结
| 环境 | 必做项 |
|--------------|--------|
| 网关 | 为 SSE 路径单独路由并设置 `response-timeout: -1`,且路由顺序优先 |
| Nginx/反向代理 | `proxy_buffering off`,`proxy_read_timeout` ≥ 30 分钟 |
| 多实例 | 会话保持或改为 Redis 等跨实例推送 |
| 前端 | 使用经网关的完整 SSE URL |
---
## 三、前端实现说明
### 3.1 基础使用(原生JavaScript)
```javascript
// 建立SSE连接
const tableId = 123; // 桌号ID
const eventSource = new EventSource(`/api/store/order/sse/${tableId}`);
// 监听连接成功事件
eventSource.addEventListener('connected', function(event) {
console.log('SSE连接成功:', event.data);
// event.data = "连接成功"
});
// 监听购物车更新事件
eventSource.addEventListener('cart_update', function(event) {
console.log('购物车更新:', event.data);
// event.data 是JSON字符串,需要解析
const cart = JSON.parse(event.data);
updateCartUI(cart); // 更新购物车UI
});
// 监听心跳事件
eventSource.addEventListener('heartbeat', function(event) {
console.log('收到心跳:', event.data);
// event.data = "ping"
});
// 监听错误事件
eventSource.onerror = function(event) {
console.error('SSE连接错误:', event);
// 连接断开或出错时会触发
// 浏览器会自动尝试重连
};
// 关闭连接
// eventSource.close();
```
### 3.2 Vue.js实现示例
```vue
正在连接...
已连接
{{ item.cuisineName }} x {{ item.quantity }}
```
### 3.3 React实现示例
```jsx
import React, { useEffect, useState, useRef } from 'react';
function CartComponent({ tableId }) {
const [connected, setConnected] = useState(false);
const [cart, setCart] = useState({ items: [], totalAmount: 0, totalQuantity: 0 });
const eventSourceRef = useRef(null);
useEffect(() => {
// 建立SSE连接
const url = `/api/store/order/sse/${tableId}`;
const eventSource = new EventSource(url);
eventSourceRef.current = eventSource;
// 连接成功
eventSource.addEventListener('connected', (event) => {
console.log('SSE连接成功');
setConnected(true);
});
// 购物车更新
eventSource.addEventListener('cart_update', (event) => {
try {
const cartData = JSON.parse(event.data);
setCart(cartData);
console.log('购物车已更新');
} catch (error) {
console.error('解析购物车数据失败:', error);
}
});
// 心跳
eventSource.addEventListener('heartbeat', (event) => {
console.log('收到心跳');
});
// 错误处理
eventSource.onerror = (error) => {
console.error('SSE连接错误:', error);
setConnected(false);
};
// 清理函数
return () => {
if (eventSourceRef.current) {
eventSourceRef.current.close();
}
};
}, [tableId]);
return (
{connected ? '已连接' : '正在连接...'}
{cart.items.map(item => (
{item.cuisineName} x {item.quantity}
))}
总金额: {cart.totalAmount}
);
}
export default CartComponent;
```
### 3.4 微信小程序实现示例
```javascript
// 微信小程序不支持EventSource,需要使用wx.request或第三方库
// 方案1:使用wx.request轮询(不推荐,但可用)
// 方案2:使用WebSocket(推荐)
// 方案3:使用第三方SSE库
// 这里提供一个简单的轮询方案作为备选
let pollingTimer = null;
function startSSEPolling(tableId, onMessage) {
const poll = () => {
wx.request({
url: `https://your-api.com/api/store/order/cart/${tableId}`,
method: 'GET',
success: (res) => {
if (res.data && res.data.code === 200) {
onMessage(res.data.data); // 购物车数据
}
},
complete: () => {
// 每2秒轮询一次
pollingTimer = setTimeout(poll, 2000);
}
});
};
poll();
}
function stopSSEPolling() {
if (pollingTimer) {
clearTimeout(pollingTimer);
pollingTimer = null;
}
}
// 使用
Page({
onLoad(options) {
const tableId = options.tableId;
startSSEPolling(tableId, (cart) => {
this.setData({ cart });
});
},
onUnload() {
stopSSEPolling();
}
});
```
---
## 四、消息数据格式
### 4.1 购物车更新消息(cart_update)
**数据格式:** JSON字符串,解析后为CartDTO对象
```json
{
"tableId": 123,
"tableNumber": "A01",
"storeId": 1,
"items": [
{
"cuisineId": 1001,
"cuisineName": "宫保鸡丁",
"cuisineType": 1,
"cuisineImage": "https://example.com/image.jpg",
"unitPrice": 38.00,
"quantity": 2,
"subtotalAmount": 76.00,
"addUserId": 100,
"addUserPhone": "13800138000",
"remark": "不要花生"
}
],
"totalAmount": 76.00,
"totalQuantity": 2
}
```
**字段说明:**
| 字段 | 类型 | 说明 |
|-----|------|------|
| tableId | Integer | 桌号ID |
| tableNumber | String | 桌号 |
| storeId | Integer | 门店ID |
| items | Array | 购物车商品列表 |
| totalAmount | BigDecimal | 总金额 |
| totalQuantity | Integer | 商品总数量 |
**CartItemDTO字段说明:**
| 字段 | 类型 | 说明 |
|-----|------|------|
| cuisineId | Integer | 菜品ID |
| cuisineName | String | 菜品名称 |
| cuisineType | Integer | 菜品类型(1:单品, 2:套餐) |
| cuisineImage | String | 菜品图片 |
| unitPrice | BigDecimal | 单价 |
| quantity | Integer | 数量 |
| subtotalAmount | BigDecimal | 小计金额 |
| addUserId | Integer | 添加该菜品的用户ID |
| addUserPhone | String | 添加该菜品的用户手机号 |
| remark | String | 备注 |
### 4.2 连接成功消息(connected)
```json
"连接成功"
```
### 4.3 心跳消息(heartbeat)
```json
"ping"
```
---
## 五、错误处理
### 5.1 连接错误
```javascript
eventSource.onerror = function(event) {
console.error('SSE连接错误:', event);
// 检查连接状态
if (eventSource.readyState === EventSource.CLOSED) {
console.log('连接已关闭');
// 可以在这里实现重连逻辑
reconnect();
} else if (eventSource.readyState === EventSource.CONNECTING) {
console.log('正在重连...');
}
};
```
### 5.2 重连机制
```javascript
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
let reconnectTimer = null;
function reconnect() {
if (reconnectAttempts >= maxReconnectAttempts) {
console.error('达到最大重连次数,停止重连');
return;
}
reconnectAttempts++;
console.log(`尝试重连 (${reconnectAttempts}/${maxReconnectAttempts})`);
reconnectTimer = setTimeout(() => {
initSSE(); // 重新初始化SSE连接
}, 3000 * reconnectAttempts); // 指数退避
}
function initSSE() {
const eventSource = new EventSource(url);
eventSource.addEventListener('connected', () => {
reconnectAttempts = 0; // 重置重连次数
clearTimeout(reconnectTimer);
});
eventSource.onerror = () => {
reconnect();
};
}
```
### 5.3 数据解析错误
```javascript
eventSource.addEventListener('cart_update', function(event) {
try {
const cart = JSON.parse(event.data);
updateCartUI(cart);
} catch (error) {
console.error('解析购物车数据失败:', error);
// 可以显示错误提示或使用默认值
showError('购物车数据解析失败,请刷新页面');
}
});
```
---
## 六、最佳实践
### 6.1 连接管理
1. **页面加载时建立连接**
```javascript
mounted() {
this.initSSE();
}
```
2. **页面卸载时关闭连接**
```javascript
beforeDestroy() {
this.closeSSE();
}
```
3. **路由切换时关闭旧连接**
```javascript
watch: {
'$route'(to, from) {
if (to.params.tableId !== from.params.tableId) {
this.closeSSE();
this.initSSE();
}
}
}
```
### 6.2 性能优化
1. **避免频繁更新UI**
```javascript
let updateTimer = null;
eventSource.addEventListener('cart_update', function(event) {
// 防抖处理,避免频繁更新
clearTimeout(updateTimer);
updateTimer = setTimeout(() => {
const cart = JSON.parse(event.data);
updateCartUI(cart);
}, 100);
});
```
2. **使用虚拟滚动(如果商品很多)**
```javascript
// 使用vue-virtual-scroll-list等库
```
### 6.3 用户体验
1. **显示连接状态**
```javascript
已连接
连接中...
```
2. **显示更新提示**
```javascript
eventSource.addEventListener('cart_update', function(event) {
const cart = JSON.parse(event.data);
updateCartUI(cart);
showToast('购物车已更新'); // 显示提示
});
```
3. **处理网络异常**
```javascript
eventSource.onerror = function(event) {
if (navigator.onLine === false) {
showError('网络连接已断开,请检查网络设置');
}
};
```
### 6.4 安全性
1. **验证tableId权限**
- 后端需要验证用户是否有权限访问该桌号的SSE连接
- 可以在请求头中添加token进行验证
2. **防止XSS攻击**
- 确保后端返回的数据是安全的
- 前端对接收到的数据进行验证和转义
---
## 七、常见问题
### 7.1 连接立即断开
**可能原因:**
- 后端未正确设置`Content-Type: text/event-stream`
- 后端连接超时设置过短
- 网络代理或防火墙阻止了SSE连接
**解决方案:**
- 检查后端Controller的`produces`属性
- 检查后端超时设置(当前为30分钟)
- 检查网络配置
### 7.2 收不到消息
**可能原因:**
- 事件名称不匹配
- 数据格式错误
- 连接已断开
**解决方案:**
- 检查事件名称是否正确(`cart_update`)
- 检查数据是否为有效的JSON
- 检查连接状态
### 7.3 心跳消息过多
**说明:**
- 心跳消息每30秒发送一次,用于保持连接
- 如果不需要处理心跳,可以忽略该事件
**解决方案:**
```javascript
// 不监听heartbeat事件即可
// eventSource.addEventListener('heartbeat', ...); // 注释掉
```
### 7.4 多个用户同时点餐
**说明:**
- 一个桌号可以同时有多个SSE连接
- 每个用户的购物车操作都会推送给该桌号的所有连接
**实现:**
- 后端已支持多连接,无需额外配置
- 前端每个用户建立自己的SSE连接即可
---
## 八、测试建议
### 8.1 功能测试
1. **连接测试**
- 测试正常连接
- 测试连接超时
- 测试连接断开重连
2. **消息推送测试**
- 测试购物车添加商品
- 测试购物车更新数量
- 测试购物车删除商品
- 测试清空购物车
3. **多用户测试**
- 测试多个用户同时连接
- 测试一个用户操作,其他用户是否收到推送
### 8.2 性能测试
1. **连接数测试**
- 测试单个桌号最多支持多少个连接
- 测试系统最多支持多少个连接
2. **消息推送测试**
- 测试高频推送场景
- 测试大量数据推送
### 8.3 兼容性测试
1. **浏览器兼容性**
- Chrome/Edge(支持)
- Firefox(支持)
- Safari(支持)
- IE(不支持,需要使用polyfill)
2. **移动端测试**
- iOS Safari
- Android Chrome
- 微信内置浏览器
---
## 九、API参考
### 9.1 建立SSE连接
```
GET /api/store/order/sse/{tableId}
```
**请求参数:**
- `tableId` (路径参数): 桌号ID
**响应:**
- Content-Type: `text/event-stream`
- 事件流
### 9.2 相关购物车API
```
POST /api/store/order/cart/add # 添加商品到购物车
PUT /api/store/order/cart/update # 更新购物车商品数量
DELETE /api/store/order/cart/remove # 删除购物车商品
DELETE /api/store/order/cart/clear # 清空购物车
GET /api/store/order/cart/{tableId} # 获取购物车
```
---
## 十、总结
SSE是一种简单高效的服务器推送技术,特别适合购物车实时更新场景。通过本文档,您可以:
1. ✅ 了解SSE的基本概念和适用场景
2. ✅ 掌握后端SSE接口的实现方式
3. ✅ 掌握前端SSE连接的使用方法
4. ✅ 了解消息格式和事件类型
5. ✅ 掌握错误处理和最佳实践
如有问题,请参考代码实现或联系开发团队。
---
# 二、WebSocket(双向通信)使用说明
## 1. WebSocket简介
### 1.1 什么是WebSocket
WebSocket是一种全双工通信协议,允许客户端和服务器之间进行双向实时通信。与SSE相比,WebSocket支持客户端主动向服务器发送消息。
### 1.2 适用场景
- ✅ 需要双向实时通信的场景
- ✅ 前端需要主动向服务器发送消息
- ✅ 实时购物车更新(前端可以直接发送变化)
- ✅ 实时聊天、协作等场景
### 1.3 技术特点
- **双向通信**:客户端 ↔ 服务器
- **实时性**:低延迟,实时推送
- **持久连接**:建立连接后保持连接
- **支持二进制和文本消息**
---
## 2. 后端实现说明
### 2.1 WebSocket端点地址
```
ws://your-domain/ws/cart/{tableId}
或
wss://your-domain/ws/cart/{tableId} (HTTPS环境)
```
**参数说明:**
- `tableId`:桌号ID(路径参数,必填)
### 2.2 后端实现代码
后端已实现 `CartWebSocketProcess` 类,位于:
```
shop.alien.dining.config.CartWebSocketProcess
```
**主要功能:**
- 建立WebSocket连接
- 接收客户端消息(添加商品、更新数量、删除商品、清空购物车)
- 推送购物车更新给所有连接的客户端
- 自动处理连接断开和错误
### 2.3 消息类型
#### 2.3.1 客户端发送的消息类型
| 消息类型 | 说明 | 数据格式 |
|---------|------|---------|
| `heartbeat` | 心跳消息 | `{"type": "heartbeat"}` |
| `add_item` | 添加商品 | 见下方示例 |
| `update_quantity` | 更新数量 | 见下方示例 |
| `remove_item` | 删除商品 | 见下方示例 |
| `clear_cart` | 清空购物车 | `{"type": "clear_cart"}` |
#### 2.3.2 服务器推送的消息类型
| 消息类型 | 说明 | 数据格式 |
|---------|------|---------|
| `connected` | 连接成功 | 见下方示例 |
| `cart_update` | 购物车更新 | 见下方示例 |
| `add_item_success` | 添加商品成功 | 见下方示例 |
| `update_quantity_success` | 更新数量成功 | 见下方示例 |
| `remove_item_success` | 删除商品成功 | 见下方示例 |
| `clear_cart_success` | 清空购物车成功 | 见下方示例 |
| `error` | 错误消息 | 见下方示例 |
| `heartbeat` | 心跳回复 | `{"type": "heartbeat", "message": "pong"}` |
---
## 3. 前端实现说明
### 3.1 原生JavaScript实现
```javascript
// 建立WebSocket连接
const tableId = 123; // 桌号ID
const ws = new WebSocket(`ws://your-domain/ws/cart/${tableId}`);
// 连接成功
ws.onopen = function(event) {
console.log('WebSocket连接成功');
};
// 接收消息
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('收到消息:', message);
switch(message.type) {
case 'connected':
console.log('连接成功:', message.message);
break;
case 'cart_update':
// 更新购物车UI
updateCartUI(message.data);
break;
case 'add_item_success':
console.log('添加商品成功');
updateCartUI(message.data);
break;
case 'update_quantity_success':
console.log('更新数量成功');
updateCartUI(message.data);
break;
case 'remove_item_success':
console.log('删除商品成功');
updateCartUI(message.data);
break;
case 'clear_cart_success':
console.log('清空购物车成功');
updateCartUI(message.data);
break;
case 'error':
console.error('错误:', message.message);
showError(message.message);
break;
case 'heartbeat':
console.log('收到心跳');
break;
}
};
// 连接错误
ws.onerror = function(error) {
console.error('WebSocket错误:', error);
};
// 连接关闭
ws.onclose = function(event) {
console.log('WebSocket连接关闭');
// 可以实现重连逻辑
reconnect();
};
// 发送消息:添加商品
function addItem(cuisineId, quantity, remark) {
const message = {
type: 'add_item',
data: {
cuisineId: cuisineId,
quantity: quantity,
remark: remark || null
}
};
ws.send(JSON.stringify(message));
}
// 发送消息:更新数量
function updateQuantity(cuisineId, quantity) {
const message = {
type: 'update_quantity',
data: {
cuisineId: cuisineId,
quantity: quantity
}
};
ws.send(JSON.stringify(message));
}
// 发送消息:删除商品
function removeItem(cuisineId) {
const message = {
type: 'remove_item',
data: {
cuisineId: cuisineId
}
};
ws.send(JSON.stringify(message));
}
// 发送消息:清空购物车
function clearCart() {
const message = {
type: 'clear_cart'
};
ws.send(JSON.stringify(message));
}
// 发送心跳
function sendHeartbeat() {
const message = {
type: 'heartbeat'
};
ws.send(JSON.stringify(message));
}
// 每30秒发送一次心跳
setInterval(sendHeartbeat, 30000);
// 关闭连接
// ws.close();
```
### 3.2 Vue.js实现示例
```vue
正在连接...
已连接
{{ item.cuisineName }} x {{ item.quantity }}
```
---
## 4. 消息格式说明
### 4.1 客户端发送消息格式
#### 添加商品
```json
{
"type": "add_item",
"data": {
"cuisineId": 1001,
"quantity": 2,
"remark": "不要花生"
}
}
```
#### 更新数量
```json
{
"type": "update_quantity",
"data": {
"cuisineId": 1001,
"quantity": 3
}
}
```
#### 删除商品
```json
{
"type": "remove_item",
"data": {
"cuisineId": 1001
}
}
```
#### 清空购物车
```json
{
"type": "clear_cart"
}
```
### 4.2 服务器推送消息格式
#### 连接成功
```json
{
"type": "connected",
"message": "连接成功",
"data": null,
"timestamp": 1704067200000
}
```
#### 购物车更新
```json
{
"type": "cart_update",
"message": "购物车更新",
"data": {
"tableId": 123,
"tableNumber": "A01",
"storeId": 1,
"items": [
{
"cuisineId": 1001,
"cuisineName": "宫保鸡丁",
"cuisineType": 1,
"cuisineImage": "https://example.com/image.jpg",
"unitPrice": 38.00,
"quantity": 2,
"subtotalAmount": 76.00,
"addUserId": 100,
"addUserPhone": "13800138000",
"remark": "不要花生"
}
],
"totalAmount": 76.00,
"totalQuantity": 2
},
"timestamp": 1704067200000
}
```
---
## 5. SSE vs WebSocket 选择建议
### 5.1 使用SSE的场景
- ✅ 只需要服务器向客户端推送数据
- ✅ 前端通过HTTP API更新数据
- ✅ 需要更好的浏览器兼容性
- ✅ 需要更简单的实现
### 5.2 使用WebSocket的场景
- ✅ 需要双向实时通信
- ✅ 前端需要主动向服务器发送消息
- ✅ 需要更低的延迟
- ✅ 需要支持二进制数据
### 5.3 本系统支持
**本系统同时支持SSE和WebSocket两种方式:**
- **HTTP API + SSE**:前端通过HTTP API更新购物车,后端通过SSE推送更新
- **WebSocket**:前端通过WebSocket发送购物车变化,后端通过WebSocket推送更新
**两种方式可以同时使用,互不冲突。**
---
## 6. 注意事项
1. **用户认证**:WebSocket连接建立时,用户信息从JWT Token中获取(如果使用HTTP API)
2. **连接管理**:页面卸载时记得关闭WebSocket连接
3. **心跳机制**:建议每30秒发送一次心跳,保持连接活跃
4. **错误处理**:实现完善的错误处理和重连机制
5. **多用户场景**:一个桌号可以同时有多个WebSocket连接,所有连接都会收到购物车更新
---
## 7. 总结
WebSocket提供了双向实时通信能力,特别适合需要前端主动发送消息的场景。通过本文档,您可以:
1. ✅ 了解WebSocket的基本概念和适用场景
2. ✅ 掌握后端WebSocket接口的实现方式
3. ✅ 掌握前端WebSocket连接的使用方法
4. ✅ 了解消息格式和事件类型
5. ✅ 掌握错误处理和重连机制
如有问题,请参考代码实现或联系开发团队。