Springboot + WebSocket + HTML简易在线聊天

初衷

在以前的公司呆的时候,只用WebSocket用来做过游戏服务端,只知道怎么用,但没有做过IM类的东西;已经有段时间没接触过长链了,这次就用WebSocket整个简易的聊天,当作复习下。

WebSocket的原理

这个就麻烦大伙自行百度了,这里不用CV大法了。

最终的效果图

在这里插入图片描述

广播和私聊

实现了广播和私聊,指定私聊号就可以单对单聊天,不指定则为群发广播消息。

后端代码

后端用了Springboot框架,因为够快。上代码…

1.WebSocket配置
package com.ssss.websocket.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 开启WebSocket支持
 * Created by L on 2020/6/8.
 */
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
2.WebSocket核心
package com.ssss.websocket.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ssss.websocket.json.MessageJson;
import com.ssss.websocket.utils.MyDateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by L on 2020/6/8.
 */
@ServerEndpoint("/imServer/{username}")
@Component
@Slf4j
public class WebSocketServer {

    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收username
     */
    private String username = "";

    @PostConstruct
    public void init() {
        System.out.println(">>>>>>>>>websocket 加载");
    }

    /**
     * 连接建立成功调用的方法
     *
     * @param session
     * @param username
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        this.session = session;
        this.username = username;
        String sessionId = session.getId();
        if (webSocketMap.containsKey(sessionId)) {
            webSocketMap.remove(sessionId);
            webSocketMap.put(sessionId, this);
        } else {
            webSocketMap.put(sessionId, this);
            addOnlineCount();
        }

        log.info("用户连接:{},当前在线人数为:{}", username, getOnlineCount());

        broadcast("用户:" + username + " 已上线,私聊号为:" + sessionId + "。当前在线人数为:" + getOnlineCount());

    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {

        String sessionId = this.session.getId();
        if (webSocketMap.containsKey(sessionId)) {
            webSocketMap.remove(sessionId);
            subOnlineCount();
        }
        log.info("用户:{} 退出,当前在线人数为:{}", username, getOnlineCount());
        broadcast("用户:" + username + " 已下线,当前在线人数为:" + getOnlineCount());

    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {

        log.info("用户消息:" + username + ",内容:" + message);
        if (StringUtils.isNotBlank(message)) {
            try {
                //解析前端发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
                // 消息类型type: one/all
                String type = jsonObject.getString("type");
                // 消息内容
                String messageModel = jsonObject.getString("message");
                // 消息私聊接收方userId
                String toUserId = jsonObject.getString("toUserId");

                String currentTime = MyDateUtils.getDateToStr(new Date(), MyDateUtils.DATETIME_DEFAULT_FORMAT);
                String fromUserId = session.getId();

                if (StringUtils.isNotEmpty(type) && "all".equals(type)) {
                    //广播
                    webSocketMap.forEach((userId, server) -> {
                        try {
                            MessageJson messageJson = new MessageJson();
                            messageJson.setType(fromUserId.equals(userId) ? "0" : "1");
                            messageJson.setUsername(this.username);
                            messageJson.setMsg(messageModel);
                            messageJson.setCurrentTime(currentTime);
                            server.sendMessage(JSON.toJSONString(messageJson));
                        } catch (IOException e) {
                            log.error("广播请求的userId:{}不在该服务器上", userId);
                        }
                    });
                    return;
                }

                //私聊
                //传送给对应toUserId用户的websocket
                if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
                    MessageJson messageJson = new MessageJson();
                    messageJson.setUsername(this.username);
                    messageJson.setMsg(messageModel);
                    messageJson.setCurrentTime(currentTime);

                    //发送给自己
                    WebSocketServer webSocketServerA = webSocketMap.get(fromUserId);
                    messageJson.setType("0");
                    webSocketServerA.sendMessage(JSON.toJSONString(messageJson));

                    //发送给对方
                    WebSocketServer webSocketServerB = webSocketMap.get(toUserId);
                    messageJson.setType("1");
                    webSocketServerB.sendMessage(JSON.toJSONString(messageJson));
                } else {
                    log.error("请求私聊号toUserId:{}不在该服务器上", toUserId);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 报错
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {

        log.error("用户错误:{},原因:{}", this.username, error.getMessage());
        error.printStackTrace();

    }

    /**
     * 实现服务器主动推送
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {

        this.session.getBasicRemote().sendText(message);

    }

    /**
     * 广播群发消息
     *
     * @param message
     */
    public void broadcast(String message) {

        webSocketMap.forEach((userId, server) -> {
            try {
                server.sendMessage(message);
            } catch (IOException e) {
                log.error("广播请求的userId:{}不在该服务器上", userId);
            }
        });

    }

