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

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


功能列表:
1、Web端的IM應用
2、支持上線、下線、實時在線提醒
3、單聊、羣聊的建立
4、普通文字、表情、圖片的傳輸(子定義富文本)
5、單人的頂級提醒,多對話的窗口的提醒
6、調用圖靈機器人的自動回覆演示
核心技術列表
1、websocket、sockjs、stomp
2、前端展示涉及的jquery、vue、elementUI、jquerybase64js
3、後端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 之上提供了一個基於幀的線路格式層,用來定義消息語義;

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

本例程序核心代碼:

<!--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));//發送服務器

對應服務端部分

#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在代碼實現。


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


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

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

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

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

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]);

5、關於富文本在指定焦點位置插入數據的問題,後續可以考慮baidueditor等成熟產品。
當前富文本主要利用了html5的屬性contenteditable解決的
具體可以查看_insertimg方法
6、在實現上述富文本的時候,類似插入表情、選擇圖片的時候,只要點擊屏幕,則當前頁面焦點即轉移,影響實際插入的位置。所以需要設置這些按鈕點擊的時候屏蔽默認效果。
一個是按鈕的@click.prvent。另外可以通過下面的方法解決

document.addEventListener("mousedown", function(e){
  if(e.target.id=="emoijT"){
        e.preventDefault()
    }
}, false);

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

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

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;
    }
};

9、關於機器人自動對話,目前使用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();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章