# 购物车实时通信使用说明书(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 ``` ### 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 ``` --- ## 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. ✅ 掌握错误处理和重连机制 如有问题,请参考代码实现或联系开发团队。