web即时通讯2--基于Spring websocket达到web聊天室

时间:2022-12-29 12:34:22

如本文所用,Spring4和websocket要构建web聊天室,根据框架SpringMVC+Spring+Hibernate的Maven项目,后台使用spring websocket进行消息转发和聊天消息缓存。client使用socket.js和stomp.js来进行消息订阅和消息发送。具体实现见以下代码。

首先在pom.xml中加入对spring websocket的相关依赖包。

   一、加入websocket依赖

<span style="font-size:14px;">             <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
    <dependency>  
                <groupId>com.fasterxml.jackson.core</groupId>  
                <artifactId>jackson-core</artifactId>  
                <version>2.3.0</version>  
            </dependency>  
            <dependency>  
                <groupId>com.fasterxml.jackson.core</groupId>  
                <artifactId>jackson-databind</artifactId>  
                <version>2.3.0</version>  
            </dependency>  
            <dependency>  
                <groupId>com.fasterxml.jackson.core</groupId>  
                <artifactId>jackson-annotations</artifactId>  
                <version>2.3.0</version>  
        </dependency>  
</span>

当中<springframework.version>4.0.3.RELEASE</springframework.version>。

由于spring4以上才支持WebSocket。

2、配置Spring WebSocket

该配置能够在Spring MVC的配置文件配置,也能够使用注解方式配置。本文使用注解@Configuration方式进行配置。

<span style="font-size:14px;">package com.test.chat.controller;

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration; @Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//加入这个Endpoint。这样在网页中就能够通过websocket连接上服务了
registry.addEndpoint("/webchat").withSockJS();
} @Override
public void configureMessageBroker(MessageBrokerRegistry config) {
System.out.println("server启动成功");
//这里设置的simple broker是指能够订阅的地址,也就是server能够发送的地址
config.enableSimpleBroker("/userChat","/initChat","/initFushionChart","/updateChart","/videoChat");
config.setApplicationDestinationPrefixes("/app");
} @Override
public void configureClientInboundChannel(ChannelRegistration channelRegistration) {
} @Override
public void configureClientOutboundChannel(ChannelRegistration channelRegistration) {
} @Override
public void configureWebSocketTransport(
WebSocketTransportRegistration registry) {
// TODO Auto-generated method stub
System.out.println("registry:"+registry);
} @Override
public boolean configureMessageConverters(
List<MessageConverter> messageConverters) {
// TODO Auto-generated method stub
System.out.println("messageConverters:"+messageConverters);
return true;
}
}
</span>

要使配置文件生效,需在Spring的文件里可以扫描到该文件所在的包。即配置<context:component-scan base-package="com.test.**.controller" />

       3、聊天内容的实体对象和后台关键代码

<span style="font-size:14px;">package com.test.chat.model;

