SpringBoot整合WebSocket初体验

SpringBoot整合WebSocket初体验

1. 新建项目:

然后手动引一下fastjson就ok了

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

2. 项目结构

所有文件就是这么个摆放

3. 文件:

WebSocketConfig.java

package kim.nzxy.websocketdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @Author: Xiaoyan
 * @Date: 2019/5/27 12:49
 */
@Configuration
public class WebSocketConfig {
    /**
     * 支持websocket
     */
    @Bean
    public ServerEndpointExporter createServerEndExporter() {
        return new ServerEndpointExporter();
    }


    /**
     * 跨域过滤器
     *
     * @return
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }


    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }
}

PageController.java

package kim.nzxy.websocketdemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Author: Xiaoyan
 * @Date: 2019/5/27 15:28
 */
@Controller
public class PageController {
    @RequestMapping("customer")
    public String customer() {
        return "customer";
    }

    @RequestMapping("official")
    public String official() {
        return "official";
    }
}

Message.java

package kim.nzxy.websocketdemo.entity;

import lombok.Data;

/**
 * 这里的Message类并不切合应用,仅仅是为了演示发送一个数据对象
 * 但是我想不出来拿什么举例子了,我项目中实际需要的代码需要保密
 * 如果是纯文本,则会简单很多,自己去百度WebSocket做一个发消息的东西就好了
 *
 * @Author: Xiaoyan
 * @Date: 2019/5/27 12:53
 */
@Data
public class Message {
    private Integer id;
    private String  theme;
    private String  content;
    private String  official;
    private String  customer;
}

MessageDecoder.java

package kim.nzxy.websocketdemo.entity;

import com.alibaba.fastjson.JSON;
import kim.nzxy.websocketdemo.entity.Message;

import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;

/**
 * json2Company
 *
 * @author xy
 */
public class MessageDecoder implements Decoder.Text<Message> {

    @Override
    public void destroy() {
    }

    @Override
    public void init(EndpointConfig arg0) {
    }

    @Override
    public Message decode(String user) {
        return JSON.parseObject(user, Message.class);
    }

    @Override
    public boolean willDecode(String arg0) {
        return true;
    }

}

MessageEncoder

package kim.nzxy.websocketdemo.entity;

import com.alibaba.fastjson.JSON;
import kim.nzxy.websocketdemo.entity.Message;

import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;

/**
 * json2Company
 *
 * @author xy
 */
public class MessageDecoder implements Decoder.Text<Message> {

    @Override
    public void destroy() {
    }

    @Override
    public void init(EndpointConfig arg0) {
    }

    @Override
    public Message decode(String user) {
        return JSON.parseObject(user, Message.class);
    }

    @Override
    public boolean willDecode(String arg0) {
        return true;
    }

}

TestWebSocket.java

package kim.nzxy.websocketdemo.ws;

import kim.nzxy.websocketdemo.entity.Message;
import kim.nzxy.websocketdemo.entity.MessageDecoder;
import kim.nzxy.websocketdemo.entity.MessageEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

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

/**
 * @author xy
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 * @ServerEndpoint 可以把当前类变成websocket服务类
 * id:当前登录对象
 * receive:
 */
@ServerEndpoint(value = "/ws/{role}", decoders = MessageDecoder.class, encoders = MessageEncoder.class)
@Component
@Slf4j
public class TestWebSocket {

    /**
     * 静态变量,用来记录当前客服人员在线连接数。应该把它设计成线程安全的。
     */
    private static int                                      official      = 0;
    /**
     * 同上 客户
     */
    private static int                                      customer      = 0;
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
     */
    private static ConcurrentHashMap<String, TestWebSocket> webSocketMapA = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String, TestWebSocket> webSocketMapB = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private        Session                                  session;
    /**
     * 当前发消息的人员编号
     */
    private        String                                   userId        = "";
    /**
     * 当前发消息的人对应的开票员
     */
    private        java.lang.String                         receiveId     = "";

    private static synchronized void addA() {
        official++;
    }

    private static synchronized void addB() {
        customer++;
    }

    private static synchronized void subA() {
        official--;
    }

    private static synchronized void subB() {
        customer--;
    }

    private static synchronized int getA() {
        return official;
    }

