先看效果:这里面demo用的是原生js跟html,方便打包一体化,在效果上我更倾向于使用vue、react等进行页面开发
还有很多可以优化的点,目前的名字我直接使用的获取时间戳并且没有提供名字跟头像的自定义功能,后续再优化吧,在打包的时候遇到了错误:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [com/an/websocket/config/WebSocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
很离谱的居然是单元测试类的注解问题
改为
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
就可以打包了
我针对上面的文件调整了boot自带的tomcat插件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
</dependencies>
可以先看我之前的集成websocket的入门文章进行搭建工程,这里我直接粘贴业务代码,config配置不写了
package com.an.websocket.socket;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author
* @title: WebSocketChatroom
* @projectName me_lab
* @description: TODO
* @date 2022/2/10 0010下午 17:48
*/
@ServerEndpoint(value = "/websocketChatroom/{info}")
@Component
public class WebSocketChatroom {
private static SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//创建时间格式对象
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//创建一个房间的集合,用来存放房间
private static ConcurrentHashMap<String,ConcurrentHashMap<String, WebSocketChatroom>> roomList = new ConcurrentHashMap<String,ConcurrentHashMap<String, WebSocketChatroom>>();
//重新加入房间的标示;
private int rejoin = 0;
static {
roomList.put("1704", new ConcurrentHashMap<String, WebSocketChatroom>());
}
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(@PathParam(value = "info") String param, Session session) throws IOException {
System.out.println(session.getId()+"链接了");
System.out.println(param);
this.session = session;
String flag = param.split("[|]")[0]; //标识
String member = param.split("[|]")[1]; //放假id
if(flag.equals("join")){
String user = param.split("[|]")[2];
joinRoom(member,user);
JSONObject jsonObject = new JSONObject();
jsonObject.put("flag",flag);
jsonObject.put("nickname",user);
jsonObject.put("message","");
jsonObject.put("member",member);
this.session.getBasicRemote().sendText(jsonObject.toString());
}
}
//加入房间
public void joinRoom(String member,String user){
ConcurrentHashMap<String, WebSocketChatroom> r = roomList.get(member);
if(r.get(user) != null){ //该用户有没有出
this.rejoin = 1;
}
r.put(user, this);//将此用户加入房间中
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
//把用户发来的消息解析为JSON对象
JSONObject obj = JSONObject.fromObject(message);
if(obj.get("flag").toString().equals("exitroom")){ //退出房间操作
String roomid = obj.get("roomid").toString();
//将用户从聊天室中移除
int f2 = 1;
roomList.get(roomid).remove(obj.get("nickname").toString());//将用户直接移除
if(roomList.get(roomid).size() == 0){//判断房间该房间是否还有用户,如果没有,则将此房间也移除
f2 = 2;
}
if(f2 == 1){ //证明该房间还有其它成员,则通知其它成员更新列表
obj.put("flag","exitroom");
String m = obj.get("nickname").toString()+" 退出了房间";
obj.put("message", m);
ConcurrentHashMap<String, WebSocketChatroom> r =roomList.get(roomid);
List<String> uname = new ArrayList<String>();
for(String u:r.keySet()){
uname.add(u);
}
obj.put("uname", uname.toArray());
for(String i:r.keySet()){ //遍历该房间
r.get(i).sendMessage(obj.toString());//调用方法 将消息推送
}
}
}else if(obj.get("flag").toString().equals("chatroom")){ //聊天室的消息 加入房间/发送消息
//向JSON对象中添加发送时间
obj.put("date", df.format(new Date()));
//获取客户端发送的数据中的内容---房间�? 用于区别该消息是来自于哪个房间
String roomid = obj.get("target").toString();
//获取客户端发送的数据中的内容---用户
String username = obj.get("nickname").toString();
//从房间列表中定位到该房间
ConcurrentHashMap<String, WebSocketChatroom> r =roomList.get(roomid);
List<String> uname = new ArrayList<String>();
for(String u:r.keySet()){
uname.add(u);
}
obj.put("uname", uname.toArray());
if(r.get(username).rejoin == 0){ //证明不是退出重连
for(String i:r.keySet()){ //遍历该房间
obj.put("isSelf", username.equals(i));//设置消息是否为自己的
r.get(i).sendMessage(obj.toString());//调用方法 将消息推送
}
}else{
obj.put("isSelf", true);
r.get(username).sendMessage(obj.toString());
}
r.get(username).rejoin = 0;
}
}
}
因为使用的原生html所以使用的样式也是单独的css文件没有进行其他框架的集成,直接粘贴css文件即可
@CHARSET "UTF-8";
html{
height: 100%;
}
body{
margin: 0;
padding:0;
height: 100%;
overflow: hidden;
}
.body-left{
float: left;
color: white;
}
.body-right{
float: right;
}
.body-left,.body-right{
background-color: #101010;
display:inline-block;
width:25%;
height: 100%;
}
.body-center{
height: 100%;
display:inline-block;
width: 50%;
}
.center-info{
height: 77%;
width:88%;
margin: 10px auto;
padding: 5px 20px;
border:1px #A9A9A9 dashed;
border-radius: 15px;
overflow: auto;
overflow-x:hidden;
}
.center-input{
display:block;
height: 10%;
width:92.2%;
margin: 0px auto;
padding: 5px 20px;
resize: none;
font-size: 20px;
border:1px #A9A9A9 dashed;
border-bottom: none;
outline: none;
border-radius: 15px 15px 0 0;
overflow-x:hidden;
}
.ensure{
height:4%;
width: 88%;
margin: 0px auto;
padding:2px 20px;
border: 1px #A9A9A9 dashed;
border-radius: 0 0 15px 15px;
border-top:none;
}
.ensure button{
float: right;
background-color: #4eacfb;
border: none;
border-radius: 5px;
border-top:none;
width: 15%;
height: 25px;
}
.basicInfo{
clear: both;
max-width:80%;
/* width: 80%; */
margin:10px 0;
}
.basicInfo-left{
width: 80px;
height: 80px;
}
.basicInfo-left img{
width: 100%;
height: 100%;
border-radius: 50px;
}
.basicInfo-right{
max-width:76%;
/* width:80%; */
padding:2px 12px;
}
.username{
color: #8b8b8b;
}
.context{
max-width:99%;
padding:0 3px;
background-color:#cdd7e2;
margin-top: 5px;
border-radius: 5px;
word-wrap:break-word;
word-break:break- all;
}
.welcome{
clear:both;
text-align: center;
}
.body-right img{
width: 22%;
position:fixed;
bottom:2%;
right: 2%;
}
.left-info{
padding: 15px 54px;
}
.memberInfo{
height: 50px;
line-height: 50px;
}
.userpic{
border-radius: 20px;
height: 50px;
float: left;
}
.userpic img{
width:50px;
height:50px;
border-radius: 50px;
margin-right: 15px;
}
.exitroom{
margin-top: 12px;
}
.exitroom:HOVER {
color:red;
}
jquery-3.2.1.min.js
里面使用了html的append操作来进行内容记录跟样式调整
<html>
<head>
<base href="<%=basePath%>" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/chat.css" />
<title>聊天室</title>
</head>
<script type="text/javascript">
$(function(){
$(".uname").append((new Date()).valueOf())
var roomid=$(".roomid").html();//房间名
var nickname = $(".uname").html();//自己的昵称
var flag = "join";
var info = flag + "|" +roomid + "|" +nickname;
//建立一条与服务器之间的连接
var socket = new WebSocket("ws://localhost:8080/websocketChatroom/"+info);
var text = "";
var welcome = JSON.stringify({ //加入房间时的欢迎消息
nickname:nickname, //用户名
content:text, //消息内容
target:roomid, //推送到目标房间
flag:"chatroom"}); //推送标识
var exitroom = JSON.stringify({ //退出房间
nickname:nickname,
flag:"exitroom",
roomid:roomid
})
//接收服务器的消息
socket.onmessage=function(ev){
var obj = eval( '('+ev.data+')' );
addMessage(obj)
};
//当服务端执行onopen后触发此方法
socket.onopen = function(){
socket.send(welcome);
};
//发送按钮被点击时
$(".ensure button").click(function(){
ensure();
});
$("body").keyup(function (event) {//监听回车键
if (event.keyCode == "13") {//keyCode=13是回车键
$(".ensure button").trigger("click");
}
});
function ensure(){
//获取输入框的内容
var txt = $(".center-input").val()
if(txt==''){
alert("不能发送空内容")
}else{
//构建一个标准格式的JSON对象
var obj = JSON.stringify({
nickname:nickname, //用户名
content:txt, //消息内容
flag:'chatroom', //标识--chatroom代表是聊天室的消息
target:roomid //消息推送的目的地
});
// 向服务器发送消息
socket.send(obj);
// 清空消息输入框
$(".center-input").val("")
// 消息输入框获取焦点
$(".center-input").focus();
}
}
function addMessage(msg){
if(msg.isSelf&&msg.content==""){ //该消息是自己发送的,并且内容为空
$(".center-info").append("<div class='welcome'>欢迎你加入群聊</div>");
refreshMember(msg.uname); //刷新成员
}
if(!msg.isSelf&&msg.content==""){//该消息是别人发送的,并且内容为空
$(".center-info").append("<div class='welcome'>欢迎"+msg.nickname+"加入群聊</div>");
//刷新成员列表
refreshMember(msg.uname)
}
if(!msg.content==""){ //内容不为空时
var align;
if(msg.isSelf){
align = "right";
}else{
align = "left";
}
$(".center-info").append(
"<div class='basicInfo' style=float:"+align+">"+
"<div class='basicInfo-left' style=float:"+align+">"+
"<img src='img/touxiang.jpg'>"+
"</div>"+
"<div class='basicInfo-right' style=float:"+align+">"+
"<div class='username' style=text-align:"+align+">"+
"<span>"+msg.nickname+"</span> "+
"<span>"+msg.date+"</span>"+
"</div>"+
"<div class='context'>"+
"<span>"+
msg.content+
"</span>"+
"</div>"+
"</div>"+
"</div>"
);
}
if(msg.flag == "exitroom"){ //退出房间
$(".center-info").append("<div class='welcome'>"+msg.message+"</div>");
//刷新成员列表
refreshMember(msg.uname)
}
$(".center-info").scrollTop(999999); //让滚动条始终保持在最下
}
$(".exitroom").click(function(){ //退出房间
socket.send(exitroom); //向服务器发送退出房间的信号
location.href="/Chatroom/home/list.do"; //跳转到前一个页面
})
function refreshMember(data){
$(".member").html("");
for(var i=0;i<data.length;i++){
$(".member").append(
"<div class='memberInfo'>"+
"<div class='userpic'>"+
"<img src='img/touxiang.jpg'>"+
"</div>"+
"<span class='username'>"+data[i]+"</span>"+
"</div>"
)
}
}
})
</script>
<body>
<div class="body-left">
<div class="left-info">
<div class="roomname">
欢迎来到:<h1 style="display: inline-block;" class="roomid">1704</h1>
</div>
<div class="member">
<div class="memberInfo">
<span class="username">ddd</span>
</div>
</div>
</div>
</div>
<div class="body-center">
<div class="center-info">
</div>
<textarea class="center-input"></textarea>
<div class="ensure">
<button>发送</button>
</div>
</div>
<div class="body-right">
</div>
<span class="uname" style="display:none"></span>
</body>
</html>