本项目基于SSM开发的即时在线聊天室系统,主要实习功能:登录记入会话信息以及登录后,记录在线人数、消息信息、记录时间,是一个比较简易的聊天室系统,对于想学习了解聊天室框架结构的,有很大的帮助,这个项目可以直接用idea或者eclipse开发工具直接打开,没有接入数据库,数据都存储在session中,所以不需要配置数据库文件,拿来就可以操作,如想深入学些,可事后配上数据源,对数据进行永久操作。
聊天登录主界面:
主要代码:
// 登录进入聊天主页面
@RequestMapping(value = "login", method = RequestMethod.POST)
public ModelAndView login(User loginUser, HttpServletRequest request) {
HttpSession session = request.getSession();
// 登录操作
// 判断是否是一个已经登录的用户,没有则登录
if (null != session.getAttribute("loginUser")) {
// 清除旧的用户
session.removeAttribute("loginUser");
}
// 新登录,需要构建一个用户
// 随机生成一个用户
String id = UUID.randomUUID().toString();
loginUser.setId(id);
// 将用户放入session
session.setAttribute("loginUser", loginUser);
// 将登录信息放入数据库,便于协查跟踪聊天者
System.out.println("新用户诞生了:" + loginUser);
return new ModelAndView("redirect:mainpage");
}
聊天室界面:
代码如下:
// 跳转到聊天室页面
@RequestMapping(value = "mainpage", method = RequestMethod.GET)
public ModelAndView mainpage(HttpServletRequest request) {
//判断,如果没有session,则跳到登录页面
HttpSession session = request.getSession();
if(null==session.getAttribute("loginUser")){
return new ModelAndView("login");
}else{
return new ModelAndView("main");
}
}
所用到的工具类:
ChatHandshakeInterceptor工具类建立链接操作
/**
* websocket的链接建立是基于http握手协议,我们可以添加一个拦截器处理握手之前和握手之后过程
* @author BoBo
*
*/
@Component
public class ChatHandshakeInterceptor implements HandshakeInterceptor{
/**
* 握手之前,若返回false,则不建立链接
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
//如果用户已经登录,允许聊天
if(session.getAttribute("loginUser")!=null){
//获取登录的用户
User loginUser=(User)session.getAttribute("loginUser") ;
//将用户放入socket处理器的会话(WebSocketSession)中
attributes.put("loginUser", loginUser);
System.out.println("Websocket:用户[ID:" + (loginUser.getId() + ",Name:"+loginUser.getNickname()+"]要建立连接"));
}else{
//用户没有登录,拒绝聊天
//握手失败!
System.out.println("--------------握手已失败...");
return false;
}
}
System.out.println("--------------握手开始...");
return true;
}
/**
* 握手之后
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
System.out.println("--------------握手成功啦...");
}
}
ChatWebSocketHandler工具类WebSocket处理器
/**
*
* 说明:WebSocket处理器
*/
@Component("chatWebSocketHandler")
public class ChatWebSocketHandler implements WebSocketHandler {
//在线用户的SOCKETsession(存储了所有的通信通道)
public static final Map<String, WebSocketSession> USER_SOCKETSESSION_MAP;
//存储所有的在线用户
static {
USER_SOCKETSESSION_MAP = new HashMap<String, WebSocketSession>();
}
/**
* webscoket建立好链接之后的处理函数--连接建立后的准备工作
*/
@Override
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
//将当前的连接的用户会话放入MAP,key是用户编号
User loginUser=(User) webSocketSession.getAttributes().get("loginUser");
USER_SOCKETSESSION_MAP.put(loginUser.getId(), webSocketSession);
//群发消息告知大家
Message msg = new Message();
msg.setText("欢迎【"+loginUser.getNickname()+"】上线!");
msg.setDate(new Date());
//获取所有在线的WebSocketSession对象集合
Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();
//将最新的所有的在线人列表放入消息对象的list集合中,用于页面显示
for (Entry<String, WebSocketSession> entry : entrySet) {
msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser"));
}
//将消息转换为json
TextMessage message = new TextMessage(GsonUtils.toJson(msg));
//群发消息
sendMessageToAll(message);
}
@Override
/**
* 客户端发送服务器的消息时的处理函数,在这里收到消息之后可以分发消息
*/
//处理消息:当一个新的WebSocket到达的时候,会被调用(在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理)
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> message) throws Exception {
//如果消息没有任何内容,则直接返回
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)));
}
}
@Override
/**
* 消息传输过程中出现的异常处理函数
* 处理传输错误:处理由底层WebSocket消息传输过程中发生的异常
*/
public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) throws Exception {
// 记录日志,准备关闭连接
System.out.println("Websocket异常断开:" + webSocketSession.getId() + "已经关闭");
//一旦发生异常,强制用户下线,关闭session
if (webSocketSession.isOpen()) {
webSocketSession.close();
}
//群发消息告知大家
Message msg = new Message();
msg.setDate(new Date());
//获取异常的用户的会话中的用户编号
User loginUser=(User)webSocketSession.getAttributes().get("loginUser");
//获取所有的用户的会话
Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();
//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)
for (Entry<String, WebSocketSession> entry : entrySet) {
if(entry.getKey().equals(loginUser.getId())){
msg.setText("万众瞩目的【"+loginUser.getNickname()+"】已经退出。。。!");
//清除在线会话
USER_SOCKETSESSION_MAP.remove(entry.getKey());
//记录日志:
System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
break;
}
}
//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)
for (Entry<String, WebSocketSession> entry : entrySet) {
msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser"));
}
TextMessage message = new TextMessage(GsonUtils.toJson(msg));
sendMessageToAll(message);
}
@Override
/**
* websocket链接关闭的回调
* 连接关闭后:一般是回收资源等
*/
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
// 记录日志,准备关闭连接
System.out.println("Websocket正常断开:" + webSocketSession.getId() + "已经关闭");
//群发消息告知大家
Message msg = new Message();
msg.setDate(new Date());
//获取异常的用户的会话中的用户编号
User loginUser=(User)webSocketSession.getAttributes().get("loginUser");
Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();
//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)
for (Entry<String, WebSocketSession> entry : entrySet) {
if(entry.getKey().equals(loginUser.getId())){
//群发消息告知大家
msg.setText("万众瞩目的【"+loginUser.getNickname()+"】已经有事先走了,大家继续聊...");
//清除在线会话
USER_SOCKETSESSION_MAP.remove(entry.getKey());
//记录日志:
System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
break;
}
}
//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)
for (Entry<String, WebSocketSession> entry : entrySet) {
msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser"));
}
TextMessage message = new TextMessage(GsonUtils.toJson(msg));
sendMessageToAll(message);
}
@Override
/**
* 是否支持处理拆分消息,返回true返回拆分消息
*/
//是否支持部分消息:如果设置为true,那么一个大的或未知尺寸的消息将会被分割,并会收到多次消息(会通过多次调用方法handleMessage(WebSocketSession, WebSocketMessage). )
//如果分为多条消息,那么可以通过一个api:org.springframework.web.socket.WebSocketMessage.isLast() 是否是某条消息的最后一部分。
//默认一般为false,消息不分割
public boolean supportsPartialMessages() {
return false;
}
/**
*
* 说明:给某个人发信息
* @param id
* @param message
* @throws IOException
* @time:2016年10月27日 下午10:40:52
*/
private void sendMessageToUser(String id, TextMessage message) throws IOException{
//获取到要接收消息的用户的session
WebSocketSession webSocketSession = USER_SOCKETSESSION_MAP.get(id);
if (webSocketSession != null && webSocketSession.isOpen()) {
//发送消息
webSocketSession.sendMessage(message);
}
}
/**
*
* 说明:群发信息:给所有在线用户发送消息
* @time:2016年10月27日 下午10:40:07
*/
private void sendMessageToAll(final TextMessage message){
//对用户发送的消息内容进行转义
//获取到所有在线用户的SocketSession对象
Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.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();
}
}
}
}
WebSocketConfig 工具类WebScoket配置处理器
/**
*
* 说明:WebScoket配置处理器
* 把处理器和拦截器注册到spring websocket中
*/
@Component("webSocketConfig")
//配置开启WebSocket服务用来接收ws请求
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
//注入处理器
@Autowired
private ChatWebSocketHandler webSocketHandler;
@Autowired
private ChatHandshakeInterceptor chatHandshakeInterceptor;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//添加一个处理器还有定义处理器的处理路径
registry.addHandler(webSocketHandler, "/ws").addInterceptors(chatHandshakeInterceptor);
/*
* 在这里我们用到.withSockJS(),SockJS是spring用来处理浏览器对websocket的兼容性,
* SockJS能根据浏览器能否支持websocket来提供三种方式用于websocket请求,
* 三种方式分别是 WebSocket, HTTP Streaming以及 HTTP Long Polling
*/
registry.addHandler(webSocketHandler, "/ws/sockjs").addInterceptors(chatHandshakeInterceptor).withSockJS();
}
}