一、WebSocket
1.1 介绍
WebSocket 是一种基于 TCP 的新型网络协议。它实现了浏览器与服务器的全双工通信 —— 只需完成一次握手,双方即可建立持久性连接,并支持双向数据传输。
1.1.1 与 HTTP 协议对比
- 连接方式
- HTTP:短连接
- WebSocket:长连接
- 通信模式
- HTTP:单向,基于请求-响应模式(客户端请求,服务端响应)
- WebSocket:支持双向通信
- 底层协议
- HTTP和WebSocket都基于TCP
1.1.2 小疑惑
Q:既然WebSocket支持双向通信,看似比HTTP更强大,那么是否可以基于WebSocket开发所有业务功能呢?
A:答案是不行,原因如下:
- 服务器长期维护长连接,资源开销大
- 浏览器兼容性存在差异
- WebSocket 长连接受网络环境影响明显,需要处理好断线重连
1.1.3 应用场景
视频弹幕

网页在线聊天
体育赛事实况
股票基金实时行情
1.2 案例
3.2.1 案例分析
需求:实现浏览器与服务器全双工通信。
- 浏览器可向服务端发送消息
- 服务端可主动向浏览器推送消息
效果展示:

实现步骤:
- 使用
websocket.html页面作为WebSocket客户端 - 导入WebSocket maven依赖
- 定义服务端组件
WebSocketServer - 注册WebSocket配置类
WebSocketConfiguration - 定义定时任务
WebSocketTask,定时向客户端推送消息
3.2.2 案例代码
(1)前端页面:websocket.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Demo</title>
</head>
<body>
<input id="text" type="text" />
<button onclick="send()">发送消息</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
var clientId = Math.random().toString(36).substr(2);
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点 注意现在是ws协议
//这其实就是一个握手的请求,如果请求成功了客户端和服务端就建立了一个长连接
// 之后就可以双向通信了。
websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(){
setMessageInnerHTML("连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//发送消息(客户端向服务端发送消息)
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
</script>
</html>
(2)Maven 依赖
在 qi-server/pom.xml 中引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
(3)服务端组件:WebSocketServer
@Component
//类似于controller方法中的路径,只不过用的注解不同,对应之前页面上的地址
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放会话对象(客户端和服务端要建立一个连接本质上就是一个会话,建立好会话之后双方之间就可以进行双向通信了)
private static Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
* 这个过程不用我们管而是由websocket这个小框架自己来调,客户端可能有多个所以要区分不同的客户端,
* 具体是通过sid来区分不同的客户端,sid是页面传递过来的clientId,clientId是页面生成的一个随机数
* @PathParam:路径参数获取
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法,类似于controller中的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/**
* 群发:把map中的session都给遍历出来了,这就相当于是群发的一个效果,因为客户端可能
* 有多个,都连接到了这个服务端,现在把这些session都遍历出来 然后都给这些
* 客户端发送消息,这就是一个群发的效果。
* 注意:这个是普通方法需要自己手动调用
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
//把sessionMap集合中的session都遍历出来
for (Session session : sessions) {
try {
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(4)配置类:WebSocketConfiguration
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
(5)定时任务:WebSocketTask
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
//调用WebSocketServer类中群发的方法
webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
}
}
3.2.3 功能测试
- 启动服务,打开
websocket.html页面 - 浏览器向服务端发送消息

- 服务端每隔 5 秒向浏览器推送数据

二、来单提醒(WebSocket实践)
2.1 需求分析
当用户下单并支付成功后,需要及时通知商家。通知方式包括:
- 语音播报
- 弹出提示框

2.1.1 设计思路
- 通过WebSocket保持管理端页面与服务端的长连接
- 用户支付成功后,服务端调用WebSocket API主动推送消息
- 客户端浏览器解析消息,根据类型区分是来单提醒还是催单,并触发对应的提示和语音播报
- 消息格式约定为 JSON,包含以下字段:
type:消息类型(1=来单提醒,2=客户催单)orderId:订单 IDcontent:提示框显示的内容
无论是来单提醒还是催单,本质都关联一个订单,因此需要传递订单 ID
2.2 实现代码
2.2.1 WebSocketServer配置
复用案例中的WebSocketServer 组件及配置类
2.2.2 前端代码
前端已实现WebSocket 客户端。
- 登录页面打开开发者工具,点击登录
- 控制台显示服务端与客户端成功建立长连接:

2.2.3 支付功能处理(有商户号或其他支付途径方案)
在 OrderServiceImpl 中注入WebSocketServer
@Autowired
private WebSocketServer webSocketServer;
@Override
public void paySuccess(String outTradeNo) {
Orders ordersDB = orderMapper.getByNumber(outTradeNo);
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
Map map = new HashMap();
map.put("type", 1); // 来单提醒
map.put("orderId", orders.getId());
map.put("content", "订单号:" + outTradeNo);
String json = JSON.toJSONString(map);
webSocketServer.sendToAllClient(json);
}
2.2.4 支付功能处理(无支付途径方案)
@Autowired
private WebSocketServer webSocketServer;
@Override
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
Long userId = BaseContext.getCurrentId();
User user = userMapper.getById(userId);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code","ORDERPAID");
OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
vo.setPackageStr(jsonObject.getString("package"));
Integer orderPaidStatus = Orders.PAID;
Integer orderStatus = Orders.TO_BE_CONFIRMED;
LocalDateTime checkoutTime = LocalDateTime.now();
Long orderId = Long.parseLong(ordersPaymentDTO.getOrderNumber());
orderMapper.updateStatus(orderStatus, orderPaidStatus, checkoutTime, orderId);
Orders ordersDB = orderMapper.getByNumber(ordersPaymentDTO.getOrderNumber());
Map map = new HashMap();
map.put("type", 1);
map.put("orderId", ordersDB.getId());
map.put("content", "订单号:" + orderId);
String json = JSON.toJSONString(map);
webSocketServer.sendToAllClient(json);
return vo;
}
提示:若提示音一直循环播放,原因是设置了 5 秒定时重复推送。
![]()
三、客户催单
3.1 需求分析
上面已经说明,不再赘述
3.1.1 接口设计
客户催单
接口地址:/user/order/reminder/{id}
请求方式:GET
请求数据类型:application/x-www-form-urlencoded
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| id | id | path | true | integer(int64) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
3.2 实现代码
3.2.1 Controller 层
UserOrderController.java
@GetMapping("/reminder/{id}")
@ApiOperation("客户催单")
public Result reminder(@PathVariable Long id){
log.info("订单id{}客户催单", id);
orderService.reminder(id);
return Result.success();
}
3.2.2 Service 层
OrderService.java
/**
* 用户催单
* @param id
*/
void reminder(Long id);
3.2.3 Service 实现类
OrderServiceImpl.java
public void reminder(Long id) {
// 根据id查询订单
Orders ordersDB = orderMapper.getById(id);
// 校验订单是否存在
if (ordersDB == null ) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
//基于WebSocket实现催单
Map map = new HashMap();
map.put("type", 2);//1表示来电提醒 2代表用户催单
map.put("orderId", id);//订单的id
map.put("content", "订单号:" + ordersDB.getNumber());//订单号
//参数需要json类型,所以需要进行转化(调用WebSocketServer组件中群发的方法)
webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
3.2.4 Mapper 层
/**
* 根据id查询订单
* @param id
*/
@Select("select * from orders where id=#{id}")
Orders getById(Long id);
四、联调测试
登录后台
浏览器与服务端建立长连接
小程序端下单支付
下单并支付
查看来单提醒
支付成功后,商家后台收到提醒,并触发语音播报点击催单
- 后台弹框提醒
关于WebSocket的学习和实践就完成了,下一章小柒味来 - 项目开发总结与经验归纳
欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com