Spring Boot学习分享(七)——整合WebSocket开发多个聊天室多人在线实时通信

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/madonghyu/article/details/80474240

(一)使用原生WebSocket的注解编写服务端


由于想要实现可以统计具体在线人数,而因为想要设计成多个房间同时进行,如果使用Spring自带的以Stomp为协议的WebSocket实现则比较繁琐,必须配置拦截器,由于拦截器可以得到的参数比较少,必须注入其它的辅助类来进行房间的确认以及当前人数的统计并发送,所以个人觉得还是直接使用原生的WebSocket进行编程爽一点,不过也可能是我没有想到更好的解决方案,SpringBoot的官方文档也没怎么仔细钻研=。=


下面是具体的实现代码,这里使用注解进行编程,是在网上一个博主的代码上进行更改的。


使用前的一点配置,不加上的话可能将无法成功注册WebSocket服务

/**
 * 对WebSocket进行配置
 * 这里使用Stomp协议
 *
 * @author MDY
 */
@Configuration
public class WebSocketConfig {
    /**
     * 是用编程式WebSocket需要的一个bean
     * 听说是注册端口。。
     */
    @Bean
    public ServerEndpointExporter aaaserverEndpointExporter() {
        return new ServerEndpointExporter();
    }

由于使用的是SpringBoot,自动装载bean的时候可能因为顺序的原因出现各种问题,像是没有得到注解装饰bean为空值之类的。


下面的代码可能会出现bean值为空的情况。

/**
 * 加上RestController注解,使其可以响应文件上传链接
 * 如果不注册成控制层则必须加一个组件扫描,否则将无法注册
 * 所有的message都将使用json进行传输
 * 可以根据链接的直接得到roomName
 * 关于服务层的注入,只能通过Spring容器的上下文来进行赋值,否则会得到空值
 * ChatMessage为自己定义的进行信息传输的类,这里全部将其转为JSON,方便解析
 *
 * @author MDY
 */
@ServerEndpoint(value = "/consult/{roomName}")
@RestController
@Log4j2//这个注解是Lombok的,可以简化代码,不用可以忽略
public class ConsultWebSocket {

    // 使用map来收集session,key为roomName,value为同一个房间的用户集合
    private static final Map<String, Set<Session>> rooms = new ConcurrentHashMap<>();
    //缓存session对应的用户
    private static final Map<String, String> users = new ConcurrentHashMap<>();
    //用来缓存聊天记录的
    private ChatCacheService chatCacheService;
    //进行文件上传具体实现细节的
    private FileService fileService;

    /**
     * 连接创建后将上线的用户广播给组员
     */
    @OnOpen
    public void connect(@PathParam("roomName") String roomName, Session session) throws IOException {

        //目前使用随机名称,可以整合自己的session管理,如shiro之类的
        String name = randomName();

        // 将session按照房间名来存储,将各个房间的用户隔离
        if (!rooms.containsKey(roomName)) {
            // 创建房间不存在时,创建房间
            Set<Session> room = new HashSet<>();
            // 添加用户
            room.add(session);
            rooms.put(roomName, room);
        } else {
            // 房间已存在,直接添加用户到相应的房间
            rooms.get(roomName).add(session);
        }

        users.put(session.getId(), name);

        //向上线的人发送当前在线的人的列表
        List<ChatMessage> userList = new LinkedList<>();
        rooms.get(roomName)
                .stream()
                .map(Session::getId)
                .forEach(s -> {
                    ChatMessage chatMessage = new ChatMessage();
                    chatMessage.setDate(new Date());
                    chatMessage.setUserName("sys");
                    chatMessage.setChatContent(users.get(s) + "在线");
                    userList.add(chatMessage);
                });
        session.getBasicRemote().sendText(JSON.toJSONString(userList));

        //向房间的所有人广播谁上线了
        ChatMessage chatMessage = new ChatMessage();
        chatMessage.setDate(new Date());
        chatMessage.setUserName("sys");
        chatMessage.setChatContent(users.get(session.getId()) + "上线了");
        broadcast(roomName, JSON.toJSONString(chatMessage));
    }

    @OnClose
    public void disConnect(@PathParam("roomName") String roomName, Session session) {
        rooms.get(roomName).remove(session);
        ChatMessage chatMessage = new ChatMessage();
        chatMessage.setDate(new Date());
        chatMessage.setUserName("sys");
        chatMessage.setChatContent(users.get(session.getId()) + "下线了");
        users.remove(session.getId());
        broadcast(roomName, JSON.toJSONString(chatMessage));
        log.info("<<<<<<<<<<<<<a client has disconnected!>>>>>>>>>>>>>>");
    }

    /**
     * @param msg 前台传回来的数据应为json数据
     */
    @OnMessage
    public void receiveMsg(@PathParam("roomName") String roomName,
                           String msg, Session session) {
        // 此处应该有html过滤,进行数据加工
        msg = users.get(session.getId()) + ":" + msg;
        // 接收到信息后进行广播
        broadcast(roomName, msg);
    }

