基於WebSocket的web端IM即時通信應用的開發

基於WebSocket的web端IM即時通信應用的開發


功能列表:
一、Web端的IM應用
二、支持上線、下線、實時在線提醒
三、單聊、羣聊的創建
四、普通文字、表情、圖片的傳輸(子定義富文本)
五、單人的頂級提醒,多對話的窗口的提醒
六、調用圖靈機器人的自動回覆演示
核心技術列表
一、websocket、sockjs、stomp
二、前端展現涉及的jquery、vue、elementUI、jquerybase64js
三、後端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協議,來爲瀏覽器 和 server間的 通訊增長適當的消息語義。
STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的簡單文本協議。 同 HTTP 在 TCP 套接字上添加請求-響應模型層同樣,STOMP 在 WebSocket 之上提供了一個基於幀的線路格式層,用來定義消息語義;css

STOMP 源碼http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js,有興趣的能夠看一下能大體瞭解其原理和用法。

本例程序核心代碼:html

<!--TO 建立socket鏈接 並訂閱相關頻道-->
var socket = new SockJS('/im-websocket');
stompClient = Stomp.over(socket);
//設置stomp 控制檯日誌爲不輸出
stompClient.debug=null;
stompClient.connect({}, function (frame) {
       // 至關於鏈接 ws://localhost:8080/gs-guide-websocket/041/hk5tax0r/websocket hk5tax0r就是sessionid
    console.log("正在鏈接",socket._transport.url);
    //訂閱通用私聊頻道 羣組也經過這裏實現
    stompClient.subscribe('/user/topic/private', function (greeting) {
        
    }
   );
    //訂閱用戶上線下線的公共頻道
    stompClient.subscribe('/topic/userlist', function (greeting) {
        
    });
},function errorCallBack (error) {
    // 鏈接失敗時(服務器響應 ERROR 幀)的回調方法
 
});

數據發送以下:
//第一個參數對應controller的 @MessageMapping註解 /app爲後臺定義的通用前綴
//第三個參數爲內容字符串
stompClient.send("/app/private", {}, JSON.stringify(message));//發送服務器前端

對應服務端部分vue

#WebSocketConfig
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

  public List<User> onlineUser=new ArrayList<User>();
  @Autowired
  private SimpMessagingTemplate template;
  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic");
    config.setUserDestinationPrefix("/user");
    config.setApplicationDestinationPrefixes("/app");
    config.setCacheLimit(1048576);//大小 1M
  }
  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    //註冊的websocket接入點,前端連接的就是它
    registry.addEndpoint("/im-websocket").withSockJS();
  }

  @Override
  public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
    //設置 文件緩衝 大小 1M
//如不設置文件稍微大一點就報錯了
    registration.setMessageSizeLimit(1048576);
    registration.setSendBufferSizeLimit(1048576);
    registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
      @Override
      public WebSocketHandler decorate(final WebSocketHandler handler) {
        return new WebSocketHandlerDecorator(handler) {
          @Override
          public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
            ******
          }

          @Override
          public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
            throws Exception {
      *****
          }
        };
      }
    });
    super.configureWebSocketTransport(registration);
  }
}


#contoller
@MessageMapping("/private")
public void privatechat(ImMessage message) throws Exception {
*****
template.convertAndSendToUser(message.getReceiver(),"/topic/private",message);
////發送其訂閱的頻道
///瀏覽器客戶端,訂閱了’/user/topic/private這條路徑,
}

其中@MessageMapping("bar") //@MessageMapping接收客戶端消息
另外@SendTo("/topic/brocast") //@SendTo廣播消息出去
@SendToUser("/topic/greetings")//發送對應人。
這兩個註解能夠用template.convertAndSendTo、template.convertAndSendToUser在代碼實現。html5


Spring-security部分
作了簡單的登陸驗證,登陸成功即在系統存有sessionid。結合上面的訂閱,實際連接的後臺地址會附加上sessionid,也就是httprequest中的登陸受權信息即javax.security.Principal會被綁定到websocket的session中。便可以實現與指定登陸人的雙向連接。java


頁面展現部分
頁面展現核心應用vue,vue很差實現的地方使用的是jquery。樣式採用的elmentUI。
在開發這個im應用測試案例的時候。實現了一些前端效果。核心有用的列到下面,各位也許在後面的學習中可以用到。jquery

