昨天接到一个需求,excel导入时,实时的向前端界面传递数据,增加大数据量的客户友好性。让用户可以感知到导入的操作。该怎么实现呢?之前没有搞过类似的东西,所以就问了同事。同事建议两种方式:第一,使用流实时的向前台写数据;第二,使用websocket长连接,实时的向前端推数据。我使用了第二种方式,因为之前没有用过websocket(惭愧),所以在网上找了找相关的信息,很大一部分是参考这篇博客中代码(地址:https://blog.csdn.net/qq_35387940/article/details/93483678),感谢!下面的代码很大一部分都是上述博客中的代码。
现在就描述一下我对websocket一些浅薄的见解,只能说是最基本的使用,后续还需要深入的去了解。这篇博文也是为了方便自己去回顾这个现象。
想要使用websocket进行通讯,我们就需要有客户端以及对应的服务端。我们现在是在后台中创建一个服务端,前台作为客户端。那在springBoot里面想要使用webscoket需要满足以下条件。
1.pom依赖文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.创建节点配置类MyEndpointConfigure.java
public class MyEndpointConfigure extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
private static volatile BeanFactory context;
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return context.getBean(clazz);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
MyEndpointConfigure.context = applicationContext;
}
}
3.然后是WebSocket配置类,WebSocketConfig.java
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Bean
public MyEndpointConfigure newConfigure() {
return new MyEndpointConfigure();
}
4.然后是关键,WebSocket的各个监听方法,ProductWebSocket.java
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.jc.websocket.config.MyEndpointConfigure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @Author:JCccc
* @Description:
* @Date: created in 15:56 2019/5/13
*/
@Component
@ServerEndpoint(value = "/productWebSocket/{userId}", configurator = MyEndpointConfigure.class)
public class ProductWebSocket {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static final AtomicInteger OnlineCount = new AtomicInteger(0);
// concurrent包的线程安全Set,用来存放每个客户端对应的ProductWebSocket对象。
private static CopyOnWriteArraySet<ProductWebSocket> webSocketSet = new CopyOnWriteArraySet<ProductWebSocket>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
private Logger log = LoggerFactory.getLogger(ProductWebSocket.class);
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(@PathParam("userId")String userId, Session session) {
log.info("新客户端连入,用户id:" + userId);
this.session = session;
webSocketSet.add(this); // 加入set中
addOnlineCount(); // 在线数加1
if(userId!=null) {
List<String> totalPushMsgs = new ArrayList<String>();
totalPushMsgs.add(userId+"连接成功-"+"-当前在线人数为:"+getOnlineCount());
if(totalPushMsgs != null && !totalPushMsgs.isEmpty()) {
totalPushMsgs.forEach(e -> sendMessage(e));
}
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
log.info("一个客户端关闭连接");
webSocketSet.remove(this); // 从set中删除
subOnlineCount(); // 在线数减1
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户发送过来的消息为:"+message);
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("websocket出现错误");
error.printStackTrace();
}
public void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
log.info("推送消息成功,消息为:" + message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message) throws IOException {
for (ProductWebSocket productWebSocket : webSocketSet) {
productWebSocket.sendMessage(message);
}
}
public static synchronized int getOnlineCount() {
return OnlineCount.get();
}
public static synchronized void addOnlineCount() {
OnlineCount.incrementAndGet(); // 在线数加1
}
public static synchronized void subOnlineCount() {
OnlineCount.decrementAndGet(); // 在线数加1
}
}
接下来用一个HTML5 页面,连接当前的WebSocket节点,接/发消息, index.html
<!DOCTYPE HTML>
<html>
<head>
<title>Test My WebSocket</title>
</head>
<body>
Test<br/>
<input id="text" type="text" /><button οnclick="send()">Send</button> <button οnclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8066/productWebSocket/001");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
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 closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
代码都在上面了,下面说一下使用的心得。
首先看后端的代码,最主要的就是ProductWebSocket .java这个文件,其他的都是一些配置类。目前在我需要的场景里面,最重要的方法就是 sendInfo()方法。这个实际上是遍历我实际启动的客户端,然后推送信息给对应的客户端。当我目前只有一个客户端,所以只会推送给当前客户端。我是在我的业务方法中调用了这个方法。先不考虑效率的问题,如果我是按照excel的条数,循环插入数据库,那么在每次我插入数据库之后,都需要调用一次推送方法,把插入成功的消息传递给前端界面。界面上面实时展示对应的消息。
再来看前端代码,最主要的在我看来是两个地方。第一,和客户端建立链接,代码如下:
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8083/productWebSocket/001");
}
else{
alert('Not support websocket')
}
这个可以放在任何地方,我是放在了js头部,也就是在我刚进入这个界面的时候,就和服务端建立链接。
第二,接受服务端推送消息的代码,代码如下
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
这里面的event.data就是服务端给客户端推送的消息。我们只要在这个方法中将接受到的信息推送到界面上就可以了。