首页感谢大佬Moshow郑锴对我们的WebSocket的精彩讲解,通过其内容我们可以迈入其技术。
但根据我们的业务要求需要改写一下代码,说明一下补充的内容,后端自动推送前端,mapper无法使用,前端的小bug。
这是遇到的bug
- javax.websocket.EncodeException: No encoder specified for object of class [类型](类型问题)
- Cannot invoke “com.example.yungongju.impl.async.RealTimeServiceImpl.list()” because “com.example.yungongju.config.WebSocketServer.realTimeService” is null(mapper无法注入)
- caught TypeError: Cannot set properties of undefined (setting ‘data’)(this无法使用)
话不多说直接上代码,遇到的bug在下面进行详细的解答。
springboot代码
对大佬的代码略作了修改,使其更加完整。
首先引入pom.xml文件
<!-- websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
WebSocketConfig类 启用WebSocket的支持
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket支持
* @author zhengkai.blog.csdn.net
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocketServer
核心代码,做了调整,因为大佬的代码不支持mapper注入,简单的来说不能使用mybatis和mybatis-plus,网上进行了研究,听说mybatis-plus需要属于3.5.1版本,要不然也不能使。
这里需要注意一下一下几点
- sendMessage方法里是自动推送给前端的内容
- **session.getBasicRemote().sendObject(result);**代码中的sendObiect是发送给前端的object类型,可以点进去查看一下可发送的类型。
- 由于我这边发送object类型遇见问题(下述会详细讲解),进行了改动封装为了HashMap进行发送。封装代码在下面。
package com.example.yungongju.config;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.yungongju.entity.DataInformation;
import com.example.yungongju.entity.async.RealTime;
import com.example.yungongju.impl.async.RealTimeServiceImpl;
import com.example.yungongju.mapper.async.AsyncDataMapper;
import com.example.yungongju.mapper.async.RealTimeMapper;
import com.example.yungongju.service.impl.DataInformationServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author zhengkai.blog.csdn.net
*/
@ServerEndpoint(value = "/imserver/{userId}", encoders = {ServerEncoder.class})
@Component
public class WebSocketServer {
static Log log = LogFactory.get(WebSocketServer.class);
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收userId
*/
private String userId = "";
private static RealTimeServiceImpl realTimeService;
@Resource
public void setRealTimeServiceImpl(RealTimeServiceImpl realTimeService) {
WebSocketServer.realTimeService = realTimeService;
}
private static RealTimeMapper realTimeMapper;
@Resource
public void setRealTimeMapper(RealTimeMapper realTimeMapper) {
WebSocketServer.realTimeMapper = realTimeMapper;
}
private static DataInformationServiceImpl dataInformationService;
@Resource
public void setDataInformationService(DataInformationServiceImpl dataInformationService) {
WebSocketServer.dataInformationService = dataInformationService;
}
private static AsyncDataMapper asyncDataMapper;
@Resource
public void setAsyncDataMapper(AsyncDataMapper asyncDataMapper) {
WebSocketServer.asyncDataMapper = asyncDataMapper;
}
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
//加入set中
} else {
webSocketMap.put(userId, this);
//加入set中
addOnlineCount();
//在线数加1
}
log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("用户:" + userId + ",网络异常!!!!!!");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
//从set中删除
subOnlineCount();
}
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:" + userId + ",报文:" + message);
//可以群发消息
//消息保存到数据库、redis
if (StringUtils.isNotBlank(message)) {
try {
//解析发送的报文
JSONObject jsonObject = JSON.parseObject(message);
//追加发送人(防止串改)
jsonObject.put("fromUserId", this.userId);
String toUserId = jsonObject.getString("toUserId");
//传送给对应toUserId用户的websocket
if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
} else {
log.error("请求的userId:" + toUserId + "不在该服务器上");
//否则不在这个服务器上,发送到mysql或者redis
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
// this.session.getBasicRemote().sendText(message);
try {
while (session.isOpen()) {
Date d = new Date();
Map<String, List<?>> result = new HashMap<>();
SimpleDateFormat sbf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//List<RealTime> realTimes = realTimeService.list();
//List<DataInformation> dataInformationArrayList1 = dataInformationService.list();
// List<AsyncData> datas = asyncDataMapper.findAllByAsyncBoltDataId();
// List<String> startDate = datas.stream().map(data -> data.getStartDate()).collect(Collectors.toList());
// List<Integer> actionBolts = datas.stream().map(data -> data.getActionBolts()).collect(Collectors.toList());
// List<Integer> completeBolts = datas.stream().map(data -> data.getCompleteBolts()).collect(Collectors.toList());
// result.put("startDate", startDate);
// result.put("actionBolts", actionBolts);
// result.put("completeBolts", completeBolts);
// List<List<String>> list=new ArrayList<>();
// for (RealTime realTime :realTimes){
// List<String> list1 = new ArrayList<>();
// list1.add(realTime.getMachineName());
// list1.add(realTime.getMachineSerialNumber());
// list1.add(realTime.getMachineBelong());
// list1.add(realTime.getName());
// list1.add(realTime.getProcessProduct());
// list1.add(realTime.getPreces());
// list.add(list1);
// }
//
// result.put("dataInformationArrayList1", dataInformationArrayList1);
// result.put("realTime", list);
// session.getBasicRemote().sendObject(result);
session.getBasicRemote().sendText((sbf.format(d)));
System.out.println("发送数据");
System.out.println(sbf.format(d));
//多少秒进行前段推送
Thread.sleep(1000);
}
} catch (IOException e) {
log.error("发送消息出错:{}", e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (EncodeException e) {
throw new RuntimeException(e);
}
}
/**
* 发送自定义消息
*/
public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
log.info("发送消息到:" + userId + ",报文:" + message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
} else {
log.error("用户" + userId + ",不在线!");
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
ServerEncoder Hash封装类
package com.example.yungongju.config;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import java.util.HashMap;
/**
* @desc: WebSocket编码器
* @author: ysdysyn
* @since: 2023-12-27
*/
public class ServerEncoder implements Encoder.Text<HashMap> {
private static final Logger log = LoggerFactory.getLogger(ServerEncoder.class);
/**
* 这里的参数 hashMap 要和 Encoder.Text<T>保持一致
* @param hashMap
* @return
* @throws EncodeException
*/
@Override
public String encode(HashMap hashMap) throws EncodeException {
/*
* 这里是重点,只需要返回Object序列化后的json字符串就行
* 你也可以使用gosn,fastJson来序列化。
* 这里我使用fastjson
*/
try {
return JSONObject.toJSONString(hashMap);
}catch (Exception e){
log.error("",e);
}
return null;
}
@Override
public void init(EndpointConfig endpointConfig) {
//可忽略
}
@Override
public void destroy() {
//可忽略
}
}
到此我们的springBoot代码完毕。
vue 代码
方法写好,可通过按钮或其他方式启动连接后端。这边写的页面加载时自动启动连接WebSocket,页面销毁时取消连接WebSocket。
需要注意,获取消息事件里的msg的值,打印出来后是否需要转换。
created(){
this.openSocket();
},
beforeDestroy() {
this.socket.close(); //离开路由之后断开websocket连接
},
methods:{
openSocket() {
let that = this
if (typeof WebSocket == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
//var socketUrl="${request.contextPath}/im/"+$("#userId").val();
//baseURl是IP+端口号
var socketUrl = baseURL+"/imserver/" + this.userId;
socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
console.log(socketUrl);
if (this.socket != null) {
this.socket.close();
this.socket = null;
}
this.socket = new WebSocket(socketUrl);
//打开事件
this.socket = new WebSocket(socketUrl);
//打开事件
this.socket.onopen = function() {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
this.socket.onmessage = function(msg) {
console.log(msg);
msg = JSON.parse(msg.data);
console.log(msg);
that.config.data = msg.realTime
// this.config.data = a
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
this.socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
this.socket.onerror = function() {
console.log("websocket发生了错误");
};
}
},
sendMessage() {
if (typeof WebSocket == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
console.log(
'{"toUserId":"' +
this.toUserId +
'","contentText":"' +
this.content +
'"}'
);
this.socket.send(
'{"toUserId":"' +
this.toUserId +
'","contentText":"' +
this.content +
'"}'
);
}
},
}
问题总结
Cannot invoke “com.example.yungongju.impl.async.RealTimeServiceImpl.list()” because “com.example.yungongju.config.WebSocketServer.realTimeService” is null
这个问题讲述的无法注入mapper的问题,导致我们也没有办法使用mybatis。
本质原因:spring管理的都是单例(singleton),和 websocket (多对象)相冲突。
解决办法很简单,这样写
private static RealTimeMapper realTimeMapper;
@Resource
public void setRealTimeMapper(RealTimeMapper realTimeMapper) {
WebSocketServer.realTimeMapper = realTimeMapper;
}
private static DataInformationServiceImpl dataInformationService;
@Resource
public void setDataInformationService(DataInformationServiceImpl dataInformationService) {
WebSocketServer.dataInformationService = dataInformationService;
}
javax.websocket.EncodeException: No encoder specified for object of class [类型](类型问题)
修改注解
@ServerEndpoint(value = "/imserver/{userId}", encoders = {ServerEncoder.class})
@Component
public class WebSocketServer {
然后添加上面的ServerEncoder工具类就OK了。
参考小刘爱搬砖
caught TypeError: Cannot set properties of undefined (setting ‘data’)
暂存this。let that = this; 在内部用that.xx代替this.xx
在出现问题的方法块里最上面写上
let that = this
到此分享完毕,请多多反馈,有问题留言哦。
小白路漫漫,让我们一起加油!!!