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

如本文所用,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教程(二)。

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

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

上一篇:bzoj 3190 赛车 半平面交


下一篇:JAVA知识的相关积累--用于自己以后查找