JFinal4.8集成undertow-websockets

       最近做项目需要后端实时推送消息给前端指定用户,比较理想的方案是集成websocket。网上找了一下,不是太老了,就是比较凌乱,基本都是集成tomcat的,没有集成JFinal推荐的undertow-websockets。最后结合官网文档和其他网友经验,终于搞定,发上来供其他人参考。

【后端代码】

1、添加undertow和对应webscoket集成到pom.xml

<!-- jfinal-undertow 开发、部署一体化 web 服务器 -->
<dependency>
    <groupId>com.jfinal</groupId>
    <artifactId>jfinal-undertow</artifactId>
    <version>2.0</version>
</dependency>

<!-- 开发 WebSockets 时开启下面的依赖 -->
<dependency>
    <groupId>io.undertow</groupId>
    <artifactId>undertow-websockets-jsr</artifactId>
    <version>2.0.28.Final</version>
</dependency>

2、在undertow启动添加websocket集成

public static void main(String[] args) {
    UndertowServer.create(AppConfig.class)
            .configWeb(builder -> {
                // 配置WebSocket需使用ServerEndpoint注解
                builder.addWebSocketEndpoint(WebSocket.class);
            })
            .start();
    System.out.println("系统服务启动完成.......");
}

备注:AppConfig.class替换为自己的配置文件,WebSocket.class为后面添加的websocket方法

3、不拦截websocket的访问(AppConfig.class配置文件)

public void configHandler(Handlers me) {
    // 不拦截websocket
    me.add(new UrlSkipHandler("^/websocket.ws", false));
}

4、编写WebSocket.class的方法(代码可以直接使用,不过代码没考虑session分布式,只是单服务器用)

import com.alibaba.fastjson.JSONObject;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 〈webSocket功能〉
 *
 * @author foam103
 * @create 2020/3/15
 */
@ServerEndpoint("/websocket.ws/{userId}")
public class WebSocket {

    private static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();
    private Session session;
    private String userId;

    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) throws IOException {

        this.userId = userId;
        this.session = session;

        clients.put(userId, this);
        //System.out.println("已连接");
    }

    @OnClose
    public void onClose(Session session) {
        clients.remove(userId);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    @OnMessage
    public void onMessage(String message) {
        // message是json格式
        JSONObject obj = JSONObject.parseObject(message);
        String user = obj.get("userId").toString();
        String mes = obj.get("message").toString();
        // 判断是否在线,如果在线发送信息
        for (WebSocket item : clients.values()) {
            if (item.userId.equals(user)) {
                item.session.getAsyncRemote().sendText(mes);
            }
        }
    }
}

5、其他业务地方触发消息发送(message可以用json格式,这样便于传递更多信息到前端)

// 引用websocket
private WebSocket ws = new WebSocket();
// websocket推送回复消息
ws.onMessage("{\"userId\":\"" + receiver + "\",\"message\":{\"tp\":\"" + type + "\",\"mes\":\"" + content + "\"}}");

【前端代码】

1、编写js代码(setMessage方法需要自己写,其他的不动)

// websocket方法
function ws(userId) {
    var websocket = null;
    var host = document.location.host;
    var Protocol = window.location.protocol.split(':')[0];

    //判断当前浏览器是否支持WebSocket
    if (window.WebSocket) {
        if (Protocol == 'https') {
            websocket = new WebSocket('wss://' + host + '/websocket.ws/' + userId);
        } else {
            websocket = new WebSocket('ws://' + host + '/websocket.ws/' + userId);
        }
    } else {
        alert('当前浏览器不支持websocket');
    }

    //连接发生错误的回调方法
    websocket.onerror = function () {
        //alert('WebSocket连接发生错误');
    };

    //连接成功建立的回调方法
    websocket.onopen = function () {
        //alert('WebSocket连接成功');
    }

    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        setMessage(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function () {
        //alert('WebSocket连接关闭');
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        websocket.close();
    }

    //将消息显示在网页上
    function setMessage(message) {
        //这里解析message,然后用js赋值给前端就行

    }
}

2、前端html页面调用

<script type="text/javascript" src="/plugins/js/ws.js"></script>

// 适当的地方加载websocket
ws(userid);

【结束语】

       代码是最简单模式,很方便读懂,供大家参考。如有不完善,请大家多指教。

评论区

JFinal

2020-03-16 11:35

比官方文档在详细,已经在官方文档中添加了对本篇分享的链接,感谢分享

foam103

2020-03-16 19:39

@JFinal 波总,我看文档里说websocket.ws这样写法(带.ws)可以不用写UrlSkipHandler,不知道是不是,为了保险起见我还是写上了。

JFinal

2020-03-16 21:44

@foam103 带 .ws 就可以不需要 UrlSkipHandler,因为带 "." 字符的 url 不会被 jfinal 框架当成 action,所以自然也就跳过去了