SpringMVC整合websocket实现多聊天室聊天功能

效果

在实现毕业设计时,中间有个功能需要实现不同聊天室不同用户之间的交流。
例如:
在这里插入图片描述

项目演示

SpringMVC整合websocket实现多聊天室聊天功能

原理讲解

创建多个不同的聊天室和实现对用户的分层管理。用户以医生的身份进入系统后,用户选择在线聊天,系统更改用户当前状态,并以其账号和姓名作为临时身份,并新建一个名称为医生账号的WebSocketsession,之后建立USER_SOCKETSESSION_MAP为房间里的每个人存储信息。将WebSocketsession存储到USER_SOCKETSESSION_MAP中,实现多人聊天室的功能。之后建立一个以自增长的数字为辨别符ROOM房间,将USER_SOCKETSESSION_MAP存储到ROOM中,用以区分每个聊天室。当用户以患者身份登录后,患者选者医生,并进入医生详情页,如果医生在线了话,则可以访问医生。用户选择医生后进入聊天室,后台会将要访问的医生账号放入WebSocketsession,系统在后台对比每个ROOM房间中USER_SOCKETSESSION_MAP里的WebSocketsession,当匹配完成后,将其WebSocketsession放进USER_SOCKETSESSION_MAP里对应的ROOM房间中。实现对房间分层多用户聊天操作。

实现步骤

1.区分聊天室

修改WebSocket的接收协议格式,使其可以接收以userid结尾的uri。其修改WebSocket协议的代码如下:

registry.addHandler(webSocketHandler,"/ws/{id}").addInterceptors(chatHandshakeInterceptor);
registry.addHandler(webSocketHandler,"/ws/sockjs/{id}").addInterceptors(chatHandshakeInterceptor).withSockJS();

在系统中要创建一个类型为HashMap的USER_SOCKETSESSION_MAP,并用userid作为标志区分每个房间里不同的用户,并将webSocketSession放入其中。之后创建类型为HashMap的ROOM用来存放不同的房间信息key作为每个房间的标志。创建USER_SOCKETSESSION_MAP和ROOM的代码如下:

final Map<String, WebSocketSession> USER_SOCKETSESSION_MAP;
final Map<Integer, Map<String, WebSocketSession>> ROOM; 
static {
    
    
	USER_SOCKETSESSION_MAP = new HashMap<String,WebSocketSession>();
	ROOM=new HashMap<Integer, Map<String, WebSocketSession>>();
} 

当患者选择要询问的主治医生后,系统将对应的医生账号写入WebSocketsession中去,当后台获取websocketsession后首先判断此时的房间ROOM是否为空,若为空房间则进行初始化操作,将websocketsession和登录用户id放入USER_SOCKETSESSION_MAP中,在初始化key为0,后将key和USER_SOCKETSESSION_MAP放入ROOM中。初始化代码如下:

if(ROOM==null || ROOM.isEmpty()){
    
    
Map<String, WebSocketSession> user_map= new HashMap<String, WebSocketSession>();
	user_map.put(loginUser.getId(), webSocketSession);
	ROOM.put(0, user_map);}

如果房间不为空则进行遍历查找,首先将ROOM分层一个一个的USER_SOCKETSESSION_MAP临时设为entry1,后将每个USER_SOCKETSESSION_MAP分层一个一个的WebSocketsession临时设为entry2。之后将得到的Websocketsession与每一个entry2中的uri对比,如果uri一样,则把当前的用户id和uri一起放入websocketsession中,将websocketsession放入对应ROOM中的USER_SOCKETSESSION_MAP中去,并设置flag值跳过后面的创建房间。最后跳出循环。寻找房间代码如下:

boolean flag=true;
here:
for(Map.Entry<Integer,Map<String,WebSocketSession>>entry1:ROOM.entrySet()){
    
    
for(Map.Entry<String,WebSocketSession>entry2:entry1.getValue().entrySet()){
    
    
if(entry2.getValue().getUri().equals(webSocketSession.getUri())){
    
    
			key=entry1.getKey();
			(ROOM.get(key)).put(loginUser.getId(), webSocketSession);
			flag=false;
			break here; 
		}
	}
}