    /**
     * 发送自定义消息
     *
     * @param message
     * @throws IOException
     */
    public static void sendInfo(String message) throws IOException {
        webSocketMap.forEach((id, server) -> {
            try {
                server.sendMessage("超级管理员:" + message + " | " + MyDateUtils.getDateToStr(new Date(), MyDateUtils.DATETIME_DEFAULT_FORMAT));
            } catch (IOException e) {

            }
        });
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    public static synchronized ConcurrentHashMap<String, WebSocketServer> getMap() {
        return WebSocketServer.webSocketMap;
    }

}

前端代码

前端只用html写了个简单的交互

<html>
<head>
    <title>Springboot+WebSocket的聊天</title>
</head>
<body>
Welcome<br/>
<input type="text" id="nickname"/>
<button onclick="connectWebSocket()">连接WebSocket</button>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
私聊号:<input id="toUserId" type="text" style="width: 50px"/><br/>
消息:<input id="text" type="text" style="width: 500px"/>
<button onclick="send()">发送消息</button>
<hr/>
<div id="message" style="border: 2px solid #979797; width: 50%; height: 400px; overflow-y:auto;"></div>
</body>

<script type="text/javascript">
    var websocket = null;

    function connectWebSocket() {
        var nickname = document.getElementById("nickname").value;
        if (nickname === "") {
            alert("请输入昵称");
            return;
        }

        //判断当前浏览器是否支持WebSocket
        if ('WebSocket' in window) {
            websocket = new WebSocket("ws://127.0.0.1:2306/imServer/" + nickname);
        } else {
            alert('当前浏览器 Not support websocket')
        }

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

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

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

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

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

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        try {
            var jsonObj = JSON.parse(innerHTML);
            var username = jsonObj.username;
            var type = jsonObj.type;

            //消息类型type:0.自己,1.别人
            if ("0" === type) {
                document.getElementById('message').innerHTML += '<div style="text-align: center">' + jsonObj.currentTime + '</div>';// 消息发送时间
                document.getElementById('message').innerHTML += '<div style="text-align: right;">' + "我" + '</div>';// 消息发送人
                document.getElementById('message').innerHTML += '<div style="text-align: right; margin-right: 30px;">' + jsonObj.msg + '</div>';// 内容
                document.getElementById('message').scrollTop = document.getElementById('message').scrollHeight;// 当出现滚动条时,滚动条将自动保持在底部
            } else {
                document.getElementById('message').innerHTML += '<div style="text-align: center">' + jsonObj.currentTime + '</div>';// 消息发送时间
                document.getElementById('message').innerHTML += '<div style="text-align: left;">' + username + '</div>';// 消息发送人
                document.getElementById('message').innerHTML += '<div style="text-align: left; margin-left: 30px;">' + jsonObj.msg + '</div>';// 内容
                document.getElementById('message').scrollTop = document.getElementById('message').scrollHeight;// 当出现滚动条时,滚动条将自动保持在底部
            }
        } catch (e) {
            document.getElementById('message').innerHTML += '<div>' + innerHTML + '</div>';
        }
    }

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

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        var toUserId = document.getElementById('toUserId').value;
        var jsonObj = {message: message, toUserId: toUserId};
        if (toUserId === '') {
            jsonObj.type = 'all';
        } else {
            jsonObj.type = 'one';
        }
        websocket.send(JSON.stringify(jsonObj));
    }
</script>
</html>

运行

1.运行后端Springboot项目