1.vue兼容性的問題
由於原本不是webpack開發模式,屬於直接引入js的普通HTML開發,如須要解決vue兼容性問題,能夠引入
https://cdn.bootcss.com/babel... 以解決。webpack

二、vue用法(v-model、@click、v-html、v-forv-if,v-bind)的應用,指令、過濾器、全局方法、watch的使用。其中指令用來實現div的默認焦點。全局方法用來代替過濾器,實現實時的消息內容base64解碼。
三、利用vue核心數據的雙向綁定,不刷新顯示更新。但數據設計爲多層的json數組數據,當底層數據變化,vue不能自動檢測到變化。須要進行手動檢測。
代碼-this.$forceUpdate();web

四、關於圖片消息的用法
用圖片上傳按鈕上,存在透明的fileinput文件,每次上傳完,經過onchange方法,先檢測文件大小、類型後,經過fileReader預覽。這其中涉及富文本框焦點的問題、和base64轉碼的問題。另外當上傳失敗經過.val('')清空file,這樣才能從新選擇文件上傳併成功觸發onchange事件(值得了解,試驗了半天才行)spring

var filec=$("#file"+index);
if (filec&&filec.length>0) {
    var fileList = filec[0].files;
    if(!/image\/\w+/.test(fileList[0].type))            //判斷獲取的是否爲圖片文件
    {
        this.$message.error('請確保文件爲圖像文件');
        //清空input 能夠再次上傳並觸發onchange
        filec.val('')
        return false;
    }
    if(fileList[0].size>1048576){
        this.$message.error('請確保圖片不大於1M');
        //清空input 能夠再次上傳並觸發onchange
        filec.val('')
        return false;
    }
    fileReader.onload = function (e) {
        // 獲取文件的base64編碼
        var base64 = e.target.result
        var image = new Image();
        image.src = base64;
        image.onload = function() {
            //文件像素過大,調整爲稍小的
            var newW="";var newH="";
            if(this.width>this.height&&this.width>200){
                newW=200;
                newH=200/this.width*this.height;
            }
            if(this.width<=this.height&&this.height>200){
                newH=200;
                newW=200/this.height*this.width;
            }
            var h = '<img src=' + base64 + ' width='+newW+' height='+newH+'>';
            _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);

七、由於當前發送的消息是帶html標籤的富文本信息,爲避免傳輸的問題,將內容進行base64轉碼,消息被接收後再轉碼回來。
var stompClient = null;
//防止亂碼
$.base64.utf8encode = true;
$.base64.btoa(thisMessage);//使用插件base64編碼
//解碼 $.base64.atob(c, true);

八、當前案例不只實現了多對話窗口,隱藏的對話提醒。也實現了當前人的瀏覽器標題提醒。

var pageMessage = {
    time: 0,
    title: document.title,
    timer: null,
    // 顯示新消息提示
    show: function () {
        var title = pageMessage.title.replace("【   】", "").replace("【新消息】", "");
        // 定時器,設置消息切換頻率閃爍效果就此產生
        pageMessage.timer = setTimeout(function () {
            pageMessage.time++;
            pageMessage.show();
            if (pageMessage.time % 2 == 0) {
                document.title = "【新消息】" + title
            }
            else {
                document.title = "【   】" + title
            }
            ;
        }, 600);
        return [pageMessage.timer, pageMessage.title];
    },
    // 取消新消息提示 v
    clear: function () {
        clearTimeout(pageMessage.timer);
        document.title = pageMessage.title;
    }
};

九、關於機器人自動對話,目前使用jsoup調用的遠程接口,由其返回答案。雖然是免費接口,可是一天不能調用屢次。

String url = "http://www.tuling123.com/openapi/api";
//請填寫本身的key
String userid="454995";
String post = "{\"key\": \"646d321c227045a69253fd07d8703840\",\"info\": \""+message.getContent()+"\",\"userid\":\""+userid+"\"}";
String body = Jsoup.connect(url).method(Connection.Method.POST)
        .requestBody(post)
        .header("Content-Type", "application/json; charset=utf-8")
        .ignoreContentType(true).execute().body();
相關文章
相關標籤/搜索