SpringBoot与webSocket实现在线聊天室——实现私聊+群聊+聊天记录保存

引用参考:原文章地址:https://blog.csdn.net/qq_41463655/article/details/92410518

在此基础上实现对聊天记录的保存。

代码地址:链接:https://pan.baidu.com/s/1IJFZDa4S_DF08773sKJWeA 提取码:jkui
思路:新建一个实体类用于保存聊天记录,在消息发送时,设置对象的各个值然后保存到数据库中。
实现步骤:
  1. 实体类: 新建实体类UserMessage,根据需要设置对象的字段,如:发送者、接收者、信息内容、发送时间等。
  2. mapper层:写UserMessageMapper接口,继承BaseMapper(我使用的是mybatis-plus,也可以使用其他方法)。
  3. service层:UserMessageService继承IService,service实现类UserMessageServiceImpl继承ServiceImpl<UserMessageMapper, UserMessage> 实现UserMessageService。
  4. controller层:在UserMessageController中的onMessage方法中,将前端传过来的json解析为字段,新建UserMessage对象message1,设置message1对象的各个值,通过消息接收者类型的判断,确实数据库中接收者的值如何设置,然后将UserMessage对象保存到数据库中。
此时,聊天记录保存到数据库时,是一条一条进行保存,为了减少对数据库频繁的操作,在UserMessageController类中定义一个静态的集合MessageList用来暂时保存聊天记录,并且设置MessageList集合的长度为一个固定值不可变,目的是为了实现将聊天记录保存到集合中,当集合的长度等于设定的值时,批量将聊天记录保存到数据库中。
  1. 所以将聊天记录保存到数据库前,先将聊天记录保存到集合MessageList中,判断集合的长度,如果集合的长度等于设定的值,将集合中的数据批量保存到数据库中,然后清空集合,为下一次保存聊天记录做准备。
我在实现保存聊天记录功能时遇到的问题:
  1. 代码没有错,群聊、私聊都可以实现,但是无法保存聊天记录,错:设置的表名与数据库没有对应好。
  2. 保存聊天记录的集合一定要设置为全局,如果是在方法中新建集合,那么聊天记录保存的并不是同一个集合,导致集合的长度永远无法达到设定的值,无法批量保存,导致数据保存失败。
  3. 当聊天页面关闭时,聊天记录的数据就会丢失,为什么不在UserMessageController中的onClose方法中判断在关闭聊天框前,集合中是否有聊天记录,如果有则进行保存??因为设置的集合为全局变量,就算是关闭了聊天页面,聊天记录也会暂时保存到集合中,并不会丢失。
效果展示图:



完整代码实现如下:

包结构图:

代码部分:
webSocket配置类:WebSocketConfig.java
package com.example.springboot_websocket.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @author Administrator*/@Configuration
@Component
public class WebSocketConfig {/*** 服务器节点** 如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
实体类:UserMessage.java 定义实体类与数据库连接,存储聊天记录
package com.example.springboot_websocket.bean;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;/*** @author Administrator*/@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserMessage {/*** 消息发送者*/private String username;/*** 聊天文本*/private String message;/*** 消息接受者*/private String tousername;/*** 发送时间*/private Date createtime;
}
mapper层:UserMessageMapper.java
package com.example.springboot_websocket.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springboot_websocket.bean.UserMessage;
import org.apache.ibatis.annotations.Mapper;/*** extends BaseMapper<T>:继承接口即可实现基本的CRUD方法* <p>* 1.如果自定义的mapper继承了mybatis-plus的BaseMapper时,xxxMapper.xml中不可以包含insert方法,因为在BaseMapper中存在该方法* 2.如果对应的xxxMapper.xml中包含insert方法,那么就会执行xxxMapper.xml中的方法,相当于重写BaseMapper中的insert方法;* 如果xxxMapper.xml中没有insert方法,默认使用BaseMapper中的方法* 3.注意:BaseMapper<T>中的泛型对应相应的实体类*//*** @author Administrator*/@Mapper
public interface UserMessageMapper extends BaseMapper<UserMessage> {}
service层:UserMessageService.java
package com.example.springboot_websocket.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.springboot_websocket.bean.UserMessage;/*** @author Administrator*/
public interface UserMessageService extends IService<UserMessage> {}
service层:UserMessageServiceImpl .java
package com.example.springboot_websocket.service;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.springboot_websocket.bean.UserMessage;
import com.example.springboot_websocket.mapper.UserMessageMapper;
import org.springframework.stereotype.Service;/*** @author Administrator*/@Service
public class UserMessageServiceImpl extends ServiceImpl<UserMessageMapper, UserMessage> implements UserMessageService {}
controller层:UserMessageController.java
package com.example.springboot_websocket.controller;import com.alibaba.fastjson.JSON;
import com.example.springboot_websocket.bean.UserMessage;
import com.example.springboot_websocket.service.UserMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;/*** websocket类** @ServerEndpoint: socket链接地址*//*** @author Administrator*/@ServerEndpoint("/websocket/{username}")
@Controller
@Component
public class UserMessageController {/*** 设置一次性存储数据的list的长度为固定值,每当list的长度达到固定值时,向数据库存储一次*/private static final Integer LIST_SIZE = 3;/*** 设置在线人数为静态变量*/public static int onlineNumber = 0;private static UserMessageService userMessageService;/*** 新建list集合存储数据*/private static ArrayList<UserMessage> MessageList = new ArrayList<>();/*** map(username,websocket)作为对象添加到集合中*/private static Map<String, UserMessageController> clients = new ConcurrentHashMap<String, UserMessageController>();private Logger logger = LoggerFactory.getLogger(this.getClass());/*** session会话*/private Session session;/*** 用户名称*/private String username;public static synchronized int getOnlineCount() {return onlineNumber;}@Autowiredpublic void setOgLocationService(UserMessageService userMessageService) {UserMessageController.userMessageService = userMessageService;}/*** 进入聊天室 --> 项目中读取用户信息获取用户名*/@RequestMapping("/websocket")public String webSocket(Model model) {//根据时间随机定义名称String name = "游客:";String datename = new SimpleDateFormat("msss").format(new Date());name = name + datename;//websock链接地址+游客名-->  项目中请定义在配置文件 -->或直接读取服务器,ip 端口String path = "ws://127.0.0.1:8080/websocket/";model.addAttribute("path", path);model.addAttribute("username", name);return "socket";}/*** 监听连接(有用户连接,立马到来执行这个方法)* session 发生变化** @param session*/@OnOpenpublic void onOpen(@PathParam("username") String username, Session session) {//每打开一个新的窗口,在线人数onlineNumber++onlineNumber++;//把新用户名赋给变量this.username = username;//把新用户的 session 信息赋给变量this.session = session;//输出 websocket 信息logger.info("现在来连接的客户id:" + session.getId() + "用户名:" + username);logger.info("有新连接加入! 当前在线人数" + onlineNumber);try {//把自己的信息加入到map当中去,this=当前类(把当前类作为对象保存起来)clients.put(username, this);//获得所有的用户listsSet<String> lists = clients.keySet();// 发送全体信息(新用户上线信息)//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息Map<String, Object> map1 = new HashMap(100);//  把所有用户列表map1.put("onlineUsers", lists);//  返回上线状态map1.put("messageType", 1);//  返回用户名map1.put("username", username);//  返回在线人数map1.put("number", onlineNumber);//  发送全体信息(用户上线信息)sendMessageAll(JSON.toJSONString(map1), username);// 给自己发一条消息:告诉自己现在都有谁在线Map<String, Object> map2 = new HashMap(100);//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息map2.put("messageType", 3);//把所有用户放入map2map2.put("onlineUsers", lists);//返回在线人数map2.put("number", onlineNumber);// 消息发送指定人(所有的在线用户信息)sendMessageTo(JSON.toJSONString(map2), username);} catch (IOException e) {logger.info(username + "上线的时候通知所有人发生了错误");}}/*** 监听连接断开(有用户退出,会立马到来执行这个方法)*/@OnClosepublic void onClose() {//每关闭一个新的窗口,在线人数onlineNumber--onlineNumber--;//从所有在线用户的map中去除下线用户clients.remove(username);try {//messageType 1代表上线 2代表下线 3代表在线名单  4代表普通消息Map<String, Object> map1 = new HashMap(100);map1.put("messageType", 2);//所有在线用户map1.put("onlineUsers", clients.keySet());//下线用户的用户名map1.put("username", username);//返回在线人数map1.put("number", onlineNumber);//发送信息,所有人,通知谁下线了sendMessageAll(JSON.toJSONString(map1), username);//关闭连接前,判断list集合是否有数据,如果有,批量保存到数据库if (MessageList.size() < LIST_SIZE) {userMessageService.saveBatch(MessageList);}} catch (IOException e) {logger.info(username + "下线的时候通知所有人发生了错误");}logger.info("有连接关闭! 当前在线人数" + onlineNumber);}@OnErrorpublic void onError(Session session, Throwable error) {logger.info("服务端发生了错误" + error.getMessage());//error.printStackTrace();}/*** 监听消息(收到客户端的消息立即执行)** @param message 消息* @param session 会话*/@OnMessagepublic void onMessage(String message, Session session) {try {logger.info("来自客户端消息:" + message + "客户端的id是:" + session.getId());//用户发送的信息com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(message);//发送的内容String textMessage = jsonObject.getString("message");//发送人String fromusername = jsonObject.getString("username");//接收人  to=all 发送消息给所有人 || to= !all   to == 用户名String tousername = jsonObject.getString("to");//新建message对象UserMessage message1 = new UserMessage();//设置发送者的usernamemessage1.setUsername(fromusername);//设置发送的信息message1.setMessage(textMessage);//设置发送时间message1.setCreatetime(new Date());//判断接收者if (tousername.equals("All")) {message1.setTousername("All");} else {message1.setTousername(tousername);}//批量保存信息//将每条记录添加到list集合中MessageList.add(message1);//判断list集合长度if (MessageList.size() == LIST_SIZE) {userMessageService.saveBatch(MessageList);//清空集合MessageList.clear();}//发送消息  -- messageType 1代表上线 2代表下线 3代表在线名单  4代表消息Map<String, Object> map1 = new HashMap(100);map1.put("messageType", 4);map1.put("textMessage", textMessage);map1.put("fromusername", fromusername);if (tousername.equals("All")) {//消息发送所有人(同步)map1.put("tousername", "所有人");sendMessageAll(JSON.toJSONString(map1), fromusername);} else {//消息发送指定人(同步)map1.put("tousername", tousername);sendMessageTo(JSON.toJSONString(map1), tousername);}} catch (Exception e) {logger.info("发生了错误了");}}/*** 消息发送指定人*/public void sendMessageTo(String message, String toUserName) throws IOException {//遍历所有用户for (UserMessageController item : clients.values()) {if (item.username.equals(toUserName)) {//消息发送指定人(同步)item.session.getBasicRemote().sendText(message);break;}}}/*** 消息发送所有人*/public void sendMessageAll(String message, String fromUserName) throws IOException {for (UserMessageController item : clients.values()) {//消息发送所有人(同步)getAsyncRemoteitem.session.getBasicRemote().sendText(message);}}
}
启动类:SpringbootWebsocketApplication.java
package com.example.springboot_websocket;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author Administrator*/
@MapperScan("com.example.springboot_websocket.Mapper")
@SpringBootApplication
public class SpringbootWebsocketApplication {public static void main(String[] args) {SpringApplication.run(SpringbootWebsocketApplication.class, args);}
}
配置文件:application.yml
spring:datasource:username: rootpassword: 123456#创建数据库连接,连接到springboot数据库url: jdbc:mysql://localhost:3306/websocket?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
前端页面:socket.html
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head><meta charset="UTF-8"><meta content="webkit" name="renderer"><meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"><meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport"><link href="../frame/layui/css/layui.css" rel="stylesheet"><link href="../frame/static/css/style.css" rel="stylesheet"><link href="../frame/static/image/code.png" rel="icon"><title>Chatting Room</title><script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.min.js" type="text/javascript"></script><script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script><script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
</head>
<css></css><body><!--socket url-->
<input id="path" style="display: none" th:value="${path}" type="hidden"/>
<!--  用户名 -->
<input id="username" style="display: none" th:value="${username}" type="hidden"/><!-- ===============================================================================================================  -->
<br><!--<input  type="hidden" value="所有人" id="onLineUser" text="所有人" style="display: none" />--><!-- ===============================================================================================================  --><!--    overflow-y :auto;   overflow :auto;  宽高自适应滚动条-->
<h2 align="center">聊天室</h2><span id="miqx"style="width:80%; height:350px; background-color: LightBlue;  float:left; overflow-y :auto; overflow :auto;"><li style="text-align: center">群聊信息</li><hr/>
</span>
<span id="miax"style="width:20%; height:350px; background-color: LightSteelBlue; float:left;overflow-y :auto; overflow :auto;"><li style="text-align: center">在线列表</li><hr/>
</span><textarea cols="35" id="text" placeholder="请输入内容" rows="3"></textarea>
<input onclick="send()" type="button" value="发送"><td>消息发送至:</td>
<select id="onLineUser" size="1" style="width:20%;height:20px"><option value="所有人">所有人</option>
</select><div id="mizx" style="width:80%;height:300px;background-color: LightBlue;float:left;overflow-y :auto;overflow :auto;"><li style="text-align: center">私聊信息</li><hr/>
</div>
<br>
<br>
<br><br><br><!-- ===============================================================================  --></body><script type="text/javascript">function uaername(name) {alert(name)}var miqx = $("#miqx");  //群聊var miax = $("#miax");  //在线列表var mizx = $("#mizx");  //私聊var onLineUser = $("#onLineUser");    //发送人select选择框var webSocket;var commWebSocket;http:if ("WebSocket" in window) {//127.0.0.1:8080/webSocket = new WebSocket(document.getElementById('path').value + document.getElementById('username').value);//连通之后的回调事件webSocket.onopen = function () {//连接成功向页面+消息提示miqx.html(miqx.html() + " <li style='text-align: center'>系统消息:[登陆成功]</li>")};//接收后台服务端的消息webSocket.onmessage = function (evt) {var received_msg = evt.data;           //接收到的数据var obj = JSON.parse(received_msg);    //json数据var messageType = obj.messageType;      //数据类型(1上线/2下线/3在线名单/4发信息)var onlineName = obj.username;         //用户名var number = obj.number;               //在线人数//上线通知+在线列表刷新if (obj.messageType == 1) {if ((onlineName != $("#username").val())) { //展示除不等于自己的所有用户miqx.html(miqx.html() + " <li style='text-align: center'>系统消息:[" + onlineName + "]上线" + "</li>");onLineUser.html(onLineUser.html() + "<option  value='" + onlineName + "'>" + onlineName + "</option>");}var onlineName = obj.onlineUsers;  //所有在线用户miax.html("<li style='text-align: center'>在线用户&nbsp;&nbsp;[" + onlineName.length + "]&nbsp;&nbsp;</li>");for (var i = 0; i < onlineName.length; i++) {if ((onlineName[i] != $("#username").val())) { //展示除不等于自己的所有用户miax.html(miax.html() + "<li style='text-align: left'>&nbsp;&nbsp;" + onlineName[i] + "</li>");}}//miax.html(miax.html()+" <li style='text-align: center'>"+ onlineName +"</li>");}//下线通知+在线列表刷新else if (obj.messageType == 2) {if ((onlineName != $("#username").val())) { //展示除不等于自己的所有用户miqx.html(miqx.html() + " <li style='text-align: center'>系统消息:[" + onlineName + "]下线" + "</li>");}var onlineName = obj.onlineUsers;  //剩余所有在线用户miax.html("<li style='text-align: center'>在线用户&nbsp;&nbsp;[" + onlineName.length + "]&nbsp;&nbsp;</li>");onLineUser.html("<option  value='所有人'>所有人</option>");for (var i = 0; i < onlineName.length; i++) {if ((onlineName[i] != $("#username").val())) { //展示除不等于自己的所有用户miax.html(miax.html() + "<li style='text-align: left'>&nbsp;&nbsp;" + onlineName[i] + "</li>");onLineUser.html(onLineUser.html() + "<option  value='" + onlineName[i] + "'>" + onlineName[i] + "</option>");}}}//在线列表else if (obj.messageType == 3) {var onlineName = obj.onlineUsers; //所有在线用户miax.html("<li style='text-align: center'>在线用户&nbsp;&nbsp;[" + onlineName.length + "]&nbsp;&nbsp;</li>");onLineUser.html("<option  value='所有人'>所有人</option>");for (var i = 0; i < onlineName.length; i++) {if (onlineName[i] != $("#username").val()) { //展示除不等于自己的所有用户miax.html(miax.html() + " <li style='text-align: left'>&nbsp;&nbsp;" + onlineName[i] + "</li>");onLineUser.html(onLineUser.html() + "<option  value='" + onlineName[i] + "'>" + onlineName[i] + "</option>");}}}//信息接收else {var time2 = new Date();var date = time2.getHours() + ":" + time2.getMinutes() + ":" + time2.getSeconds();  //时间if (obj.fromusername != $("#username").val()) {    //自己不接自己的消息if (obj.tousername == "所有人") {//发给所有人miqx.html(miqx.html() + " <li style='text-align: left'>[" + obj.fromusername + "]说:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + obj.textMessage + "</li>");} else {//发给指定人mizx.html(mizx.html() + " <li style='text-align: left'>[" + obj.fromusername + "]说:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + obj.textMessage + "</li>");}}//setMessageInnerHTML(obj.fromusername+"对"+obj.tousername+"说:"+obj.textMessage);}};//连接关闭的回调事件webSocket.onclose = function () {console.log("连接已关闭...");setMessageInnerHTML("连接已经关闭....");};} else {// 浏览器不支持 WebSocketalert("您的浏览器不支持 WebSocket!");}//将消息显示在网页上function setMessageInnerHTML(innerHTML) {document.getElementById('message').innerHTML += innerHTML + '<br/>';}function closeWebSocket() {//直接关闭websocket的连接webSocket.close();}//信息发送+ 页面显示发送信息$(document).keyup(function (event) {//浏览器适应if (event.ctrlKey && event.which == 13 || event.which == 10) {send();} else if (event.shiftKey && event.which == 13 || event.which == 10) {send();}});//信息发送+ 页面显示发送信息function send() {var usernameX = $("#username").val()         //发送数据人var usernameY = $("#onLineUser").val();      //接收数据人var message = $("#text").val();               //发送的数据if (usernameY == "所有人") {usernameY = "All";/*  <li style="text-align: center">群聊信息</li><li style="text-align: right">靠右</li><li style="text-align: left" >靠左</li>*/// miqx.html(miqx.html()+" <li style='text-align: right'>"+ message+" <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ["+usernameX +"]</li>");miqx.html(miqx.html() + " <li style='text-align: right'>" + "[" + usernameX + "]说:&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + message + "</li>");} else {mizx.html(mizx.html() + " <li style='text-align: right'>" + "你对-[" + usernameY + "]说:&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + message + "</li>");}var message = {"message": message,"username": usernameX,"to": usernameY};//发送数据webSocket.send(JSON.stringify(message));$("#text").val("");}layui.use(['form', 'layedit', 'laydate'], function () {var form = layui.form, layer = layui.layer, layedit = layui.layedit, laydate = layui.laydate;//监听指定开关form.on('switch(switchTest)', function (data) {layer.msg('你以' + (this.checked ? '上线' : '下线'), {offset: '6px'});//layer.tips('温馨提示:请注意开关状态的文字可以随意定义,而不仅仅是ON|OFF', data.othis)});});
</script>
</html>

SpringBoot与webSocket实现在线聊天室——实现私聊+群聊+聊天记录保存相关推荐