在这里插入图片描述

2.打开html页

在这里插入图片描述
简易的聊天就好了,下面看交互效果。

交互效果

广播聊

开三个窗口,名字分别为:“东东”,“西西”和“灯泡”
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

私聊,通过私聊号

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看出,后面通过了私聊,名为“灯泡”的男子,已经看不到另外两个人的聊天。

总结

例子只是用WebSocket简易建立起实时聊天,还有很多要优化的地方,这个以后有空再处理。源码莫得地方上传,网络被限制了github,没法上传,恶心了,后续再处理。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【资源说明】 1、该资源包括项目的全部源码,下载可以直接使用! 2、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料学习借鉴。 3、本资源作为“参考资料”如果需要实现其他功能,需要能看懂代码,并且热爱钻研,自行调试。 基于springboot在线聊天系统源码+项目说明.zip # Huxin-Project huxin项目是一套聊天系统,包括前台手机界面及后台分布式系统,基于SpringBoot+Netty+MUI+H5Plus+Nginx+FastDFS分布式文件系统搭建的聊天系统。 前端聊天系统包含首页门户登录注册、互信、通讯录、发现、我等模块,添加了扫一扫,朋友圈等功能。 后台管理系统主要实现实时聊天功能。 # huxin ## 说明 > 基于SpringBoot+Netty+MUI+H5Plus+Nginx+FastDFS分布式文件系统搭建的聊天系统,前端聊天系统包含首页门户登录注册、互信、通讯录、发现、我等模块,添加了扫一扫,朋友圈等功能。 后台通信系统主要实现实时聊天功能。 ## 前言 `huxin`项目致力于打造一个完整的聊天系统,采用现阶段流行技术实现。 ## 项目介绍 `huxin`项目是一套聊天系统,包括前台门户系统及后台通信系统,基于SpringBoot+Netty+MUI+H5Plus+Nginx+FastDFS实现。 前台聊天系统包含首页门户登录注册、互信、通讯录、发现、我等模块,添加了扫一扫,朋友圈等功能等模块。 后台通信系统主要实现实时聊天功能。 ### 组织结构 ``` lua huxin ├── huyan-huxin- -- 前端聊天系统接口 ├── huyan-huxin-mybatis -- 基于后台数据层代码生成接口 ├── huyan-huxin-netty -- 后台聊天系统接口 └── huyan-huxin-hello -- 基于聊天功能简单网络编程实现 ``` ### 技术选型 #### 后端技术 技术 | 说明 | 官网 ----|----|---- Spring Boot | 容器+MVC框架 | https://spring.io/projects/spring-boot MyBatis | ORM框架 | http://www.mybatis.org/mybatis-3/zh/index.html MyBatisGenerator | 数据层代码生成 | http://www.mybatis.org/generator/index.html HikariCP | 数据库连接池 | https://github.com/brettwooldridge/HikariCP FastDFS | 对象存储 | https://sourceforge.net/projects/fastdfs/ Nginx | 反向代理服务器 | http://nginx.org/ Netty | 网络编程框架 | https://netty.io/index.html Maven | 项目对象模型 | http://maven.apache.org/ #### 前端技术 技术 | 说明 | 官网 ----|----|---- H5plus | 用于调用手机端功能 | http://www.html5plus.org/ MUI | 原生手机端页面框架 | http://dev.dcloud.net.cn/mui/ #### 架构图 ##### 系统架构图 ![系统架构图](/document/mind/系统架构图.png) ##### 业务架构图 ![业务架构图](/document/mind/业务架构图.png) #### 开发进度 ## 环境搭建 ### 开发工具 工具 | 说明 | 官网 ----|----|---- Eclipse | 开发IDE | https://www.eclipse.org/ X-shell | Linux远程连接工具 | http://www.netsarang.com/download/software.html Navicat | 数据库连接工具 | http://www.formysql.com/xiazai.html Xmind | 思维导图设计工具 | https://www.xmind.net/ ### 开发环境 工具 | 版本号 | 下载 ----|----|---- JDK | 1.8 | https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-213315
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值