public class ChatMessage {
//房间号
private String roomid;
//username
private String userName;
//机构名
private String deptName;
//当前系统时间
private String curTime;
//聊天内容
private String chatContent;
//是否是系统消息
private String isSysMsg; public String getIsSysMsg() {
return isSysMsg;
}
public void setIsSysMsg(String isSysMsg) {
this.isSysMsg = isSysMsg;
}
public String getRoomid() {
return roomid;
}
public void setRoomid(String roomid) {
this.roomid = roomid;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getCurTime() {
return curTime;
}
public void setCurTime(String curTime) {
this.curTime = curTime;
}
public String getChatContent() {
return chatContent;
}
public void setChatContent(String chatContent) {
this.chatContent = chatContent;
} }
</span>

后台关键处理代码,用于转发消息并缓存聊天记录

<span style="font-size:14px;">package com.test.chat.controller;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import javax.annotation.Resource;
import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping; import com.alibaba.fastjson.JSONObject;
import com.test.chat.model.ChatMessage;
import com.test.chat.model.LimitQueue;
import com.test.chat.model.VideoMessage;
import com.test.framework.common.SessionContainer;
import com.test.framework.service.GenericService;
import com.test.framework.utils.DateUtil; @Controller
public class UserChatController {
//每一个聊天室缓存最大聊天信息条数,该值由SpringMVC的配置文件注入,超过该值将清理出缓存
private int MAX_CHAT_HISTORY; public void setMAX_CHAT_HISTORY(int MAX_CHAT_HISTORY) {
this.MAX_CHAT_HISTORY = MAX_CHAT_HISTORY;
} @Resource
private GenericService genericService;
// 用于转发数据 sendTo
private SimpMessagingTemplate template;
//消息缓存列表
 private Map<String, Object> msgCache = new HashMap<String, Object>(); @Autowired
public UserChatController(SimpMessagingTemplate t) {
template = t;
} /**
* WebSocket聊天的对应接收方法和转发方法
* client通过app/userChat调用该方法,并将处理的消息发送client订阅的地址
* @param userChat 关于用户聊天的各个信息
*/
@MessageMapping("/userChat")
public void userChat(ChatMessage chatMessage) {
// 找到须要发送的地址(client订阅地址)
String dest = "/userChat/chat" + chatMessage.getRoomid();
// 获取缓存,并将用户最新的聊天记录存储到缓存中
Object cache = msgCache.get(chatMessage.getRoomid());
try {
chatMessage.setRoomid(URLDecoder.decode(chatMessage.getRoomid(),"utf-8"));
chatMessage.setUserName(URLDecoder.decode(chatMessage.getUserName(), "utf-8"));
chatMessage.setDeptName(URLDecoder.decode(chatMessage.getDeptName(), "utf-8"));
chatMessage.setChatContent(URLDecoder.decode(chatMessage.getChatContent(), "utf-8"));
chatMessage.setIsSysMsg(URLDecoder.decode(chatMessage.getIsSysMsg(),"utf-8"));
chatMessage.setCurTime(DateUtil.format(new Date(),DateUtil.formatStr_yyyyMMddHHmmss));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 发送用户的聊天记录
this.template.convertAndSend(dest, chatMessage);
 ((LimitQueue<ChatMessage>) cache).offer(chatMessage);
} @SubscribeMapping("/initChat/{roomid}")
public LimitQueue<ChatMessage> initChatRoom(@DestinationVariable String roomid) {
System.out.print("-------新用户进入聊天室------");
LimitQueue<ChatMessage> chatlist = new LimitQueue<ChatMessage>(MAX_CHAT_HISTORY);
// 发送用户的聊天记录
if (!msgCache.containsKey(roomid)) {
// 从来没有人进入聊天空间
msgCache.put(roomid, chatlist);
} else {
chatlist = (LimitQueue<ChatMessage>) msgCache.get(roomid);
}
return chatlist;
} }
</span>

在Spring的配置文件里注入MAX_CHAT_HISTRORY

<span style="font-size:14px;">    <bean id="userChatController" class="com.test.chat.controller.UserChatController">
<property name="MAX_CHAT_HISTORY" value="20"/>
</bean> </span>

当中缓存队列LimitQueue的实现为:

<span style="font-size:14px;">package com.test.chat.model;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue; public class LimitQueue<E> implements Queue<E> {
private int limit;
private Queue<E> queue; public LimitQueue(int limit) {
this.limit = limit;
this.queue = new LinkedList<E>();
} @Override
public int size() {
return queue.size();
} @Override
public boolean isEmpty() {
return queue.isEmpty();
} @Override
public boolean contains(Object o) {
return queue.contains(o);
} @Override
public Iterator<E> iterator() {
return queue.iterator();
} @Override
public Object[] toArray() {
return queue.toArray();
} @Override
public <T> T[] toArray(T[] a) {
return queue.toArray(a);
} @Override
public boolean add(E e) {
return queue.add(e);
} @Override
public boolean remove(Object o) {
return queue.remove(0);
} @Override
public boolean containsAll(Collection<?> c) {
return queue.containsAll(c);
} @Override
public boolean addAll(Collection<? extends E> c) {
return queue.addAll(c);
} @Override
public boolean removeAll(Collection<?> c) {
return queue.removeAll(c);
} @Override
public boolean retainAll(Collection<?> c) {
return queue.retainAll(c);
} @Override
public void clear() {
queue.clear();
} @Override
public boolean offer(E e) {
if (queue.size() >= limit) {
queue.poll();
}
return queue.offer(e);
} @Override
public E remove() {
return queue.remove();
} @Override
public E poll() {
return queue.poll();
} @Override
public E element() {
return queue.element();
} @Override
public E peek() {
return queue.peek();
} public int getLimit() {
return this.limit;
}
}
</span>