  1. javaWeb实现聊天室(私聊+群聊)

    写在前面 近几天,迎来了第一个小项目,不做不知道,一做吓一跳.好多知识都掌握的不够扎实,看似会了,但其实似懂非懂,不能真正掌握原理,导致使用起来错误百出.而且深深体会到,知识只有到用时方恨少,一个简单 ...

  2. 基于netty的在线聊天室,支持群聊和私聊——【一】基本功能介绍和nginx配置

    netty虽然可以实现聊天室的功能.但完整的做下来,还是要自己去封装很多东西,尤其是客户端和服务器通信的数据格式,服务端消息派发器的设计.这一点就比spring 的websocket over sto ...

  3. 聊天室私聊php代码,window_聊天室实现私聊(三),聊天室程序是一个application和se - phpStudy...

    聊天室实现私聊(三) 聊天室程序是一个application和session对象结合性很强的asp程序.首先,它比较具有实时性,聊天速度太慢,那么没有人会喜欢的,而且在多人同时发言的时侯,如果程序处理 ...

  4. android下的XMPP对应smack-4.2.1,实现登录,注册,发单聊,加聊天室,发群聊等简单功能

    1.API地址:点击打开链接 2.openfire,spark下载:点击打开链接 3.效果图 4.引用的JAR: compile files('libs/fscontext.jar') compile ...

