本项目基于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 * @author 传智.BoBo老师 * @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); } } /** * * 说明:群发信息:给所有在线用户发送消息 * @author 传智.BoBo老师 * @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(); } }
项目源码下载地址:请点击》》》