-
通过WebSocket实现管理端页面和服务端保持长连接状态
-
当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息
-
客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
-
约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
-
type 为消息类型,1为来单提醒 2为客户催单
-
orderId 为订单id
-
content 为消息内容
-
1).导入WebSocket的maven坐标、
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2). 导入WebSocket服务端组件WebSocketServer,用于和客户端通信
由于websocket在jvm中有两个实例,一个在tomcat中,一个在ioc容器中,所以autowired注入的对象实例不能生效,类属性也不是唯一的,所以在下面使用了应用上下文工具类,从ioc容器中根据反射类获取ioc容器中的唯一实例,这样才能使用redis在redis中设置empid和sid,并在ioc容器中的websocketserver的sessionmap中设置sid和session,才能在下面的sendToClient方法中,使用session去给该长连接对应的管理端发送消息。
@Slf4j
@Component
@ServerEndpoint("/ws/{sid}")
public class WebsocketServer {
/**
* 保存客户端id和session对应关系
* */
private Map<String, Session> sessionMap = new ConcurrentHashMap<>();
/**
* 当与客户端建立连接以后,springboot框架回调此方法
* @param session 用来与客户端进行通信的
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid")String sid){
// 从容器中获取Bean对象
WebsocketServer bean = ApplicationContextUtil.getBean(WebsocketServer.class);
StringRedisTemplate redisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
EmpProperties empProperties = ApplicationContextUtil.getBean(EmpProperties.class);
// 保存sid到redis,key:商户id,value:sid
redisTemplate.opsForValue().set("emp:"+empProperties.getEmpId(), ""+sid);
log.info("与客户端建立连接, sid:{}", sid);
bean.sessionMap.put(sid, session);
log.info("sessionMap.size():{}, sessionMap hashcode:{}, this.hashCode:{}", sessionMap.size(), sessionMap.hashCode(), this.hashCode());
}
/**
* 当收到来自客户端的消息以后,springboot框架回调此方法
*
*/
@OnMessage
public void onMessage(String msg, @PathParam("sid")String sid){
log.info("收到来自客户端的消息, msg:{}, sid:{}", msg, sid);
}
/**
* 当与客户端断开以后,springboot框架回调此方法
*/
@OnClose
public void onclose(@PathParam("sid")String sid){
log.info("与客户端断开连接,sid:{}", sid);
WebsocketServer bean = ApplicationContextUtil.getBean(WebsocketServer.class);
StringRedisTemplate redisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
EmpProperties empProperties = ApplicationContextUtil.getBean(EmpProperties.class);
// 删除redis
Long empId = empProperties.getEmpId();
redisTemplate.delete("emp:"+empId);
// 防止内存泄露
bean.sessionMap.remove(sid);
}
/**
* 单发
* */
public void sendToClient(String sid, String message)throws Exception{
// 根据客户端的sid查找与客户端对应的session对象,就代表了一个tcp的连接
Session session = sessionMap.get(sid);
if(session == null){
log.error("客户端不在线,无法发送消息");
}else {
// 向客户端发消息
session.getBasicRemote().sendText(message);
}
}
/**
* 群发
* */
public void send2All(String message)throws Exception{
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
session.getBasicRemote().sendText(message);
}
}
}
3). 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
@Configuration
public class WebsocketConfig {
/**
* 暴漏websocke服务端与客户端通信的端点
* */
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
4).在生成订单时调用
try{
// 根据商家id获取sid
String sid = redisTemplate.opsForValue().get("emp:" + empProperties.getEmpId());
// 向sid发送消息
websocketServer.sendToClient(sid, JSON.toJSONString(msgDto));
// websocketServer.send2All(JSON.toJSONString(msgDto));
}catch(Exception e){
log.error(e.getMessage(), e);
}