    /**
     * 发送图片,视频,语音等
     *
     * @param name     用户名
     * @param roomName 房间id
     * @param file     上传的文件
     */
    @PostMapping("/consult/{roomName}/{name}")
    public void file(@PathVariable("name") String name, @PathVariable("roomName") String roomName, MultipartFile file) {
        ChatMessage chatMessage = new ChatMessage();
        chatMessage.setDate(new Date());
        chatMessage.setUserName(name);
        chatMessage.setChatContent(fileService.upload(file, roomName));
        broadcast(roomName, JSON.toJSONString(chatMessage));
    }

    // 按照房间名进行广播
    private void broadcast(String roomName, String msg) {
        rooms.get(roomName).forEach(s -> {
            try {
                s.getBasicRemote().sendText(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        //将聊天记录加入缓存
        //这里需要将此服务层的bean手动注入
        if (chatCacheService == null) {
            chatCacheService = ApplicationContextRegister.getApplicationContext().getBean(ChatCacheService.class);
        }
        chatCacheService.cacheMsg(msg, roomName, CacheType.CONSULT);
    }


    //随机姓名
    private String randomName() {
        Random random = new Random();
        String str = "";
        int hightPos, lowPos;
        for (int i = 0; i < 4; ++i) {
            hightPos = (176 + Math.abs(random.nextInt(39)));
            lowPos = (161 + Math.abs(random.nextInt(93)));
            byte[] b = new byte[2];
            b[0] = (Integer.valueOf(hightPos)).byteValue();
            b[1] = (Integer.valueOf(lowPos)).byteValue();
            try {
                str += new String(b, "GB2312");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return str;
    }
}

下面则是前端页面的简单实现,从网上直接找到的

<!DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>网络聊天室</title>
</head>
<style type="text/css">
    .msg_board {
        width: 644px;
        height: 200px;
        border: solid 1px darkcyan;
        padding: 5px;
        overflow-y: scroll;
        // 文字长度大于div宽度时换行显示
        word-break: break-all;
    }
    /*set srcoll start*/
    ::-webkit-scrollbar
    {
        width: 10px;
        height: 10px;
        background-color: #D6F2FD;
    }
    ::-webkit-scrollbar-track
    {
        -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
        /*border-radius: 5px;*/
        background-color: #D6F2FD;
    }
    ::-webkit-scrollbar-thumb
    {
        height: 20px;
        /*border-radius: 10px;*/
        -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
        background-color: #89D7F7;
    }
    /*set srcoll end*/
</style>
<body>
<label>房间名</label>
<input id="input_roomName" size="10" maxlength="10">
<input type="button"  value="进入聊天室" onclick="initWebSocket()" />
<input type="button" value="退出聊天室" onclick="closeWs()" /><br>
<div class="msg_board"></div>
<input id="input_msg" size="43" maxlength="40">
<input type="button" value="发送" onclick="send_msg()" />
</body>
<script type="text/javascript">
    var webSocket;

    function send_msg() {
        if (webSocket != null) {
            var input_msg = document.getElementById("input_msg").value.trim();
            if (input_msg == "") {
                return;
            }
            webSocket.send(input_msg);
            // 清除input框里的信息
            document.getElementById("input_msg").value = "";
        } else {
            alert("您已掉线,请重新进入聊天室...");
        }
    };

    function closeWs() {
        webSocket.close();
    };

    function initWebSocket() {
        var roomName = document.getElementById("input_roomName").value;
        // 房间名不能为空
        if (roomName == null || roomName == "") {
            alert("请输入房间名");
            return;
        }
        if ("WebSocket" in window) {
            if (webSocket == null) {
            //链接和端口号必须要正确,如何还是报404错误,那就是WebSocket服务没有启动
                var url = "ws://localhost:8010/consult/" + roomName;
                // 打开一个 web socket
                webSocket = new WebSocket(url);
            } else {
                alert("您已进入聊天室...");
            }

            webSocket.onopen = function () {
                alert("已进入聊天室,畅聊吧...");
            };

            webSocket.onmessage = function (evt) {
                var msg_board = document.getElementsByClassName("msg_board")[0];
                var received_msg = evt.data;
                var old_msg = msg_board.innerHTML;
                msg_board.innerHTML = old_msg + received_msg + "<br>";
                // 让滚动块往下移动
                msg_board.scrollTop = msg_board.scrollTop + 40;
            };

            webSocket.onclose = function () {
                // 关闭 websocket,清空信息板
                alert("连接已关闭...");
                webSocket = null;
                document.getElementsByClassName("msg_board")[0].innerHTML = "";
            };
        }
        else {
            // 浏览器不支持 WebSocket
            alert("您的浏览器不支持 WebSocket!");
        }
    }
</script>
</html>

最后是Java的简单超实现,这里就不整合SpringBoot了

所需的依赖

        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.3.8</version>
        </dependency>

简单实现,没什么比较坑的地方,直接用就可以了

WebSocketClient webSocketClient = new WebSocketClient(URI.create("ws://localhost:8010/consult/637"), new Draft_6455()) {
            @Override
            public void onOpen(ServerHandshake serverHandshake) {
                System.out.println("链接创建成功");
            }

            @Override
            public void onMessage(String s) {
                System.out.println(s);
            }

            @Override
            public void onClose(int i, String s, boolean b) {
                System.out.println("链接断开");
            }

            @Override
            public void onError(Exception e) {
                System.out.println(e.getMessage());
            }
        };
        webSocketClient.connect();
    }

猜你喜欢

转载自blog.csdn.net/madonghyu/article/details/80474240