         四、前台聊天室的实现(前台界面使用dhtmlx控件)

<span style="font-size:14px;"><%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>聊天室管理</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="/dhtmlx/dhtmlxEditor/codebase/skins/dhtmlxeditor_dhx_skyblue.css">
<script src="/common/js/lib-base.js" type="text/javascript"></script>
<script src="/dhtmlx/dhtmlxEditor/codebase/dhtmlxeditor.js" type="text/javascript"></script>
<!-- <script src="/dhtmlx/dhtmlxEditor/codebase/ext/dhtmlxeditor_ext.js" type="text/javascript"></script> -->
<!-- web chat 引入相关脚本 -->
<script src="/common/js/websocket/sockjs-0.3.4.min.js" type="text/javascript"></script>
<script src="/common/js/websocket/stomp.js" type="text/javascript"></script>
<!----------------end------------------->
<script>
var chatLayout;
var roomid="${roomid}";
var roomName=null;
var friendTree=null;
var userid=null;
var username=null;
var deptSortName=null;
var editor=null;
$(function(){
commonForm.initForm();
chatLayout= new dhtmlXLayoutObject(document.body, "3J");
ajaxPost("/chatroom/findById",{"id":roomid},function(data,status){
chatLayout.cells("a").setText("<img src='/images/Pub/User.gif' width='16px' height='16px' align='absmiddle' style='margin-right:5px'>"+data.roomName);
roomName=data.roomName;
$("#roomRemark").html(data.remark);
}) ;
chatLayout.cells("a").hideHeader();
chatLayout.cells("a").attachObject("chatMsg");
chatLayout.cells("c").setHeight(150);
chatLayout.cells("c").hideHeader();
chatLayout.setAutoSize("a;c","a;b");
chatLayout.cells("b").setWidth(180);
var friendLayout=chatLayout.cells("b").attachLayout("2E");
friendTree=friendLayout.cells("b").attachTree();
friendLayout.cells("a").setText("<img src='/images/Pub/User.gif' width='16px' height='16px' align='absmiddle' style='margin-right:5px'>群公告");
friendLayout.cells("a").attachObject("roomRemark");
friendLayout.cells("a").setHeight(100);
friendLayout.cells("b").setText("<img src='/images/Pub/User.gif' width='16px' height='16px' align='absmiddle' style='margin-right:5px'>好友列表");
friendLayout.setAutoSize("a;b","b");
//载入好友列表树
ajaxPost("/auth/getCurUser",null,function(data,status){
userid=data.id;
username=data.name;
deptSortName=data.deptSortName;
})
loadChatFriend();
//载入聊天
var talkLayout=chatLayout.cells("c").attachLayout("2E");
talkLayout.cells("a").hideHeader();
talkLayout.cells("b").hideHeader();
talkLayout.cells("b").setHeight(29);
//dhtmlx.image_path="/dhtmlx/dhtmlxEditor/codebase/imgs/";
editor=talkLayout.cells("a").attachEditor();
var toolbar=talkLayout.cells("b").attachToolbar();
toolbar.setIconsPath("/images/Pub/");
var tbindex=0;
toolbar.addSeparator("sep1", tbindex++);
toolbar.addSpacer("sep1");
toolbar.addButton("closeChat", tbindex++, "关闭", "delete.png","delte.png");
toolbar.addSeparator("sep2",tbindex++);
toolbar.addButton("videoChat", tbindex++, "视频", "FrameReLogin.gif","FrameReLogin.gif");
toolbar.addSeparator("sep3",tbindex++);
toolbar.addButton("sendMessage", tbindex++, "发送", "redo.gif","redo.gif"); toolbar.attachEvent("onclick",function(tid){
switch(tid){
case "sendMessage":
if(editor.getContent()=="" )
return;
sendMessage("0");
editor.setContent("");
break;
case "closeChat":
sendMessage("1","离开");
parent.dhxWins.window("chatWin").close();
break;
case "videoChat":
top.openWindow("/video/openVideoChat? roomid="+roomid,"videoWin","聊天室【"+roomName+"】",650,550,false,false,true);
break;
default:
break;
}
}) }); function loadChatFriend(){
friendTree.setSkin('dhx_skyblue');
friendTree.setImagePath("/dhtmlx/dhtmlxTree/codebase/imgs/csh_dhx_skyblue/");
ajaxPost("/chatroom/getChatFriends",{"roomid":roomid},function(data,status){
friendTree.deleteChildItems(friendTree.rootId);
$.each(data,function(index,item){
var id=item.user.id;
var deptName=item.user.corg.shortName;
var userName=item.user.name;
var isCreator=item.isCreator;
friendTree.insertNewItem(friendTree.rootId,id,deptName+"--"+userName+(isCreator=="1"? "(群主)":""),0,0,0,0,"");
if(userid==id)
friendTree.setItemColor(id,"red","");
})
})
} //---------------------------------------聊天室关键代码(websocket)---------------------------------------
var stompClient=null;content=null;
$(function(){
connect();
})
//connect the server
function connect(){
var socket=new SockJS("/webchat");
stompClient=Stomp.over(socket);
stompClient.connect('','',function(frame){
console.log('Connected: '+frame);
//用户聊天订阅
//alert("hello: "+frame);
stompClient.subscribe("/userChat/chat"+roomid,function(chat){
showChat(JSON.parse(chat.body));
}); //初始化
stompClient.subscribe("/app/initChat/"+roomid,function(initData){
//alert("初始化聊天室");
console.log(initData);
content=JSON.parse(initData.body);
//content=body.document.content;
//alert(content+":"+content.document.content);
content.forEach(function(item){
showChat(item);
});
sendMessage("1","进入");
});
},function(){
connect();
});
} //显示聊天信息
function showChat(message){
var htmlMsg=decodeURIComponent(message.chatContent);
var image="<img src='/images/Pub/User.gif' width='16px' height='16px' align='absmiddle'/>";
var userMsg=decodeURIComponent(message.deptName)
+"--"+decodeURIComponent(message.userName)+"   "+decodeURIComponent(message.curTime)+"</font>";
htmlMsg=userMsg+"<br/>    "+htmlMsg;
if(htmlMsg!="") {
if($("#chatMsg").html()!=""){
if(message.isSysMsg=="1")
$("#chatMsg").append("<br/><div style='text-align:center'><font color='gray'>"+htmlMsg+"</div>");
else
$("#chatMsg").append("<br/>"+image+"<font color='blue'>"+htmlMsg);
}
else {
if(message.isSysMsg=="1")
$("#chatMsg").append("<div style='text-align:center'><font color='gray'>"+htmlMsg+"</div>");
else
$("#chatMsg").append(image+"<font color='blue'>"+htmlMsg);
} $("#chatMsg")[0].scrollTop=$("#chatMsg")[0].scrollHeight;
}
} function sendMessage(isSysMsg,textMsg){
var chatCont=editor.getContent();
if(isSysMsg=="1"){
chatCont="<font color='gray'>"+textMsg+"聊天室</font>";
}
stompClient.send("/app/userChat",{},JSON.stringify({
'roomid':encodeURIComponent(roomid),
'userName':encodeURIComponent(username),
'deptName':encodeURIComponent(deptSortName),
'chatContent':encodeURIComponent(chatCont),
'isSysMsg':encodeURIComponent(isSysMsg)
}))
}
//--------------------------------------------------------------------------------------------------------------- </script>
</head>
<body style="width:100%;height:100%;margin:0px;overflow:hidden;">
<div id="roomRemark"></div>
<div style="position:relative;width:99%;height:100%;overflow:auto;display:none;margin-left:5px;" id="chatMsg"></div>
</body>
</html>
</span>

