本文使用Spring4和websocket搭建一个web聊天室,框架基于SpringMVC+Spring+Hibernate的Maven项目,后台使用spring websocket进行消息转发和聊天消息缓存。客户端使用socket.js和stomp.js来进行消息订阅和消息发送。详细实现见下面代码。
首先在pom.xml中添加对spring websocket的相关依赖包。
一、添加websocket依赖
org.springframework
spring-webmvc
${springframework.version}
org.springframework
spring-websocket
${springframework.version}
org.springframework
spring-messaging
${springframework.version}
org.apache.poi
poi
3.9
com.fasterxml.jackson.core
jackson-core
2.3.0
com.fasterxml.jackson.core
jackson-databind
2.3.0
com.fasterxml.jackson.core
jackson-annotations
2.3.0
2、配置Spring WebSocket
该配置可以在Spring MVC的配置文件配置,也可以使用注解方式配置,本文使用注解@Configuration方式进行配置。
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("服务器启动成功");
//这里设置的simple broker是指可以订阅的地址,也就是服务器可以发送的地址
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 messageConverters) {
// TODO Auto-generated method stub
System.out.println("messageConverters:"+messageConverters);
return true;
}
}
3、聊天内容的实体对象和后台关键代码
package com.test.chat.model;
public class ChatMessage {
//房间号
private String roomid;
//用户名
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;
}
}
后台关键处理代码,用于转发消息并缓存聊天记录
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 msgCache = new HashMap();
@Autowired
public UserChatController(SimpMessagingTemplate t) {
template = t;
}
/**
* WebSocket聊天的相应接收方法和转发方法
* 客户端通过app/userChat调用该方法,并将处理的消息发送客户端订阅的地址
* @param userChat 关于用户聊天的各个信息
*/
@MessageMapping("/userChat")
public void userChat(ChatMessage chatMessage) {
// 找到需要发送的地址(客户端订阅地址)
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) cache).offer(chatMessage);
}
@SubscribeMapping("/initChat/{roomid}")
public LimitQueue initChatRoom(@DestinationVariable String roomid) {
System.out.print("-------新用户进入聊天室------");
LimitQueue chatlist = new LimitQueue(MAX_CHAT_HISTORY);
// 发送用户的聊天记录
if (!msgCache.containsKey(roomid)) {
// 从来没有人进入聊天空间
msgCache.put(roomid, chatlist);
} else {
chatlist = (LimitQueue) msgCache.get(roomid);
}
return chatlist;
}
}
在Spring的配置文件中注入MAX_CHAT_HISTRORY
package com.test.chat.model;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
public class LimitQueue implements Queue {
private int limit;
private Queue queue;
public LimitQueue(int limit) {
this.limit = limit;
this.queue = new LinkedList();
}
@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 iterator() {
return queue.iterator();
}
@Override
public Object[] toArray() {
return queue.toArray();
}
@Override
public 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;
}
}
四、前台聊天室的实现(前台界面使用dhtmlx控件)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
聊天室管理
五、实现效果
聊天室好友tester1进入并发言
文中代码部分参考了Spring WebSocket教程(一)和Spring WebSocket教程(二)。