zoukankan      html  css  js  c++  java
  • 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="100%;height:100%;margin:0px;overflow:hidden;"> <div id="roomRemark"></div> <div style="position:relative;99%;height:100%;overflow:auto;display:none;margin-left:5px;" id="chatMsg"></div> </body> </html> </span>

           五、实现效果

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


         聊天室好友tester1进入并发言


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


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

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

  • 相关阅读:
    commands.getstatusoutput和subprocess.call结果不一致
    win10 企业版 2015 长期服务激活
    pycharm设置护眼模式
    实战--滚动菜单
    javascript中this的用法
    jQuery之remove与empty的区别
    clone方法案例实践
    jQuery内部插入与外部插入
    jQuery文档处理
    2020/02/11星期二复习
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4804557.html
Copyright © 2011-2022 走看看