       五、实现效果

创建人tester4进入后,输入聊天内容后,退出。

web即时通讯2--基于Spring websocket达到web聊天室



聊天室好友tester1进入并发言

web即时通讯2--基于Spring websocket达到web聊天室

   文中代码部分參考了Spring WebSocket教程(一)Spring
WebSocket教程(二)。

下一篇文章将这个网页上介绍加入聊天室视频聊天。

版权声明:本文博主原创文章。博客,未经同意不得转载。

web即时通讯2--基于Spring websocket达到web聊天室的更多相关文章

  1. 对接融云即时通讯组件SDK,轻松实现App聊天室

    我好像特别喜欢做聊天室类的东东,刚折腾完微软的SignalR又折腾App.本来想研究研究XMPP的,由于服务器的搭建问题,先采用一个第三方的吧,看看效果如何.听到弟弟说他们公司用到了融云,我也下载个S ...

  2. IdentityServer4 &plus; SignalR Core &plus;RabbitMQ 构建web即时通讯(三)

    IdentityServer4 + SignalR Core +RabbitMQ 构建web即时通讯(三) 后台服务用户与认证 新建一个空的.net core web项目Demo.Chat,端口配置为 ...

  3. 一套简单的web即时通讯——第一版

    前言 我们之前已经实现了 WebSocket+Java 私聊.群聊实例,后面我们模仿layer弹窗,封装了一个自己的web弹窗 自定义web弹窗/层:简易风格的msg与可拖放的dialog,生成博客园 ...

