使用Spring + websocket 的方式向前台推送数据

最近遇到一个需求,首页的待办任务要求实时刷新。刚开始的时候再前端写了一个定时器轮训查询。但是过了一段时间之后觉得太LOW逼了,正好想到了websocket,准备试验一下,废话不说,上代码:

(注:这里使用maven方式添加 手动添加的同学请自行下载相应jar包放到lib目录,本文使用的版本为4.3.5.RELEASE)



    <!-- spring websocket-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-messaging</artifactId>
        <version>${spring.version}</version>
    </dependency>
 
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-websocket</artifactId>
        <version>${spring.version}</version>
    </dependency>

在spring配置文件中添加如下代码

<!-- websocket相关扫描,主要扫描:WebSocketConfig  如果前面配置能扫描到此类则可以不加 -->
<context:component-scan base-package="org.zwc.ssm" />

package com.mlkj.common.websocket;

import com.mlkj.common.config.Global;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * Component注解告诉SpringMVC该类是一个SpringIOC容器下管理的类
 * 其实@Controller, @Service, @Repository是@Component的细化
 */
@Component
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
    @Autowired
    WebSocketHandler handler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        //添加websocket处理器,添加握手拦截器
        webSocketHandlerRegistry.addHandler(handler, Global.getAdminPath()+"/ws").addInterceptors(new HandShakeInterceptor());

        //添加websocket处理器,添加握手拦截器
        webSocketHandlerRegistry.addHandler(handler, Global.getAdminPath()+"/ws/sockjs").addInterceptors(new HandShakeInterceptor()).withSockJS();
    }
}

首先定义一个WebSocketConfig    继承 WebMvcConfigurerAdapter 以及实现   WebSocketConfigurer接口  ,使用注解@EnableWebSocket 表示这是一个webSocket

2 定义 WebSocketHandler

package com.mlkj.common.websocket;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mlkj.common.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


@Component
public class WebSocketHandler implements org.springframework.web.socket.WebSocketHandler {

    //当MyWebSocketHandler类被加载时就会创建该Map,随类而生
    public static final Map<String, WebSocketSession> userSocketSessionMap;

    static {
        userSocketSessionMap = new HashMap<String, WebSocketSession>();
    }

    //握手实现连接后
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        String uid = (String) webSocketSession.getAttributes().get("uid");
        userSocketSessionMap.putIfAbsent(uid, webSocketSession);
    }

    //发送信息前的处理
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {

        if(webSocketMessage.getPayloadLength()==0)return;

        //TODO 得到Socket通道中的数据并转化为Message对象

        //TODO 将信息保存至数据库


        //发送Socket信息
        sendMessageToUser("1", new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(new Result())));
    }
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {

    }

    /**
     * 在此刷新页面就相当于断开WebSocket连接,原本在静态变量userSocketSessionMap中的
     * WebSocketSession会变成关闭状态(close),但是刷新后的第二次连接服务器创建的
     * 新WebSocketSession(open状态)又不会加入到userSocketSessionMap中,所以这样就无法发送消息
     * 因此应当在关闭连接这个切面增加去除userSocketSessionMap中当前处于close状态的WebSocketSession,
     * 让新创建的WebSocketSession(open状态)可以加入到userSocketSessionMap中
     * @param webSocketSession
     * @param closeStatus
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        System.out.println("WebSocket:"+webSocketSession.getAttributes().get("uid")+"close connection");
        Iterator<Map.Entry<String,WebSocketSession>> iterator = userSocketSessionMap.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<String,WebSocketSession> entry = iterator.next();
            if(entry.getValue().getAttributes().get("uid")==webSocketSession.getAttributes().get("uid")){
                userSocketSessionMap.remove(webSocketSession.getAttributes().get("uid"));
                System.out.println("WebSocket in staticMap:" + webSocketSession.getAttributes().get("uid") + "removed");
            }
        }
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
    //发送信息的实现
    public void sendMessageToUser(String uid, TextMessage message) throws IOException {
        WebSocketSession session = userSocketSessionMap.get(uid);
        if (session != null && session.isOpen()) {
            session.sendMessage(message);
        }
    }
}

定义  HandShakeInterceptor 握手拦截器

package com.mlkj.common.websocket;


import com.mlkj.common.utils.StringUtils;
import com.mlkj.modules.sys.utils.UserUtils;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * websocket握手拦截器
 * 拦截握手前,握手后的两个切面
 */
public class HandShakeInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        System.out.println("Websocket:用户[ID:" + ((ServletServerHttpRequest) serverHttpRequest).getServletRequest().getSession(false).getAttribute("id") + "]已经建立连接");
        if (serverHttpRequest instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            // 标记用户
            String id = UserUtils.getUser().getLoginName();
            if (StringUtils.isNotBlank(id)) {
                map.put("uid", id);//为服务器创建WebSocketSession做准备
                System.out.println("用户id:" + id + " 被加入");
            } else {
                System.out.println("user为空");
                return false;
            }
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }
}

每次用户请求的时候通过后台获取到用户的id,将id放入到map中,带到WebSocketHandler.userSocketSessionMap 中存储起来