    private static synchronized int getB() {
        return customer;
    }

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "role") String role) {
        this.session = session;
        userId = UUID.randomUUID().toString();
        // 在线数加1,a代表客服
        if ("a".equals(role)) {
            // 客服的id为数字,这样就可以区分客服和用户了,这里假设不会重复
            // 这里随机的用户id,所以不方便重连,正式环境大概要和用户表结合
            addA();
            webSocketMapA.put(userId, this);
        } else {
            userId = UUID.randomUUID().toString();
            addB();
            webSocketMapB.put(userId, this);
        }
        // 这里可以加一个判断,如果webSocketMap.containsKey(id),则系统重新指定一个id
        log.info("新连接: " + userId + "----当前客服人数:" + getA() + "----当前客户人数:" + getB());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        // 从map中删除
        if (webSocketMapA.containsKey(userId)) {
            webSocketMapA.remove(userId);
            // 在线数减1
            subA();
            log.info("客服下线:" + userId + "----当前客服人数:" + getA() + "----当前客户人数:" + getB());
        } else {
            webSocketMapB.remove(userId);
            subB();
            log.info("客户下线:" + userId + "----当前客服人数:" + getA() + "----当前客户人数:" + getB());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(Message message, @PathParam(value = "role") String role) {
        System.out.println(message);
        if ("a".equals(role)) {
            receiveId = message.getCustomer();
            if (message.getCustomer() == null || !webSocketMapB.containsKey(receiveId)) {
                webSocketMapA.get(userId).sendMessage("客户已下线");
            } else {
                webSocketMapB.get(receiveId).sendObj(message);
            }
        } else {
            if (message.getOfficial() != null) {
                if (webSocketMapA.containsKey(message.getOfficial())) {
                    webSocketMapA.get(message.getOfficial()).sendObj(message);
                } else {
                    webSocketMapB.get(userId).sendMessage("当前客服已下线,请换个客服人员重新咨询");
                }
            } else {
                if (webSocketMapA.size() == 0) {
                    webSocketMapB.get(userId).sendMessage("当前无在线客服");
                } else {
                    // 系统随机指定客服,正式环境应当判断客服接应人数然后再进行指配
                    int i = new Random().nextInt(webSocketMapA.size());
                    message.setOfficial(webSocketMapA.keySet().toArray(new String[0])[i]);
                    message.setCustomer(userId);
                    webSocketMapA.get(message.getOfficial()).sendObj(message);
                }
            }
        }
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Throwable error) {
        log.info(error.toString());
    }

    /**
     * 发送对象到客户端
     */
    private void sendObj(Message message) {
        try {
            this.session.getBasicRemote().sendObject(message);
        } catch (EncodeException | IOException e) {
            log.info("错误:由用户" + userId + "向" + receiveId + message.toString() + "具体错误为:" + e.toString());
        }
    }

    /**
     * 发送文本到客户端
     */
    private void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.info("错误:由用户" + userId + "向" + receiveId + message + "具体错误为:" + e.toString());
        }
    }
}

WebSocketApplication.java

package kim.nzxy.websocketdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebsocketDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebsocketDemoApplication.class, args);
    }

}

4. 前端代码

前端代码颇为简单,而且可以抽取一个js文件,但是由于功能增加后肯定就不方便抽取了,而且为了看代码方便,此处就保留这样

customer.html

<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
          name="viewport">
    <meta content="ie=edge" http-equiv="X-UA-Compatible">
    <title>客户的页面</title>

</head>
<body>
<div id="hi">

</div>
<input placeholder="主题" type="text">
<input placeholder="内容" type="text">
<button onclick="send()" type="button">发送</button>
</body>
<script>
    let websocket = null;

    let LData = {};

    //判断当前浏览器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost/ws/b");
    } else {
        alert('当前浏览器不支持\n请更换浏览器');
    }

    //发送消息
    function send() {
        let ins = document.getElementsByTagName("input");
        LData['theme'] = ins[0].value;
        LData['content'] = ins[1].value;
        websocket.send(JSON.stringify(LData));
    }

    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        let data = event.data;
        console.log(data);
        if ("{" === data.substr(0, 1)) {
            LData = JSON.parse(data);
            console.log('LData' + LData);
            document.getElementById("hi").innerHTML += LData['theme'] + '----' + LData['content'] + '\n';
        } else {
            document.getElementById("hi").innerHTML += data + '\n';
        }
    };

    //连接成功建立的回调方法
    websocket.onopen = function () {
        console.log("onopen...");
    };

    //连接关闭的回调方法
    websocket.onclose = function () {
        console.log("onclose...");
    };

    //连接发生错误的回调方法
    websocket.onerror = function () {
        alert("连接服务器失败,请刷新页面重试")
    };

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

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }
</script>
</html>

official.html

<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
          name="viewport">
    <meta content="ie=edge" http-equiv="X-UA-Compatible">
    <title>客服的页面</title>
</head>
<body>
<pre id="hi">
</pre>
<input placeholder="主题" type="text">
<input placeholder="内容" type="text">
<button onclick="send()" type="button">发送</button>
</body>
<script>
    let websocket = null;
    // 用来保留客户信息用的
    let LData = {};
    //判断当前浏览器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost/ws/a");
    } else {
        alert('当前浏览器不支持\n请更换浏览器');
    }

    //发送消息
    function send() {
        let ins = document.getElementsByTagName("input");
        LData['theme'] = ins[0].value;
        LData['content'] = ins[1].value;
        console.log(LData);
        websocket.send(JSON.stringify(LData));
    }

    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        let data = event.data;
        console.log("收到:"+data);
        if ("{" === data.substr(0, 1)) {
            LData = JSON.parse(data);
            console.table(LData);
            document.getElementById("hi").innerHTML += LData['theme'] + '----' + LData['content'] + '\n';
        } else {
            document.getElementById("hi").innerHTML += data + '\n';
        }
    };

    //连接成功建立的回调方法
    websocket.onopen = function () {
        console.log("onopen...");
    };

    //连接关闭的回调方法
    websocket.onclose = function () {
        console.log("onclose...");
    };

    //连接发生错误的回调方法
    websocket.onerror = function () {
        alert("连接服务器失败,请刷新页面重试")
    };

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

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }
</script>
</html>

猜你喜欢

转载自www.cnblogs.com/liangyun/p/10932018.html