  5. Netty+Android搭建一个简易聊天室(实现群聊和私聊)

    零,前言 JRBM项目中无论是好友私聊,公开聊天室,还是比赛平台都需要用到长连接,之前没有接触过网络通信等知识,更别说框架了,因此直接上手netty确实有些困难,在前期主要是在b站上看(https:/ ...

  6. 在线聊天室的消息单聊的实现——springboot整合WebSocket(二)

    一.声明 项目的搭建请大家移步到:在线聊天室的消息群聊的实现--springboot整合WebSocket(一) 单聊的实现是在群聊项目上进行延申改造的. 二.引入依赖 <dependency& ...

  7. 完成聊天室的私聊功能

    1 完成聊天室的私聊功能 完成聊天室私聊功能.私聊功能是指,客户端之间可以实现一对一的聊天. 服务器端程序启动后,将等待客户端连接,界面效果如图-1所示: 图-1 客户端程序运行时,需要用户先输入昵称 ...

  8. Java websocket + redis 实现多人单聊天室,多人多聊天室, 一对一聊天

    多人,单聊天室版 FEATURE 多人聊天, 界面简洁美观, 使用ueditor支持发送文字,图片信息 群聊成员列表, 登入登出公告 存储聊天记录, 查看历史消息 技术点 使用CopyOnWriteM ...

  9. Websocket直播间聊天室教程 - GoEasy快速实现聊天室

    最近两年直播那个火啊,真的是无法形容!经常有朋友问起,我想实现一个直播间聊天或者我想开发一个聊天室, 要如何开始呢? 今天小编就手把手的教你用GoEasy做一个聊天室,当然也可以用于直播间内的互动.全 ...

最新文章

  1. 深圳杯---垃圾焚烧厂的经济补偿问题
  2. Franzis CutOut 9 Professional中文版
  3. DIV的id和class
  4. iOS之深入解析objc_msgSend消息转发机制的底层原理
  5. 关于equals和hashcode方法
  6. For input string:
  7. 小红书回应赴港IPO:暂无明确计划
  8. android 加载更多动画效果,Android实践之带加载效果的下拉刷新上拉加载更多
  9. 扫地机自动回充揭秘之科沃斯T8
  10. MySQL 语句使用到的关键字 函数 记录
  11. 计算机原理电梯控制系统设计,基于PLC的电梯控制系统的设计与研究
  12. 股票:巧用均线多头排列选股
  13. 第三章 原位正三和弦的连接
  14. no identity-based policy allows the cloudformation:CreateStack action
  15. #新技能# ps 简单抠图【持续更新】
  16. 在线支付各类错误提示
  17. Unity 升级版本后Shader导致崩溃
  18. 正在等待缓存锁:无法获得锁 /var/lib/dpkg/lock-frontend。锁正由进程 12836(unattended-upgr)持有
  19. CATIA中的拓扑知识(1)基本篇
  20. 计算机设备编号中字母代号对照,设备编号设置方法(一)

热门文章

  1. 奥迪AUDI DELFOR 交付预测报文详解
  2. 渗透测试-地基钓鱼篇-Cobalt Strike钓鱼(二十五)
  3. uefi能重置系统吗_windows uefi怎么重装系统
  4. 快讯:地震灾区婴儿诞生在禅凳上
  5. “云见教育·共享未来”2020云栖大会教育专场顺利举办,前沿技术引领教育创新升级!
  6. 《火星救援》为什么像真的?作者、导演和NASA专家告诉你
  7. 材料科学计算机科学,计算机在材料科学中的应用---完整版.doc
  8. 中国式教育-虎妈猫爸给我的启发
  9. SDUT-1131 C/C++训练1---最大公约数与最小公倍数(JAVA*)
  10. python matplotlib频率分布(累计)直方图