前端代码: 

<script type="text/javascript">
    var webSocket;
    $(function() {
        websocketRefresh()
    });

    function websocketRefresh(){
        if('WebSocket' in window) {
            try{
                var  ip = '${pageContext.request.localAddr}';
                var reg =  /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
                if( reg.test(ip)){
                    console.log("此浏览器支持websocket");
                    webSocket = new WebSocket("ws://"+ip+":${pageContext.request.localPort}${ctx}/ws");
                }else{
                    /*alert("ip地址"+ip+"不合法,请勿使用127.0.0.1或者http://localhost");*/
                }

            }catch (e) {
                alert("websocket链接异常");
            }

        } else if('MozWebSocket' in window) {
            alert("此浏览器只支持MozWebSocket");
        } else {
            alert("此浏览器只支持SockJS");
        }

        webSocket.onopen = function(event){
            console.log("连接成功");
            console.log(event);
        };
        webSocket.onerror = function(event){
            console.log("连接失败");
            console.log(event);
        };
        webSocket.onclose = function(event){
            console.log("Socket连接断开");
            console.log(event);
        };
        webSocket.onmessage = function(event){
            //接受来自服务器的消息
            //...
            console.log(webSocket.onmessage);
            console.log(event);
            var actLsit = JSON.parse(event.data);
            console.log(actLsit);
            apendTask(actLsit.body.data.list);
        }


        /*websocket.onopen = function(evnt) {
            console.log("链接服务器成功")
        };
        websocket.onmessage = function(evnt) {
            var actLsit = JSON.parse(evnt.data);
            console.log(actLsit.body.data);
            apendTask(actLsit.body.data);
        };
        websocket.onerror = function(evnt) {};
        websocket.onclose = function(evnt) {
            if(i<10){
                console.log("与服务器断开了链接,尝试连接"+i);
                i++;
                websocketRefresh();
            }else{
                alert("与服务器断开连接,请刷新活重新登录");
                i=0;
            }
        }*/
    }

    function apendTask(acts){
        $("#act").empty();
        console.log(acts);
       var  actsLength = acts.length;
        if(actsLength>10){
            actsLength =10;
        }
        var st = "";
        for (var i = 0; i <actsLength ; i++) {
            var vars = acts[i].vars;
            st += '<div class="search-result search-hover" style="padding: 0 20px;">';
            st += '    <div class="row">';
            st += '             <div class="col-sm-5">';
            st += '                 <h4 style="margin: 10px 0;line-height: normal;">';
            st += '                     <a href="${ctx}/act/task/form?taskId=' + acts[i].taskId + '&taskName=' + acts[i].taskName + '&taskDefKey=' + acts[i].taskDefKey + '&procInsId=' + acts[i].procInsId + '&procDefId=' + acts[i].procDefId + '&status=' + acts[i].status + '">';
            st +=                           vars.map.title == null ? acts[i].taskId : vars.map.title;
            st += '                     </a>';
            st += '                 </h4>';
            st += '             </div>';
            st += '             <div class="col-sm-2 text-right" style="text-align: center;">';
            st += '                 <p class="forum-sub-title" style="font-size: 14px;margin: 10px 0;color: #181818;">';
            st +=                       acts[i].taskName;
            st += '                 </p>';
            st += '             </div>';
            st += '             <div class="col-sm-5" style="text-align: right;margin: 10px 0; color: #8b9298;">';
            st += '                 <div class="forum-sub-title">接收时间:' + acts[i].taskCreateDate + '</div>';
            st += '             </div>';
            st += '     </div>';
            st += '</div>';
        }
        $("#act").append(st);
    }
</script>

我的业务逻辑为每隔20秒从后台查询数据并推送到前端,具体的推送环节我使用了定时器 如下代码

package com.mlkj.common.schedule;

import com.google.gson.GsonBuilder;
import com.mlkj.common.json.AjaxJson;
import com.mlkj.common.persistence.Page;
import com.mlkj.modules.act.entity.Act;
import com.mlkj.modules.act.service.ActTaskService;
import com.mlkj.common.websocket.WebSocketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Map;

/**
 * 待办任务定时器
 */
@Service
@Lazy(false)
public class actTaskTodoSchedule {

    @Autowired
    private WebSocketHandler handler;

    @Autowired
    private ActTaskService actTaskService;

    /**
     * 每隔20秒获取所有连接websocket的用户,推送当前最新的代办列表
     */
    @Scheduled(cron = "*/20 * * * * ? ")
    public void act() {
        Map<String, WebSocketSession> stringWebSocketSessionMap = WebSocketHandler.userSocketSessionMap;
        for (String st : stringWebSocketSessionMap.keySet()) {
            try {
                Page<Act> list = actTaskService.todoList(new Act(), st, new Page<Act>(0, 8));
                AjaxJson ajaxJson = new AjaxJson();
                ajaxJson.put("data",list);
                handler.sendMessageToUser(st, new TextMessage(ajaxJson.getJsonStr()));
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

本人小白,刚刚接触,如有不对敬请指正 

参考代码 使用Spring WebSocket实现消息推送_spring websocket 消息转发-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值