1. 程式人生 > >Senparc.Weixin.MP SDK 微信公眾平臺開發教程(二十一):在小程式中使用 WebSocket (.NET Core)

Senparc.Weixin.MP SDK 微信公眾平臺開發教程(二十一):在小程式中使用 WebSocket (.NET Core)

  本文將介紹如何在 .NET Core 環境下,藉助 SignalR 在小程式內使用 WebSocket。關於 WebSocket 和 SignalR 的基礎理論知識不在這裡展開,已經有足夠的參考資料,例如參考 SignalR 的官方教程:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction?view=aspnetcore-2.1

  我們先看一下完成本教程內容後,在小程式內實現的 WebSocket 效果:

私有及群發訊息

 

小程式模板訊息

 

  上圖中包含了只有傳送者本人可以收到的訊息,以及群發的訊息,還有一條傳送者本人收到的模板訊息。

  所有程式碼都已開源,可以在  https://github.com/JeffreySu/WeiXinMPSDK 中找到,其中伺服器端程式碼位於 /Samples/Senparc.Weixin.MP.Sample.vs2017/Senparc.Weixin.MP.Sample.vs2017.sln 解決方案中,小程式客戶端程式碼位於 /src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen.AppDemo 中。

  下面讓我們開始吧!

  

伺服器端

  首先,我們需要通過 nuget 安裝 Separc.WebSocket 包:https://www.nuget.org/packages/Senparc.WebSocket。Senparc.WebSocket 包提供了可供小程式使用的 WebScoket 的基礎功能封裝(當然,它也可以被很好地使用在小程式以外的場景,只不過在其他場景中我們通常有更多的選擇,以及更多的複雜的業務需求)。

       注意:Senparc.WebSocket 提供了針對 .NET Framework 4.5 和 .NET Standard 2.0(支援 .NET Core)的兩個版本。其中,前者使用了相對比較底層的 WebSocket 構建方式,而後者我們直接使用了 SignalR,因為 .NET Core 下的 SignalR 官方擺脫了對 jQuery 的依賴,而 jQuery 在微信小程式中是不被支援的,這也是為什麼我們沒有在 .NET Framework 4.5 中沒有使用 SignalR 這個優秀元件的原因。

       安裝完畢 Senparc.WebSocket 之後,我們還需要在 startup.cs 中對其進行啟用,如在 ConfigureServices() 方法中新增程式碼:

services.AddSenparcWebSocket<CustomNetCoreWebSocketMessageHandler>();//Senparc.WebSocket 註冊(按需)

  注意:上述過程會附帶執行 services.AddSignalR(); 指令,因此如果你在系統的其他地方也需要使用 SIgnalR,那麼可以忽略對其註冊過程。

  關於上述方法中的泛型 CustomNetCoreWebSocketMessageHandler,其邏輯類似於公眾號的 CustomMessageHandler,用於對訊息進行同一的處理。讓我們來看一下 CustomNetCoreWebSocketMessageHandler.cs的樣子:

 1 using System;
 2 using System.Linq;
 3 using System.Threading.Tasks;
 4 using Senparc.WebSocket;
 5 using Senparc.Weixin.MP.AdvancedAPIs.TemplateMessage;
 6 using Senparc.Weixin.WxOpen.Containers;
 7 
 8 namespace Senparc.Weixin.MP.Sample.CommonService.MessageHandlers.WebSocket
 9 {
10     /// <summary>
11     /// .NET Core 自定義 WebSocket 處理類
12     /// </summary>
13     public class CustomNetCoreWebSocketMessageHandler : WebSocketMessageHandler
14     {
15         public override Task OnConnecting(WebSocketHelper webSocketHandler)
16         {
17             //TODO:處理連線時的邏輯
18             return base.OnConnecting(webSocketHandler);
19         }
20 
21         public override Task OnDisConnected(WebSocketHelper webSocketHandler)
22         {
23             //TODO:處理斷開連線時的邏輯
24             return base.OnDisConnected(webSocketHandler);
25         }
26 
27 
28         public override async Task OnMessageReceiced(WebSocketHelper webSocketHandler, ReceivedMessage receivedMessage, string originalData)
29         {
30             if (receivedMessage == null || string.IsNullOrEmpty(receivedMessage.Message))
31             {
32                 return;
33             }
34 
35             var message = receivedMessage.Message;
36 
37             await webSocketHandler.SendMessage("originalData:" + originalData, webSocketHandler.WebSocket.Clients.Caller);
38             await webSocketHandler.SendMessage("您傳送了文字:" + message, webSocketHandler.WebSocket.Clients.Caller);
39             await webSocketHandler.SendMessage("正在處理中(反轉文字)...", webSocketHandler.WebSocket.Clients.Caller);
40 
41             await Task.Delay(1000);
42 
43             //處理文字
44             var result = string.Concat(message.Reverse());
45             await webSocketHandler.SendMessage(result, webSocketHandler.WebSocket.Clients.Caller);
46 
47             var appId = Config.SenparcWeixinSetting.WxOpenAppId;//與微信小程式賬號後臺的AppId設定保持一致,區分大小寫。
48 
49             try
50             {
51             
52                 var sessionBag = SessionContainer.GetSession(receivedMessage.SessionId);
53 
54                 //臨時演示使用固定openId
55                 var openId = sessionBag != null ? sessionBag.OpenId : "onh7q0DGM1dctSDbdByIHvX4imxA";// "使用者未正確登陸";
56 
57                 //await webSocketHandler.SendMessage("OpenId:" + openId, webSocketHandler.WebSocket.Clients.Caller);
58                 //await webSocketHandler.SendMessage("FormId:" + formId);
59 
60                 //群發
61                 await webSocketHandler.SendMessage($"[群發訊息] [來自 OpenId:***{openId.Substring(openId.Length - 10, 10)},暱稱:{sessionBag.DecodedUserInfo?.nickName}]:{message}", webSocketHandler.WebSocket.Clients.All);
62 
63                 //傳送模板訊息
64 
65                 //var data = new WxOpenTemplateMessage_PaySuccessNotice(
66                 //    "線上購買", SystemTime.Now, "圖書眾籌", "1234567890",
67                 //    100, "400-9939-858", "http://sdk.senparc.weixin.com");
68 
69                 var formId = receivedMessage.FormId;//傳送模板訊息使用,需要在wxml中設定<form report-submit="true">
70 
71                 var data = new
72                 {
73                     keyword1 = new TemplateDataItem("來自小程式WebSocket的模板訊息(測試資料)"),
74                     keyword2 = new TemplateDataItem(SystemTime.Now.LocalDateTime.ToString()),
75                     keyword3 = new TemplateDataItem($"來自 Senparc.Weixin SDK 小程式 .Net Core WebSocket 觸發\r\n您剛才傳送了文字:{message}"),
76                     keyword4 = new TemplateDataItem(SystemTime.NowTicks.ToString()),
77                     keyword5 = new TemplateDataItem(100.ToString("C")),
78                     keyword6 = new TemplateDataItem("400-031-8816"),
79                 };
80 
81                 var tmResult = Senparc.Weixin.WxOpen.AdvancedAPIs.Template.TemplateApi.SendTemplateMessage(appId, openId, "Ap1S3tRvsB8BXsWkiILLz93nhe7S8IgAipZDfygy9Bg", data, receivedMessage.FormId, "pages/websocket/websocket", "websocket",
82                          null);
83             }
84             catch (Exception ex)
85             {
86                 var msg = ex.Message + "\r\n\r\n" + originalData + "\r\n\r\nAPPID:" + appId;
87 
88                 await webSocketHandler.SendMessage(msg, webSocketHandler.WebSocket.Clients.Caller); //VS2017以下如果編譯不通過,可以註釋掉這一行
89 
90                 WeixinTrace.SendCustomLog("WebSocket OnMessageReceiced()過程出錯", msg);
91             }
92         }
93     }
94 }
View Code

  從上述程式碼啊可以看到,CustomNetCoreWebSocketMessageHandler 繼承了來自 Senparc.WebSocket 的 WebSocketMessageHandler 抽象類,實現了 3 個重要的方法:

  •     OnConnecting()
  •     OnDisConnected()
  •     OnMessageReceiced()

 

  其中最重要的就是 OnMessageReceiced() 方法,用於處理來自於小程式的 WebSocket 訊息(注意:這條訊息在小程式中經過了特殊約定的處理,才能到達此處,並被正確讀取,在本文後面將會介紹)。

  上述方法中,執行了如下邏輯:

  1. 讀取訊息
  2. 向用戶的客戶端即時傳送三條訊息(originalData……、您傳送了文字……、正在處理中(反轉文字)...),本條訊息只有傳送者可以收到(注意使用了webSocketHandler.WebSocket.Clients.Caller)
  3. 對使用者傳送的訊息字串進行處理,使其反轉
  4. 將處理結果傳送給操作人的客戶端(Caller)
  5. 執行緒休眠 1 秒鐘(模擬長時間的系統執行)
  6. 向所有已經連線的客戶端群發訊息(包括髮送者本人在內):[群發訊息] [來自 OpenId:***…… (注意使用了webSocketHandler.WebSocket.Clients.All)
  7. 獲取 formId,並使用獲取到的 formId 傳送模板訊息

 

  上述過程中,一共傳送了 5 條 WebSocket 訊息,其中 4 條只有傳送者本人可以收到,1 條所有人(包括髮送者本人)可以收到,除此之外,還發送了一條模板訊息。

  我們可以使用 SignalR 的優秀特性,有針對性地進行定向傳送或分組傳送,相關知識點可以參考官方教程,這裡不再展開。

  

  接下來,我們需要在 Startup.cs 對 SignalR 新增一個 Hub 的路由規則,在  Configure() 方法中新增:

            //使用 SignalR
            app.UseSignalR(routes =>
            {
                routes.MapHub<SenparcHub>("/senparcHub");
            });

  根據 SignalR 的定義,"/senparcHub" 是我們自定義的 Hub 的連線地址,和類的命名沒有關聯。泛型 SenparcHub 是我們自定義的 SignalR 類,程式碼如下:

using Senparc.WebSocket.SignalR;

namespace Senparc.Weixin.MP.CoreSample.WebSocket.Hubs
{
    public class SenparcHub : SenparcWebSocketHubBase
    {

    }
}

  注意:和常規的 SignalR 的 Hub 自定義類直接繼承 Microsoft.AspNetCore.SignalR.Hub 類不同,這裡繼承了 SenparcWebSocketHubBase,由 SenparcWebSocketHubBase 繼承了 Microsoft.AspNetCore.SignalR.Hub 類,SenparcWebSocketHubBase 為自動處理小程式訊息進行了一系列的邏輯處理。因此,SenparcHub 也具備了所有  Microsoft.AspNetCore.SignalR.Hub 的能力,我們也可以用於常規的網頁 WebSocket 通訊。

  下面我們來豐富一下這個類:

using Microsoft.AspNetCore.SignalR;
using Senparc.WebSocket.SignalR;
using System.Threading.Tasks;

namespace Senparc.Weixin.MP.CoreSample.WebSocket.Hubs
{
    public class SenparcHub : SenparcWebSocketHubBase
    {
        /// <summary>
        /// 給普通網頁用的自定義擴充套件方法 /WebScoket
        /// </summary>
        /// <param name="user"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task SendCustomMessage(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveCustomMessage", user, message);
        }
    }
}

  這裡所新增的 SendCustomMessage 就是給普通網頁用的,可以參考官方教程:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-2.1&tabs=visual-studio,關於頁面上的程式碼以及js檔案的開發不再贅述。

  可以開啟網頁 https://sdk.weixin.senparc.com/WebSocket 進行測試,效果如下:

 

  關於 Web 端的 WebSocket 不是本文的重點,這裡不再展開,下面看小程式的客戶端中如何處理。

 