如果房间不为空且找不到房间时,将websocketsession和登录用户id放入USER_SOCKETSESSION_MAP中,提取当前ROOM房间的所有值,并进行排序,取得最大值,将key设置为最大值加一,后创建新的ROOM房间并将取到的key值和USER_SOCKETSESSION_MAP放入ROOM中去。不为空并创建新房间代码如下:

if(flag){
    
    
	//无房间自增长添加房间
	Set<Integer> set = ROOM.keySet();
	Object[] obj = set.toArray();
	Arrays.sort(obj);
    key=obj.length;
	Map<String, WebSocketSession> user_map= new HashMap<String, WebSocketSession>();
	user_map.put(loginUser.getId(), webSocketSession);
	ROOM.put(obj.length, user_map);
}

通过两个HashMap类型的变量完成对不同房间的创建和对不同用户的区分,实现不同房间的多用户聊天室。

2.发送消息

如果客户端有消息进来,需要先判断客户端的这条信息是从哪个聊天室发来的:

@Override
	/**
     * 客户端发送服务器的消息时的处理函数,在这里收到消息之后可以分发消息
     */
	//处理消息:当一个新的WebSocket到达的时候,会被调用(在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理)
	public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> message) throws Exception {
    
    
//		判断哪个房间
		here:
		for(Map.Entry<Integer, Map<String, WebSocketSession>> entry1 : ROOM.entrySet()){
    
    
			for(Map.Entry<String, WebSocketSession> entry2 : entry1.getValue().entrySet()){
    
    
				if(entry2.getValue().getUri().equals(webSocketSession.getUri())){
    
    
					key=entry1.getKey();
					break here; 
				}
			}
		}
		
		//如果消息没有任何内容,则直接返回
		if(message.getPayloadLength()==0)return;
		//反序列化服务端收到的json消息
		Message msg = GsonUtils.fromJson(message.getPayload().toString(), Message.class);
		msg.setDate(new Date());
		//处理html的字符,转义:
		String text = msg.getText();
		//转换为HTML转义字符表示
		String htmlEscapeText = HtmlUtils.htmlEscape(text);
		msg.setText(htmlEscapeText);
		System.out.println("消息:"+message.getPayload().toString());
		//判断是群发还是单发
		if(msg.getTo()==null||msg.getTo().equals("-1")){
    
    
			//群发
			sendMessageToAll(new TextMessage(GsonUtils.toJson(msg)));
		}else{
    
    
			//单发
			sendMessageToUser(msg.getTo(), new TextMessage(GsonUtils.toJson(msg)));
		}
	}

之后再对消息进行客户端的发送

private void sendMessageToUser(String id, TextMessage message) throws IOException{
    
    
		//获取到要接收消息的用户的session
		WebSocketSession webSocketSession = ROOM.get(key).get(id);
		if (webSocketSession != null && webSocketSession.isOpen()) {
    
    
			//发送消息
			webSocketSession.sendMessage(message);
		}
	}
	
	private void sendMessageToAll(final TextMessage message){
    
    
		//对用户发送的消息内容进行转义
		
		//获取到所有在线用户的SocketSession对象
		Set<Entry<String, WebSocketSession>> entrySet = ROOM.get(key).entrySet();
		for (Entry<String, WebSocketSession> entry : entrySet) {
    
    
			//某用户的WebSocketSession
			final WebSocketSession webSocketSession = entry.getValue();
			//判断连接是否仍然打开的
			if(webSocketSession.isOpen()){
    
    
				//开启多线程发送消息(效率高)
				new Thread(new Runnable() {
    
    
					public void run() {
    
    
						try {
    
    
							if (webSocketSession.isOpen()) {
    
    
								webSocketSession.sendMessage(message);
							}
						} catch (IOException e) {
    
    
							e.printStackTrace();
						}
					}

				}).start();
				
			}
		}
	}