  4. 基于Spring MVC的Web应用开发&lpar;三&rpar; - Resources

    基于Spring MVC的Web应用开发(3) - Resources 上一篇介绍了在基于Spring MVC的Web项目中加入日志,本文介绍Spring MVC如何处理资源文件. 注意到本项目的we ...

  5. 构建一个基于 Spring 的 RESTful Web Service

    本文详细介绍了基于Spring创建一个“hello world” RESTful web service工程的步骤. 目标 构建一个service,接收如下HTTP GET请求: http://loc ...

  6. 基于Server-Sent Event的简单聊天室 Web 2&period;0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。

    基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...

  7. IdentityServer4 &plus; SignalR Core &plus;RabbitMQ 构建web即时通讯(二)

    IdentityServer4 + SignalR Core +RabbitMQ 构建web即时通讯(二) IdentityServer4 用户中心生成数据库 上文已经创建了所有的数据库上下文迁移代码 ...

  8. IdentityServer4 &plus; SignalR Core &plus;RabbitMQ 构建web即时通讯(一)

    IdentityServer4 + SignalR Core +RabbitMQ 构建web即时通讯 前言 .net core 2.1已经正式发布了,signalr core1.0随之发布,是时候写个 ...

  9. 分享基于 websocket 网页端聊天室

    博客地址:https://ainyi.com/67 有一个月没有写博客了,也是因为年前需求多.回家过春节的原因,现在返回北京的第二天,想想,应该也要分享技术专题的博客了!! 主题 基于 websock ...

随机推荐

  1. 【Mail】搭建邮件服务器(LAMP&plus;Postfix&plus;Dovcot&plus;PostfixAdmin&plus;Roundcubemail)

    大纲 一.mail部署说明 二.安装准备 三.LMAP环境配置 四.配置postfixadmin 五.配置postfix 六.配置dovecot 七.测试SMTP和POP3服务 八.配置Roundcu ...

  2. ubuntu 12&period;04 react-native 安装

    1.安装nodejs 和npm apt-get install nodejs apt-get install npm 2. 升级node js 和npm sudo npm cache clean -f ...

  3. 【GoLang】GoLang fmt 占位符详解

    golang 的fmt 包实现了格式化I/O函数,类似于C的 printf 和 scanf. # 定义示例类型和变量 type Human struct { Name string } var peo ...

  4. Careercup - Microsoft面试题 - 6314866323226624

    2014-05-11 05:29 题目链接 原题: Design remote controller for me. 题目:设计一个遥控器. 解法:遥控什么?什么遥控?传统的红外线信号吗?我只能随便说 ...

  5. BZOJ 1592&colon; &lbrack;Usaco2008 Feb&rsqb;Making the Grade 路面修整

    Description FJ打算好好修一下农场中某条凹凸不平的土路.按奶牛们的要求,修好后的路面高度应当单调上升或单调下降,也就是说,高度上升与高度下降的路段不能同时出现在修好的路中. 整条路被分成了 ...

  6. 在子jsp页面中调用父jsp中的function或父jsp调用子页面中的function

    项目场景: A.jsp中有一个window,window里嵌入了一个<iframe>,通过<iframe>引入了另一个页面B.jsp.在B.jsp中的一个function中需要 ...

  7. ios多线程操作(五)—— GCD串行队列与并发队列

          GCD的队列能够分为2大类型,分别为串行队列和并发队列      串行队列(Serial Dispatch Queue):      一次仅仅调度一个任务,队列中的任务一个接着一个地运行( ...

  8. SharedPreferences封装类

    最近一直在读马伟奇老师的简书,给人以不一样的感觉,接下来的时间会做做笔记,毕竟好东西变成自己的才有用 原文地址SharedPreferencesUtils 依赖 dependencies { comp ...

  9. CentOS7为firewalld添加开放端口及相关操作

    1.firewalld的基本使用 启动: systemctl start firewalld 查看状态: systemctl status firewalld 停止: systemctl disabl ...

  10. Python2&period;7-fnmacth

    fnmatch 模块,提供了对 Unix shell 的规则的支持,类似正则,但不一样,匹配的规则只有3条:*, ?, 在 [] 里的任意字符 模块方法: fnmatch.fnmatch(filena ...