基于WebSocket的Web端IM即时通讯应用开发功能列表:1、Web端IM应用2、支持在线、离线、实时在线提醒3.单聊、群聊的建立4.普通文字、表情、图片的传输(亚定义富文本)5.单人置顶提醒,多人对话窗口提醒6.自动回复演示图灵机器人核心技术一览1.websocket、sockjs、stomp2,前端涉及jquery、vue、elementUI、jquerybase64js3的展示,后端springboot、jsoup、spring-security、spring-websocket成果展示:技术实现说明:Websocket部分Web端的IM应用,为了实现客户端与客户端之间的通信,必须通过服务器进行转发。例如A要与B通信,A首先要将信息发送给IM应用服务器,服务器根据A的信息中携带的接收者转发给B,这与B到A的模式相同。web端实现实时通信,websocket也是最好的方式,还有其他协议如长轮询、短轮询、iframe数据、htmlfile等。在实际开发中,我们通常会使用一些编写的实时通信库其他的,比如socket.io和sockjs(我们这次用的是他,类似于jquery,封装了其他的即时通讯技术),他们的原理就是封装了上面的一些技术(还有一些其他的技术比如基于Flash的push)在客户端和服务端,然后给开发者一个统一的调用接口。该接口在支持websocket的环境下使用websocket,在不支持的情况下启用上面提到的一些hack技术。WebSocket是HTML5的一种新的通信协议(ws协议)。它是一种不强制执行任何特定消息协议的消息体系结构。它依赖于应用层来解释消息的含义;与应用层的HTTP不同,WebSocket在TCP之上的一个非常薄的层将字节流转换为文本/二进制消息。因此,对于实际应用来说,WebSocket的通信形式层次太低了。因此,可以在WebSocket之上使用STOMP协议,为浏览器与服务器之间的通信添加适当的消息语义。STOMP(SimpleText-OrientatedMessagingProtocol)面向消息的简单文本协议。就像HTTP在TCP套接字上添加了一个请求-响应模型层一样,STOMP在WebSocket之上提供了一个基于帧的行格式层来定义消息语义;STOMP源码http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js,有兴趣的可以看看,大概了解一下它的原理和用法。本示例程序核心代码:varsocket=newSockJS('/im-websocket');stompClient=Stomp.over(socket);//设置stompconsole日志不输出stompClient.debug=null;stompClient.connect({},function(frame){//相当于连接ws://localhost:8080/gs-guide-websocket/041/hk5tax0r/websockethk5tax0r是sessionidconsole.log("Connecting",socket._transport.url);//订阅一般的私聊频道群也是这里实现的stompClient.subscribe('/user/topic/private',function(greeting){});//订阅用户上网的公共频道和offlinestompClient.subscribe('/topic/userlist',function(greeting){});},functionerrorCallBack(error){//连接失败时的回调方法(服务端响应ERROR帧)});发送数据如下://第一个参数对应controller/app的@MessageMapping注解是后台定义的通用前缀//第三个参数是内容字符串stompClient.send("/app/private",{},JSON.stringify(message));//发送服务器对应的server部分#WebSocketConfig@Configuration@EnableWebSocketMessageBrokerpublicclassWebSocketConfigextendsAbstractWebSocketMessageBrokerConfigurer{publicListonlineUser=newArrayList<用户>();@Autowired私有SimpMessagingTemplate模板;@OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryconfig){config.enableSimpleBroker("/topic");config.setUserDestinationPrefix("/用户");config.setApplicationDestinationPrefixes("/app");config.setCacheLimit(1048576);//大小1M}@OverridepublicvoidregisterStompEndpoints(StompEndpointRegistryregistry){//注册websocket接入点,前端链接为其registry.addEndpoint("/im-websocket").withSockJS();}@OverridepublicvoidconfigureWebSocketTransport(finalWebSocketTransportRegistrationregistration){//设置文件缓冲区大小为1M//如果不设置文件稍微大一点会报错registration.setMessageSizeLimit(1048576);registration.setSendBufferSizeLimit(1048576);registration.addDecoratorFactory(newWebSocketHandlerDecoratorFactory(){@OverridepublicWebSocketHandlerdecorate(finalWebSocketHandlerhandler){返回新的WebSocketHandlerDecorator(handler){@OverridepublicvoidafterConnectionEstablished(finalWebSocketSessionsession)throwsException{*****}@OverridepublicvoidafterConnectionClosed(WebSocketSessionsession,CloseStatuscloseStatus)throwsException{*****}};}});superet.portureconfig(注册);}}#contoller@MessageMapping("/private")publicvoidprivatechat(ImMessagemessage)throwsException{*****template.convertAndSendToUser(message.getReceiver(),"/topic/private",message);////发送它订阅的频道///浏览器客户端,订阅路径为'/user/topic/private,}其中@MessageMapping("bar")//@MessageMapping接收客户端消息和@SendTo("/topic/brocast")//@SendTo向外广播消息@SendToUser("/topic/greetings")//发送给相应的人。这两个注解可以用template.convertAndSendTo和template.convertAndSendToUser在代码中实现。Spring-security部分做了简单的登录验证。如果登录成功,sessionid将存储在系统中。结合上面的订阅,实际链接的后台地址会附加sessionid,即httprequest中的登录授权信息,即javax.security.Principal,会绑定websocketsession。也就是说,可以实现与指定注册人的双向链接。页面展示部分页面展示核心应用vue,在vue不好实现的地方使用了jquery。样式采用的elmentUI。在开发这个im应用程序测试用例时。实现了一些前端效果。下面列出了核心有用的,在以后的学习中可能会用到。1、vue兼容性问题不是webpack开发模式,而是正常的html开发直接引入js。如果需要解决vue兼容问题,可以引入https://cdn.bootcss.com/babel...来解决。2、vue的应用使用方法(v-model、@click、v-html、v-forv-if、v-bind)、指令、过滤器、全局方法、watches的使用。该指令用于实现div的默认焦点。使用全局方法代替过滤器实现消息内容的实时base64解码。3、使用vue核心数据双向绑定,显示更新不刷新。但是数据被设计为多层json数组数据。当底层数据发生变化时,Vue无法自动检测到变化。需要人工检测。代码-this.$forceUpdate();4.关于图片信息的使用图片上传按钮上有一个透明的文件输入文件。每次上传后,先使用onchange方法检测文件大小和类型,然后通过fileReader进行预览。这就涉及到富文本框的焦点问题和base64转码的问题。另外,上传失败时,通过.val('')清除文件,这样可以重新选择文件上传,并成功触发onchange事件(值得知道的是,花了很长时间测试一下)varfilec=$("#file"+index);if(filec&&filec.length>0){varfileList=filec[0].files;if(!/image\/\w+/.test(fileList[0].type))//判断获取到的图片文件是否为{this.$message.error('请确认文件是图片文件');//清空输入再次上传并触发onchangefilec.val('')returnfalse;}if(fileList[0].size>1048576){这个。$message.error('请确保图片不大于1M');//清除输入重新上传并触发onchangefilec.val('')returnfalse;}fileReader.onload=function(e){//获取文件base64编码varbase64=e.target.resultvarimage=newImage();图片.src=base64;image.onload=function(){//文件像素太大,调小一点varnewW="";变量newH="";如果(this.width>this.height&&this.width>200){newW=200;newH=200/this.width*this.height;}如果(这个。宽度<=this.height&&this.height>200){newH=200;newW=200/this.height*this.width;}varh='';_insertimg(h,index)//插入到富文本对应的位置};}fileReader.readAsDataURL(fileList[0]);可以考虑baidueditor等成熟的产品。目前的富文本主要是利用html5的contenteditable属性来解决具体的解决方案。可以参考_insertimg方法。焦点被转移,影响实际插入发生的位置。所以需要设置这些按钮在点击时屏蔽默认效果。一个是按钮的@click.prvent。另外可以用下面的方法解决document.addEventListener("mousedown",function(e){if(e.target.id=="emoijT"){e.preventDefault()}},false);7.因为当前发送的消息是带html标签的富文本信息。为了避免传输问题,内容进行了base64转码,消息接收后转码回来。varstompClient=null;//防止乱码$.base64.utf8encode=true;$.base64.btoa(thisMessage);//使用插件base64编码//解码$.base64.atob(c,true);8、当前案例不仅实现了多对话窗口,还隐藏了对话提醒。还实现了当前人的浏览器标题提醒。varpageMes??sage={time:0,title:document.title,timer:null,//显示新消息提示show:function(){vartitle=pageMes??sage.title.replace("【 】","").replace("【新消息】","");//定时器,设置消息切换频率,产生闪烁效果pageMes??sage.timer=setTimeout(function(){pageMes??sage.time++;pageMes??sage.show();if(pageMes??sage.time%2==0){document.title="【新消息】"+title}else{document.title="【 】"+title};},600);返回[pageMes??sage.timer,pageMes??sage.title];},//取消新消息提示vclear:function(){clearTimeout(pageMes??sage.timer);文档.title=pageMes??sage.title;}};9.关于机器人的自动对话,目前是通过jsoup调用的远程接口,从中返回答案。虽然是免费接口,但是不能一天调用多次。Stringurl="http://www.tuling123.com/openapi/api";//请填写自己的keyStringuserid="454995";Stringpost="{\"key\":\"646d321c227045a69253fd07d8703840\",\"info\":\""+message.getContent()+"\",\"userid\":\""+userid+"\"}";Stringbody=Jsoup.connect(url).method(Connection.Method.POST).requestBody(post).header("Content-Type","application/json;charset=utf-8").ignoreContentType(true).execute().body();