Bootstrap

springboot+vue实现WebSocket实时数据交互,后端推送,mybatis不能使用,踩的坑都已埋好。

首页感谢大佬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

到此分享完毕,请多多反馈,有问题留言哦。

小白路漫漫,让我们一起加油!!!

;