页面端接受消息
contact.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page language="java" import="java.util.*"%>
<%@ page language="java" import="java.sql.*"%>
<%
	String path = request.getContextPath();
	String basePath = request.getServerName() + ":" + request.getServerPort() + path + "/";
	String baseUrlPath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
			+ path + "/";
%>
<% String path1 = request.getContextPath()+"/123456";%>
<%=path1 %>
<script type="text/javascript">
	var path = '<%=basePath%>';
	
	var uid = '${sessionScope.loginUser.id}';
	//发送人编号
	var from = '${sessionScope.loginUser.id}';
	var fromName = '${sessionScope.loginUser.nickname}';
	
	//接收人编号
	var to = "-1";
	
	// 创建一个Socket实例
	//参数为URL,ws表示WebSocket协议。onopen、onclose和onmessage方法把事件连接到Socket实例上。每个方法都提供了一个事件,以表示Socket的状态。
	var websocket;
	//不同浏览器的WebSocket对象类型不同
	//alert("ws://" + path + "/ws?uid="+uid);
	if ('WebSocket' in window) {
    
    
		var url="ws://" + path + "/ws/"+uid;
		websocket = new WebSocket(url);
		console.log("=============WebSocket");
		//火狐
	} else if ('MozWebSocket' in window) {
    
    
		websocket = new MozWebSocket("ws://" + path + "ws");
		console.log("=============MozWebSocket");
	} else {
    
    
		websocket = new SockJS("http://" + path + "ws/sockjs");
		console.log("=============SockJS");
	}
	
	console.log("ws://" + path + "ws");
	
	//打开Socket,
	websocket.onopen = function(event) {
    
     
		console.log("WebSocket:已连接");
	}
	
	// 监听消息
	//onmessage事件提供了一个data属性,它可以包含消息的Body部分。消息的Body部分必须是一个字符串,可以进行序列化/反序列化操作,以便传递更多的数据。
	websocket.onmessage = function(event) {
    
     
		console.log('Client received a message',event);
		//var data=JSON.parse(event.data);
		var data=$.parseJSON(event.data);
		console.log("WebSocket:收到一条消息",data);
		
		//2种推送的消息
		//1.用户聊天信息:发送消息触发
		//2.系统消息:登录和退出触发
		
		//判断是否是欢迎消息(没用户编号的就是欢迎消息)
		if(data.from==undefined||data.from==null||data.from==""){
    
    
			//===系统消息
			$("#contentUl").append("<li><b>"+data.date+"</b><em>系统消息:</em><span>"+data.text+"</span></li>");
			//刷新在线用户列表
			$("#chatOnline").html("在线用户("+data.userList.length+")人");
			$("#chatUserList").empty();
			$(data.userList).each(function(){
    
    
				$("#chatUserList").append("<li>"+this.nickname+"</li>");
				$("#illnessoption").append("<option>"+this.nickname+"("+this.id+")</option>");
			});
			
		}else{
    
    
			//===普通消息
			//处理一下个人信息的显示:
			if(data.fromName==fromName){
    
    
				data.fromName="我";
				$("#contentUl").append("<li><span  style='display:block; float:right;'><em>"+data.fromName+"</em><span>"+data.text+"</span><b>"+data.date+"</b></span></li><br/>");
			}else{
    
    
				$("#contentUl").append("<li><b>"+data.date+"</b><em>"+data.fromName+"</em><span>"+data.text+"</span></li><br/>");
			}
			
		}
		
		scrollToBottom();
	}; 
	
	// 监听WebSocket的关闭
	websocket.onclose = function(event) {
    
     
		$("#contentUl").append("<li><b>"+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</b><em>系统消息:</em><span>连接已断开!</span></li>");
		scrollToBottom();
		console.log("WebSocket:已关闭:Client notified socket has closed",event); 
	}; 
	
	//监听异常
	websocket.onerror = function(event) {
    
    
		$("#contentUl").append("<li><b>"+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</b><em>系统消息:</em><span>连接异常,建议重新登录</span></li>");
		scrollToBottom();
		console.log("WebSocket:发生错误 ",event);
	};
	
	//onload初始化
	$(function(){
    
    
		//发送消息
		$("#sendBtn").on("click",function(){
    
    
			sendMsg();
		});
		
		//给退出聊天绑定事件
		$("#exitBtn").on("click",function(){
    
    
			closeWebsocket();
			location.href="${pageContext.request.contextPath}/index.jsp";
		});
		
		//给输入框绑定事件
		$("#msg").on("keydown",function(event){
    
    
			keySend(event);
		});
		
		//初始化时如果有消息,则滚动条到最下面:
		scrollToBottom();
		
	});

	//使用ctrl+回车快捷键发送消息
	function keySend(e) {
    
    
		var theEvent = window.event || e; 
		var code = theEvent.keyCode || theEvent.which; 
		if (theEvent.ctrlKey && code == 13) {
    
    
			var msg=$("#msg");
			if (msg.innerHTML == "") {
    
    
				msg.focus();
				return false;
			}
			sendMsg();
		}
	}
	
	//发送消息
	function sendMsg(){
    
    
		//对象为空了
		if(websocket==undefined||websocket==null){
    
    
			//alert('WebSocket connection not established, please connect.');
			alert('您的连接已经丢失,请退出聊天重新进入');
			return;
		}
		//获取用户要发送的消息内容
		var msg=$("#msg").val();
		if(msg==""){
    
    
			return;
		}else{
    
    
			var data={
    
    };
			data["from"]=from;
			data["fromName"]=fromName;
			data["to"]=to;
			data["text"]=msg;
			//发送消息
			websocket.send(JSON.stringify(data));
			//发送完消息,清空输入框
			$("#msg").val("");
		}
	}

	//关闭Websocket连接
	function closeWebsocket(){
    
    
		if (websocket != null) {
    
    
			websocket.close();
			websocket = null;
		}
		
	}
	
	//div滚动条(scrollbar)保持在最底部
	function scrollToBottom(){
    
    
		//var div = document.getElementById('chatCon');
		var div = document.getElementById('up');
		div.scrollTop = div.scrollHeight;
	}	
	//格式化日期
	Date.prototype.Format = function (fmt) {
    
     //author: meizz 
	    var o = {
    
    
	        "M+": this.getMonth() + 1, //月份 
	        "d+": this.getDate(), //日 
	        "h+": this.getHours(), //小时 
	        "m+": this.getMinutes(), //分 
	        "s+": this.getSeconds(), //秒 
	        "q+": Math.floor((this.getMonth() + 3) / 3), //季度 
	        "S": this.getMilliseconds() //毫秒 
	    };
	    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
	    for (var k in o)
	    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
	    return fmt;
	}			
</script>
<!-- 聊天窗口 -->
	<!--聊天区域开始-->
	<div class="chatArea" id="chatArea">
		<div class="inChatArea">
			<div class="chatCon">
				<div id="chatSidebar" class="chatSidebar">
					<h2 id="chatOnline">在线用户(0)</h2>
					<ul id="chatUserList">
					</ul>
				</div>
				<div class="up" id="up">
					<ul id="contentUl">
						<!-- <li><b>14:08</b><em>江山如此多娇</em><span>今天天气不大家出来嗨!!!!!</span></li>-->
					</ul>
				</div>
				<div class="down">
					<textarea class="textInfo" id="msg" title="按ctrl+enter直接发送"></textarea>
					<button class="btn" id="sendBtn"></button>
				</div>
				
			
			</div>
			<!-- 
	    <div class="ad">
        	<iframe src="http://m.itheimacast.icoc.in/" width="315" height="635" scrolling="no" frameborder="no" />
        </div>
         -->
		</div>
	</div>
	<!--聊天区域结束-->

未经授权,请勿转载

猜你喜欢

转载自blog.csdn.net/qq_38922932/article/details/106410227