前边用websocket写了一个一对一聊天的demo,但是没有前端的界面,后来找了一个前端的插件JqueryChat,把前端的界面也整出来了。下面先把demo整出来。
开发环境:
jdk8
springboot2.1.4
STS
maven
涉及技术:
HTML5
WebSocket
thmeleaf
Jquery
JqueryChat
首先,在pom中引入必须的架包;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- springboot和websocket的集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
</dependency>
第二,配置websocket的配置文件;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* websocket配置文件配置
* @author 程就人生
* @date 2019年9月23日
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
第三,websocket连接的建立、断开,信息的收发;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.vo.User;
import com.example.demo.vo.WebSocketMsg;
/**
* WebSocket服务端,模拟在线聊天
* @author 程就人生
* @date 2019年9月23日
*/
@ServerEndpoint("/websocket3/{userUid}")
@Component
public class WebSocketServer3 {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static Logger log = LoggerFactory.getLogger(WebSocketServer3.class);
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<WebSocketServer3> webSocketSet = new CopyOnWriteArraySet<WebSocketServer3>();
//用来存储在线的人
private static Map<String,Session> userWsSession = new HashMap<String,Session>();
//用来展示已经上线的人
private static Map<String,User> userInfoMap = new HashMap<String,User>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private String userUid;
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(@PathParam("userUid") String userUid,Session session) {
User userInfo = new User();
userInfo.setNickName("nickName" + userUid);
if(userUid.equals("1")){
userInfo.setUserLogo("icon01.png");
}else if(userUid.equals("2")){
userInfo.setUserLogo("icon02.png");
}else{
userInfo.setUserLogo("icon03.png");
}
userInfo.setUserUid(userUid);
this.userUid = userUid;
userWsSession.put(userUid, session);
userInfoMap.put(userUid, userInfo);
webSocketSet.add(this); //加入set中
log.info("有新连接加入!当前在线人数为" + webSocketSet.size());
WebSocketMsg wssrm = null;
//把当上线的人通知到每一个已经在线的人
for (String useruid : userInfoMap.keySet()) {
wssrm = new WebSocketMsg();
wssrm.setMsgType("connectInit");
wssrm.setMsg(JSONObject.toJSONString(userInfoMap));
sendMessage(JSONObject.toJSONString(wssrm),useruid);
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
//移除在线的人
userWsSession.remove(userUid);
userInfoMap.remove(userUid);
//从set中删除
webSocketSet.remove(this);
//把下线的人通知到每一个在线的人
WebSocketMsg wssrm = null;
for (String useruid : userInfoMap.keySet()) {
wssrm = new WebSocketMsg();
wssrm.setMsgType("connectInit");
wssrm.setMsg(JSONObject.toJSONString(userInfoMap));
sendMessage(JSONObject.toJSONString(wssrm),useruid);
}
log.info("有一连接关闭!当前在线人数为" + webSocketSet.size());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("来自客户端的消息:" + message);
WebSocketMsg wssrm = JSONObject.toJavaObject(JSONObject.parseObject(message), WebSocketMsg.class);
if("img".equals(wssrm.getMsgType())){
wssrm.setMsgType("img");
}else{
wssrm.setMsgType("common");
}
//从数据库中获取用户信息省略
//User fromUser = userService.findByLoginName(wsRequestMsg.getFromUser());
User fromUser = new User();
fromUser.setNickName("nickName" + userUid);
if(userUid.equals("1")){
fromUser.setUserLogo("icon01.png");
}else if(userUid.equals("2")){
fromUser.setUserLogo("icon02.png");
}else{
fromUser.setUserLogo("icon03.png");
}
fromUser.setUserUid(userUid);
wssrm.setFromUser(JSONObject.toJSONString(fromUser));
wssrm.setMsg(wssrm.getMsg());
sendMessage(JSONObject.toJSONString(wssrm), wssrm.getToUser());
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 发送消息到指定的人
* @param message
* @param user
*
*/
@SuppressWarnings("static-access")
public void sendMessage(String message,String userUid) {
try{
this.userWsSession.get(userUid).getBasicRemote().sendText(message);
}catch(Exception e){
e.printStackTrace();
}
}
}
第四,前端页面的展示、信息的收发;
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" type="text/css" th:href="@{/jqueryChat/font_Icon/iconfont.css}">
<link rel="stylesheet" type="text/css" th:href="@{/jqueryChat/css/chat.css}">
</head>
<body>
<div class="chatContainer">
<div class="chatBtn">
<i class="iconfont icon-xiaoxi1"></i>
</div>
<div class="chat-message-num"></div>
<div class="chatBox" ref="chatBox">
<div class="chatBox-head">
<div class="chatBox-head-one">
在线列表
<div class="chat-close" style="margin: 10px 10px 0 0;font-size: 14px">关闭</div>
</div>
<div class="chatBox-head-two">
<div class="chat-return">返回</div>
<div class="chat-people">
<div class="ChatInfoHead">
<img src="" alt="头像" id="friendLogo" />
</div>
<div class="ChatInfoName" id="friendName" >这是用户的名字,看看名字到底能有多长</div>
</div>
<div class="chat-close">关闭</div>
</div>
</div>
<div class="chatBox-info">
<div id="chatBoxList" class="chatBox-list" ref="chatBoxlist">
<!-- 当前在线的人员 -->
</div>
<div class="chatBox-kuang" ref="chatBoxkuang">
<div id="chatBox-content-Id" class="chatBox-content">
</div>
<div class="chatBox-send">
<div id="wsMsgContent" class="div-textarea" contenteditable="true"></div>
<div>
<button id="chat-biaoqing" class="btn-default-styles">
<i class="iconfont icon-biaoqing"></i>
</button>
<label id="chat-tuxiang" title="发送图片" for="inputImage" class="btn-default-styles">
<input type="file" onchange="selectImg(this)" accept="image/jpg,image/jpeg,image/png"
name="file" id="inputImage" class="hidden">
<i class="iconfont icon-tuxiang"></i>
</label>
<button id="chat-fasong" class="btn-default-styles"><i class="iconfont icon-fasong"></i>
</button>
</div>
<div class="biaoqing-photo">
<ul>
<li><span class="emoji-picker-image" style="background-position: -9px -18px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -40px -18px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -71px -18px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -102px -18px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -133px -18px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -164px -18px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -9px -52px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -40px -52px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -71px -52px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -102px -52px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -133px -52px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -164px -52px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -9px -86px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -40px -86px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -71px -86px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -102px -86px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -133px -86px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -164px -86px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -9px -120px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -40px -120px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -71px -120px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -102px -120px;"></span>
</li>
<li><span class="emoji-picker-image" style="background-position: -133px -120px;"></span>
</li>
<li><span class="emoji-picker-image" style="background-position: -164px -120px;"></span>
</li>
<li><span class="emoji-picker-image" style="background-position: -9px -154px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -40px -154px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -71px -154px;"></span></li>
<li><span class="emoji-picker-image" style="background-position: -102px -154px;"></span>
</li>
<li><span class="emoji-picker-image" style="background-position: -133px -154px;"></span>
</li>
<li><span class="emoji-picker-image" style="background-position: -164px -154px;"></span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="http://www.jq22.com/jquery/jquery-1.10.2.js"></script>
<script th:inline="javascript" >
$().ready(function (){
//创建websocket
createWebSocket();
screenFuc();
(window.onresize = function () {
screenFuc();
})();
//总的未读消息,初始化都为0
var totalNum = $(".chat-message-num").html();
if (totalNum == "") {
$(".chat-message-num").css("padding", 0);
}
//每一个对应的人,未读消息
$(".message-num").each(function () {
var wdNum = $(this).html();
if (wdNum == "") {
$(this).css("padding", 0);
}
});
//打开/关闭聊天框
$(".chatBtn").click(function () {
$(".chatBox").toggle(10);
})
$(".chat-close").click(function () {
$(".chatBox").toggle(10);
})
//返回列表
$(".chat-return").click(function () {
$(".chatBox-head-one").toggle(1);
$(".chatBox-head-two").toggle(1);
$(".chatBox-list").fadeToggle(1);
$(".chatBox-kuang").fadeToggle(1);
});
//发送信息回车键
$(".chatBox").toggle(10);
$(document).keydown(function(event){
if(event.keyCode==13){
$("#chat-fasong").click();
}
});
//发送表情
$("#chat-biaoqing").click(function () {
$(".biaoqing-photo").toggle();
});
$(document).click(function () {
$(".biaoqing-photo").css("display", "none");
});
$("#chat-biaoqing").click(function (event) {
event.stopPropagation();//阻止事件
});
//发送信息
$("#chat-fasong").click(function () {
var textContent = $(".div-textarea").html().replace(/[\n\r]/g, '<br>')
if (textContent != "") {
$("#chatBox-content-demo"+toUser).append("<div class=\"clearfloat\">" +
"<div class=\"author-name\"><small class=\"chat-date\">"+getNowTime()+"</small> </div> " +
"<div class=\"right\"> <div class=\"chat-message\"> " + textContent + " </div> " +
"<div class=\"chat-avatars\"><img src=\"/jqueryChat/img/"+userLogo+"\" alt=\"头像\" /></div> </div> </div>");
//发送后清空输入框
$(".div-textarea").html("");
//组装消息
var sendMsgObj = {};
sendMsgObj.userUid = userUid;
sendMsgObj.toUser = toUser;
sendMsgObj.msg = textContent;
sendMsgObj.msgType = "common";
socket.send(JSON.stringify(sendMsgObj));
//聊天框默认最底部
$(document).ready(function () {
$("#chatBox-content-demo"+toUser).scrollTop($("#chatBox-content-demo"+toUser)[0].scrollHeight);
});
}
});
//发送标签
$(".emoji-picker-image").each(function () {
$(this).click(function () {
var bq = $(this).parent().html();
console.log(bq)
$("#chatBox-content-demo"+toUser).append("<div class=\"clearfloat\">" +
"<div class=\"author-name\"><small class=\"chat-date\">"+getNowTime()+"</small> </div> " +
"<div class=\"right\"> <div class=\"chat-message\"> " + bq + " </div> " +
"<div class=\"chat-avatars\"><img src=\"/jqueryChat/img/"+userLogo+"\" alt=\"头像\" /></div> </div> </div>");
//发送后关闭表情框
$(".biaoqing-photo").toggle();
//组装消息
var sendMsgObj = {};
sendMsgObj.userUid = userUid;
sendMsgObj.toUser = toUser;
sendMsgObj.msg = textContent;
sendMsgObj.msgType = "common";
socket.send(JSON.stringify(sendMsgObj));
//聊天框默认最底部
$(document).ready(function () {
$("#chatBox-content-demo"+toUser).scrollTop($("#chatBox-content-demo"+toUser)[0].scrollHeight);
});
})
});
});
//要聊天的用户
var toUser;
//获取当前用户的userUid
var userUid = [[${userUid}]];
//当前用户的头像
var userLogo = [[${userLogo}]];
//在线人数
var numberCount = 0;
<!-- ws客户端 -->
var socket;
var wsUrl = "ws://localhost:8080/websocket3/" + userUid;
//避免重复连接
var lockReconnect = false;
var tt;
function createWebSocket() {
try {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
}
socket = new WebSocket(wsUrl);
//初始化
init();
} catch(e) {
console.log('catch');
reconnect();
}
}
//初始化
function init() {
socket.onclose = function () {
console.log('链接关闭');
reconnect();
};
socket.onerror = function() {
console.log('发生异常了');
reconnect();
};
socket.onopen = function () {
//心跳检测重置
heartCheck.start();
};
socket.onmessage = function (event) {
var resData = JSON.parse(event.data);// 将json字符串转换为对象
console.log("onmessage:" + resData);
//如果是新上线的人员,就加入到在线人员的列表里
if(resData && resData.msgType=="connectInit"){
$("#chatBoxList").html("");//先清空
$.each(JSON.parse(resData.msg),function(i,e){ // 渲染聊天列表页面
var userCharHtml = ' <div onclick="intoChatBox(this);" class="chat-list-people"> ';
userCharHtml+= ' <div><img src="/jqueryChat/img/'+e.userLogo+'" alt="头像"/></div> ';
userCharHtml+= ' <div class="chat-name"> ';
userCharHtml+= ' <p name="'+e.userUid+'">'+e.nickName+'</p> ';
userCharHtml+= ' </div> ';
userCharHtml+= ' <div class="message-num" style="padding: 0px;" ></div> ';
userCharHtml+= ' </div> ';
$("#chatBoxList").append(userCharHtml);
//进入和用户聊天页面的时候,为每个用户单独创建聊天页面
if(!$("#chatBox-content-demo"+e.userUid).length>0){
$("#chatBox-content-Id").append(' <div class="chatBox-content-demo" id="chatBox-content-demo'+e.userUid+'"></div> ');
}
})
// 渲染聊天内容页面(每一个聊天会话对应一个内容页面)
}else{
var fromUser = JSON.parse(resData.fromUser);
console.log("fromUser:" + fromUser);
var resMsgHtml = ' <div class="clearfloat"> ';
resMsgHtml+= ' <div class="author-name"> ';
resMsgHtml+= ' <small class="chat-date">'+getNowTime()+'</small> ';
resMsgHtml+= ' </div> ';
resMsgHtml+= ' <div class="left"> ';
resMsgHtml+= ' <div class="chat-avatars"><img src="/jqueryChat/img/'+fromUser.userLogo+'" alt=\"头像\" /></div> ';
resMsgHtml+= ' <div class="chat-message"> ';
if(resData.msgType=="common"){
resMsgHtml+= ' '+resData.msg+' ';
}else{
resMsgHtml+= ' <img src="'+resData.msg+'"> ';
}
resMsgHtml+= ' </div> ';
resMsgHtml+= ' </div> ';
resMsgHtml+= ' </div> ';
$("#chatBox-content-demo"+fromUser.userUid).append(resMsgHtml);
//聊天框默认最底部
$("#chatBox-content-demo"+fromUser.userUid).scrollTop($("#chatBox-content-demo"+fromUser.userUid).prop('scrollHeight'));
//设置总的聊天记录条数
var totalNum = $(".chat-message-num").html();
if(totalNum ==''){
$(".chat-message-num").removeAttr("css","padding");
totalNum = 1;
}else{
totalNum++;
}
//设置每一个人的未读消息
$(".chat-message-num").html(totalNum);
var msgNum = $("p[name='"+fromUser.userUid+"']").parent("div").next("div").html();
if(msgNum ==''){
$("p[name='"+fromUser.userUid+"']").parent("div").next("div").removeAttr("css","padding");
msgNum = 1;
}else{
msgNum++;
}
$("p[name='"+fromUser.userUid+"']").parent("div").next("div").html(msgNum);
}
heartCheck.start();
}
}
//重新连接
function reconnect() {
if(lockReconnect) {
return;
};
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
tt && clearTimeout(tt);
tt = setTimeout(function () {
createWebSocket();
lockReconnect = false;
}, 4000);
}
//心跳检测
var heartCheck = {
timeout: 210000,
timeoutObj: null,
serverTimeoutObj: null,
start: function(){
console.log(getNowTime() +" Socket 心跳检测");
var self = this;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
console.log(getNowTime() +' Socket 连接重试');
//socket.send("连接成功");
self.serverTimeoutObj = setTimeout(function() {
console.log(socket);
socket.close();
}, self.timeout);
}, this.timeout)
}
}
//进聊天页面
function intoChatBox(e) {
toUser = $(e).children(".chat-name").children("p").eq(0).attr("name");
$(".chatBox-content-demo").hide(); // 隐藏所有的聊天内容页面
$("#chatBox-content-demo"+toUser).show(); // 显示和正在聊天对象的聊天内容页面
$(".chatBox-head-one").toggle();
$(".chatBox-head-two").toggle();
$(".chatBox-list").fadeToggle();
$(".chatBox-kuang").fadeToggle();
//传名字
$("#friendName").html($(e).children(".chat-name").children("p").eq(0).html());
//传头像
$("#friendLogo").attr("src", $(e).children().eq(0).children("img").attr("src"));
//聊天框默认最底部
$(document).ready(function () {
$("#chatBox-content-demo"+toUser).scrollTop($("#chatBox-content-demo"+toUser).prop('scrollHeight'));
});
//设置打开对话框人的消息为已读
var msgNum = $("p[name='"+toUser+"']").parent("div").next("div").html();
//未读条数为空
$("p[name='"+toUser+"']").parent("div").next("div").html('');
$("p[name='"+toUser+"']").parent("div").next("div").attr("padding",0);
//设置总的聊天记录条数
var totalNum = $(".chat-message-num").html();
if(parseInt(totalNum) <= parseInt(msgNum)){
$(".chat-message-num").attr("padding",0);
$(".chat-message-num").html('');
}else{
$(".chat-message-num").html(parseInt(totalNum) - parseInt(msgNum));
}
}
function screenFuc() {
var topHeight = $(".chatBox-head").innerHeight();//聊天头部高度
//屏幕小于768px时候,布局change
var winWidth = $(window).innerWidth();
if (winWidth <= 768) {
var totalHeight = $(window).height(); //页面整体高度
$(".chatBox-info").css("height", totalHeight - topHeight);
var infoHeight = $(".chatBox-info").innerHeight();//聊天头部以下高度
//中间内容高度
$(".chatBox-content").css("height", infoHeight - 46);
$(".chatBox-content-demo").css("height", infoHeight - 46);
$(".chatBox-list").css("height", totalHeight - topHeight);
$(".chatBox-kuang").css("height", totalHeight - topHeight);
$(".div-textarea").css("width", winWidth - 106);
} else {
$(".chatBox-info").css("height", 495);
$(".chatBox-content").css("height", 448);
$(".chatBox-content-demo").css("height", 448);
$(".chatBox-list").css("height", 495);
$(".chatBox-kuang").css("height", 495);
$(".div-textarea").css("width", 260);
}
}
/**
* 获取系统当前时间
* @returns
*/
function p(s) {
return s < 10 ? '0' + s : s;
}
function getNowTime() {
var myDate = new Date();
//获取当前年
var year = myDate.getFullYear();
//获取当前月
var month = myDate.getMonth() + 1;
//获取当前日
var date = myDate.getDate();
var h = myDate.getHours(); //获取当前小时数(0-23)
var m = myDate.getMinutes(); //获取当前分钟数(0-59)
var s = myDate.getSeconds();
return year + '-' + p(month) + "-" + p(date) + " " + p(h) + ':' + p(m) + ":" + p(s);
}
</script>
</body>
</html>
最后,测试;
打开多个浏览器窗口,输入不同的userUid,然后,相互之间发送信息,可以进行未读信息的数字提示;
总结
在前端界面,使用了JqueryChat插件来做聊天窗口,省去了前端页面的设计。这只是一个功能简单的demo,如果要考虑性能、安全等问题,仅仅依靠这个demo是不够的。
最后的思考:
如果要和springboot结合,如何完美融合呢?融进springboot的全家桶,实现分布式,经过网关,保证性能和安全,貌似没有那么容易。
网友评论