小程式客戶端

  第一步,建立或開啟小程式客戶端專案,如圖:

  第二步:在 utils 資料夾中加入加入兩個檔案:

  1. signalr.1.0.js 檔案(https://github.com/JeffreySu/WeiXinMPSDK/blob/Developer/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen.AppDemo/utils/signalr.1.0.js)
  2. senparc.websocket.js 檔案(https://github.com/JeffreySu/WeiXinMPSDK/blob/Developer/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen.AppDemo/utils/senparc.websocket.js)

  注意:signalr.js 檔案還在測試中,目前請勿使用!雖然這裡的 signalr.1.0.js 使用的並非最新的版本的 js 客戶端檔案,不過至今為止仍然支援最新的 SignalR nuget 包,可以放心使用。

  特別感謝 LcFireRabbit 提供的修改後的 signalr.js 檔案!如果對於新版本 js 您有更好的解決方案,也歡迎留言交流!

 

 

  第三步:建立測試頁面,見 websocket_signalr 資料夾:

 

  由於原始碼都已經提供,這裡不再展開,介紹一下和 WebSocket 通訊相關的核心程式碼,在 websocket_signalr.js 中:

 1 var signalR = require("../../utils/signalr.1.0.js")
 2 var senparcWebsocket = require("../../utils/senparc.websocket.js")
 3 
 4 var connection;// Signalr 連線
 5 var app = getApp()
 6 var socketOpen = false;//WebSocket 開啟狀態
 7 Page({
 8   data: {
 9     messageTip: '正在連線中,請等待...',
10     messageTextArr: [],
11     messageContent: 'TEST',
12     userinfo: {}
13   },
14   //sendMessage
15   formSubmit: function (e) {
16     var that = this;
17     console.log('formSubmit', e);
18     if (socketOpen) {
19       var text = e.detail.value.messageContent;//必填,獲得輸入文字
20       var sessionId = wx.getStorageSync("sessionId");//選填,不需要可輸入''
21       var formId = e.detail.formId//選填formId用於傳送模板訊息,不需要可輸入''
22       senparcWebsocket.sendMessage(text,sessionId,formId);//傳送 websocket 請求
23 
24       that.setData({
25         messageContent: ''
26       })
27     } else {
28       that.setData({
29         messageTip: 'WebSocket 連結失敗,請重新連線!'
30       })
31     }
32   },
33   onLoad: function () {
34     console.log('onLoad')
35   },
36   onShow:function(){
37     console.log('onShow');
38 
39     var that = this;
40     var hubUrl = wx.getStorageSync('wssDomainName') + "/SenparcHub";//Hub Url
41     var onStart = function () {
42       console.log('ws started');
43       socketOpen = true;
44       that.setData({
45         messageTip: 'WebSocket 連線成功!'
46       })
47     };
48     connection = senparcWebsocket.buildConnectionAndStart(hubUrl, signalR, onStart);
49 
50     //定義收到訊息後觸發的事件
51     var onReceive = function (res) {
52       console.log('收到伺服器內容:' + res)
53       var jsonResult = JSON.parse(res);
54       var currentIndex = that.data.messageTextArr.length + 1;
55       var newArr = that.data.messageTextArr;
56       newArr.unshift(
57         {
58           index: currentIndex,
59           content: jsonResult.content,
60           time: jsonResult.time
61         });
62       console.log(that);
63       that.setData({
64         messageTextArr: newArr
65       });
66     };
67     senparcWebsocket.onReceiveMessage(onReceive);
68 
69     //WebSocket 連線成功
70     wx.onSocketOpen(function (res) {
71       console.log('WebSocket 連線成功!')
72       
73     })
74     //WebSocket 已關閉
75     wx.onSocketClose(function (res) {
76       console.log('WebSocket 已關閉!')
77       socketOpen = false;
78     })
79     //WebSocket 開啟失敗
80     wx.onSocketError(function (res) {
81       console.log('WebSocket連線開啟失敗,請檢查!')
82     })
83   }
84 })
View Code

  其中:

  • 第 1 行引入之前建立的 signalr.1.0.js 檔案。
  • 第 2 行引入之前建立的 senparc.websocket.js 檔案。
  • 第 15 行由點選了介面上的【傳送】按鈕之後,觸發提交form表單的事件(不使用form也可以,只是無法獲取到formId,並用於傳送模板訊息)。
  • 第 19-21行準備了 3 個引數:text、sessionId、formId
  • 第 22 行使用封裝好的 senparcWebsocket 進行 WebSocket 訊息傳送, Senparc.WebScoket 的方法內部能夠自動處理的格式,必須嚴格按照此格式設定,可以通過修改 senparc.websocket.js 增加屬性,但是目前已經提供的屬性不能減少(FormId 如果沒有可以留空)。
  • 第 36 行開始,我們把 websocket 的連線和設定程式碼放在了 onShow() 事件中,而不是 onLoad(),這樣可以確保使用者每次回到此頁面都能夠正確獲得連線。當然為了節省開銷,我們也可以在 onLoad() 中啟動連線,並在 onShow() 中對連線狀態進行判斷,這裡是簡化的做法。
  • 第 40 行,設定 SenparcHub 的連線地址。
  • 第 41-47 行,設定連線成功後的事件程式碼。
  • 第 48 行,正式啟動 WebSocket 連線。
  • 第 51-67 行,設定收到 WebSocket 訊息後的觸發事件程式碼。
  • 第 69-82 行,使用微信官方的介面進行連線狀態的監控。

 

  以上效果可以掃描下方二維碼,授權登入後,點選【WebSocket】按鈕進行體驗(可開多個手機微信進行測試,但建議不要在一臺上多開,微信的 websocket 限制比較多)。

 

  微信小程式開發QQ群:108830388 

 

列教程索引

地址:http://www.cnblogs.com/szw/archive/2013/05/14/weixin-course-index.html

  1. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(一):微信公眾平臺註冊
  2. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(二):成為開發者
  3. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(三):微信公眾平臺開發驗證
  4. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(四):Hello World
  5. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(五):使用Senparc.Weixin.MP SDK
  6. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(六):瞭解MessageHandler
  7. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(七):解決使用者上下文(Session)問題
  8. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(八):通用介面說明
  9. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(九):自定義選單介面說明
  10. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十):多客服介面說明
  11. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十一):高階介面說明
  12. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十二):OAuth2.0說明
  13. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十三):地圖相關介面說明
  14. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十四):請求訊息去重
  15. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十五):訊息加密
  16. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十六):AccessToken自動管理機制
  17. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十七):個性化選單介面說明
  18. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十八):Web代理功能
  19. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(十九):MessageHandler 的未知型別訊息處理
  20. Senparc.Weixin.MP SDK 微信公眾平臺開發教程(二十):使用選單訊息功能

&n