一、目的
在勝派SDK的官方Demo中,我發現他把所有處理消息請求的方法都放在了CustomMessageHandler,這就導致了CustomMessageHandler異常的臃腫,維護起來也挺麻煩。
所以我就想把處理消息這塊封提取出來,這樣代碼就清爽了很多,而且代碼維護也變得簡單。如圖所示,拿到消息之後直接丟給messageService處理,不需要關係如何處理的,只需要返回處理結果,這也是面向接口編程的好處。
二、自定義MessageService
WeiXinApi.Application項目services文件夾新建Message文件夾並新建MessageService類和接口
MessageService類繼承IMessageService接口,並且通過Furion註冊生命週期爲瞬時
namespace WeiXinApi.Application.Services { public class MessageService : IMessageService, ITransient { } }
在CustomMessageHandler.cs中重寫OnTextRequestAsync處理文字消息的請求方法
public override async Task<IResponseMessageBase> OnTextRequestAsync(RequestMessageText requestMessage) { return await base.OnTextRequestAsync(requestMessage); }
根據官方demo中的代碼,OnTextRequestAsync方法的返回類型是IResponseMessageBase,要用到的參數是requestMessage,currentMessageContext,GlobalMessageContext.ExpireMinutes, GlobalMessageContext.MaxRecordCount
所以我們需要在IMessageService中定義一個方法OnTextRequestAsync
namespace WeiXinApi.Application.Services { public interface IMessageService { /// <summary> /// 處理文字消息 /// </summary> /// <param name="requestMessage"></param> /// <param name="mpMessageContext"></param> /// <param name="ExpireMinutes"></param> /// <param name="MaxRecordCount"></param> /// <returns></returns> Task<ResponseMessageText> OnTextRequestAsync(RequestMessageText requestMessage, DefaultMpMessageContext mpMessageContext, int ExpireMinutes, int MaxRecordCount); } }
先簡單的實現一下接口,基本就是把官方demo中的代碼簡化了一下
public async Task<ResponseMessageText> OnTextRequestAsync(RequestMessageText requestMessage, DefaultMpMessageContext mpMessageContext, int ExpireMinutes, int MaxRecordCount) { var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage); var requestHandler = await requestMessage.StartHandler() //關鍵字不區分大小寫,按照順序匹配成功後將不再運行下面的邏輯 .Keyword("你好", () => { responseMessage.Content = "你也好啊!"; return responseMessage; }).Default(async () => { var result = new StringBuilder(); result.AppendFormat("您剛纔發送了文字信息:{0}\r\n\r\n", requestMessage.Content); var currentMessageContext = mpMessageContext; if (currentMessageContext.RequestMessages.Count > 1) { result.AppendFormat("您此前還發送了如下消息({0}/{1}):\r\n", currentMessageContext.RequestMessages.Count, currentMessageContext.StorageData); for (int i = currentMessageContext.RequestMessages.Count - 2; i >= 0; i--) { var historyMessage = currentMessageContext.RequestMessages[i]; result.AppendFormat("{0} 【{1}】{2}\r\n", historyMessage.CreateTime.ToString("HH:mm:ss"), historyMessage.MsgType.ToString(), (historyMessage is RequestMessageText) ? (historyMessage as RequestMessageText).Content : $"[非文字類型{((historyMessage is IRequestMessageEventKey eventKey) ? $"-{eventKey.EventKey}" : "")}]" ); } result.AppendLine("\r\n"); } result.AppendFormat("如果您在{0}分鐘內連續發送消息,記錄將被自動保留(當前設置:最多記錄{1}條)。過期後記錄將會自動清除。\r\n", ExpireMinutes, MaxRecordCount); result.AppendLine("\r\n"); result.AppendLine( "您還可以發送【位置】【圖片】【語音】【視頻】等類型的信息(注意是這幾種類型,不是這幾個文字),查看不同格式的回覆。\r\nSDK官方地址:https://sdk.weixin.senparc.com"); responseMessage.Content = result.ToString(); return responseMessage; }); return responseMessage; }
這裏的currentMessageContext.StorageData這是一個用於儲存任何和用戶上下文有關數據的容器,WeixinContext和IMessageContext沒有對它進行任何引用,完全由開發者決定裏面的內容(比如用戶執行到哪一步、或某個比較重要的位置信息等等),類似於Session的作用。這裏官網的demo裏用到了,我直接把官方demo裏的,拿過來抄了,直接在CustomMessageHandler.cs重寫下面兩個方法
public override async Task OnExecutedAsync(CancellationToken cancellationToken) { //演示:MessageContext.StorageData var currentMessageContext = await base.GetUnsafeMessageContext();//爲了在分佈式緩存下提高讀寫效率,使用此方法,如果需要獲取實時數據,應該使用 base.GetCurrentMessageContext() currentMessageContext.StorageData = ((int)currentMessageContext.StorageData) + 1; GlobalMessageContext.UpdateMessageContext(currentMessageContext);//儲存到緩存 await base.OnExecutedAsync(cancellationToken); } public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage) { var responseMessage = base.CreateResponseMessage<ResponseMessageText>(); //ResponseMessageText也可以是News等其他類型 responseMessage.Content = "這條消息來自DefaultResponseMessage。"; return responseMessage; }
每次用戶發送消息都會存到緩存中,所以我們要在ConfigureServices裏注入緩存服務
services.AddMemoryCache();//使用本地緩存必須添加
修改CustomMessageHandler,將Imessageservice通過構造函數傳進來。
修改OnTextRequestAsync,當接收到文字消息的時候,調用messageservice裏的OnTextRequestAsync方法
public override async Task<IResponseMessageBase> OnTextRequestAsync(RequestMessageText requestMessage) { var currentMessageContext = await base.GetCurrentMessageContext(); var result = await _messageService.OnTextRequestAsync(requestMessage, currentMessageContext, GlobalMessageContext.ExpireMinutes, GlobalMessageContext.MaxRecordCount); return result; }
最後就是在WeixinService裏注入IMessageService
private readonly IMessageService _messageService; public WeiXinService(IHttpContextAccessor httpContextAccessor, IMessageService messageService) { this._httpContextAccessor = httpContextAccessor; this._messageService = messageService; }
new CustomMessageHandler的時候把_messageservice傳進去
發佈到雲服務器,測試一下效果,沒毛病
三、動態回覆消息
上面的例子中,雖然可以自動回覆消息,但是回覆內容都是寫死在代碼裏,靈活性太差,我們可以將自動回覆內容改爲從數據庫讀取,然後再回復。正好趁着這個機會推薦一波Sqlugar,國產最NB的ORM。
引入sqlsugar的nuget包
我們使用的是Sqlsugar的單例模式,簡單粗暴,直接在WeiXinApi.Core項目下新建DB文件夾
直接定義靜態變量Db
using Furion; using SqlSugar; using System; using System.IO; namespace WeiXinApi.Core { public class DbContext { public static string ConnectionString = Path.Combine(App.WebHostEnvironment.ContentRootPath, "weixin.sqlite"); public static SqlSugarScope Db = new SqlSugarScope(new ConnectionConfig() { DbType = SqlSugar.DbType.Sqlite, ConnectionString = "DataSource=" + ConnectionString, IsAutoCloseConnection = true }, db => { //單例參數配置,所有上下文生效 db.Aop.OnLogExecuting = (s, p) => { var sql = UtilMethods.GetSqlString(DbType.SqlServer, s, p); Console.WriteLine(sql); }; }); } }
我們需要創建數據庫和表,這裏我直接使用的sqlite數據庫,生成表和實體我用的是sqlsugar推薦的webfirst,具體用法可以去官網看看
創建完會自動生成sqlite文件
下面開始建表,使用的是類建表
先簡單的建一個消息回覆表
選擇創建的類,點擊預覽
WeiXinApi.Core項目新建Entity文件夾
在文件夾下新建MessageReceive實體類,將預覽的實體類複製進去
using SqlSugar; namespace WeiXinApi.Core { /// <summary> /// 自動回覆表 ///</summary> [SugarTable("MessageReceive")] public class MessageReceive { /// <summary> /// 主鍵 ///</summary> [SugarColumn(ColumnName = "Id", IsPrimaryKey = true,IsIdentity = true)] public int Id { get; set; } /// <summary> /// 回覆類型:文字,圖片等 ///</summary> [SugarColumn(ColumnName = "ReceiveType")] public int ReceiveType { get; set; } /// <summary> /// 關鍵字 ///</summary> [SugarColumn(ColumnName = "KeyWords")] public string KeyWords { get; set; } /// <summary> /// 回覆內容 ///</summary> [SugarColumn(ColumnName = "ReceiveString")] public string ReceiveString { get; set; } } }
因爲我們的回覆類型可以是枚舉,所以我們新建一個枚舉類ReceiveType
namespace WeiXinApi.Core { public enum ReceiveType { 文字 = 1, 圖片 } }
將實體中的ReceiveType從int改爲我們的枚舉
我們需要一些數據,首先將建的表同步到數據庫
手動添加一些數據
INSERT INTO "MessageReceive" ("Id", "ReceiveType", "KeyWords", "ReceiveString") VALUES (1, '1', '你好', '你也好'); INSERT INTO "MessageReceive" ("Id", "ReceiveType", "KeyWords", "ReceiveString") VALUES (2, '1', '在嗎', '我在'); INSERT INTO "MessageReceive" ("Id", "ReceiveType", "KeyWords", "ReceiveString") VALUES (3, '1', '激活碼', '1234567');
測試一下有沒有數據
查到了3條數據
IMessageService新增一個接口
/// <summary> /// 從數據庫處理文字消息 /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> Task<ResponseMessageText> OnTextDbRequestAsync(RequestMessageText requestMessage);
實現接口
public async Task<ResponseMessageText> OnTextDbRequestAsync(RequestMessageText requestMessage) { var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage); var receives = await DbContext.Db.Queryable<MessageReceive>().ToListAsync();//獲取列表 var receive = receives.Where(it => it.KeyWords == requestMessage.Content).FirstOrDefault();//查找關鍵字是否存在 if (receive != null) { responseMessage.Content = receive.ReceiveString; } else { //如果關鍵字搜不到,列出關鍵字 var result = new StringBuilder(); result.AppendFormat("聽不懂你再說什麼,可以試試下面的關鍵字\r\n\r\n"); for (int i = 0; i < receives.Count; i++) { result.AppendFormat($"{i+1}:{receives[i].KeyWords}\r\n"); } responseMessage.Content = result.ToString(); } return responseMessage; }
修改CustomMessageHandler的OnTextRequestAsync,改成OnTextDbRequestAsync
發佈服務器測試一下,沒毛病
四、本章Gitee地址
https://gitee.com/huguodong520/weixinapi/tree/%E8%87%AA%E5%AE%9A%E4%B9%89MessageService/