天天看點

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

作者:Hu廠長

一、目的

在勝派SDK的官方Demo中,我發現他把所有處理消息請求的方法都放在了CustomMessageHandler,這就導緻了CustomMessageHandler異常的臃腫,維護起來也挺麻煩。

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

是以我就想把處理消息這塊封提取出來,這樣代碼就清爽了很多,而且代碼維護也變得簡單。如圖所示,拿到消息之後直接丢給messageService處理,不需要關系如何處理的,隻需要傳回處理結果,這也是面向接口程式設計的好處。

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

二、自定義MessageService

WeiXinApi.Application項目services檔案夾建立Message檔案夾并建立MessageService類和接口

.Net6+Furion+Senparc開發微信公衆号五:自定義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

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService
.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

是以我們需要在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
                                    : #34;[非文字類型{((historyMessage is IRequestMessageEventKey eventKey) ? #34;-{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通過構造函數傳進來。

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

修改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傳進去

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

釋出到雲伺服器,測試一下效果,沒毛病

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService
.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

三、動态回複消息

上面的例子中,雖然可以自動回複消息,但是回複内容都是寫死在代碼裡,靈活性太差,我們可以将自動回複内容改為從資料庫讀取,然後再回複。正好趁着這個機會推薦一波Sqlugar,國産最NB的ORM。

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

引入sqlsugar的nuget包

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

我們使用的是Sqlsugar的單例模式,簡單粗暴,直接在WeiXinApi.Core項目下建立DB檔案夾

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

直接定義靜态變量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,具體用法可以去官網看看

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

建立完會自動生成sqlite檔案

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

下面開始建表,使用的是類建表

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

先簡單的建一個消息回複表

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

選擇建立的類,點選預覽

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

WeiXinApi.Core項目建立Entity檔案夾

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

在檔案夾下建立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

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService
namespace WeiXinApi.Core
{
    public enum ReceiveType
    {
        文字 = 1,
        圖檔
    }
}           

将實體中的ReceiveType從int改為我們的枚舉

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

我們需要一些資料,首先将建的表同步到資料庫

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

手動添加一些資料

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

測試一下有沒有資料

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

查到了3條資料

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

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(#34;{i+1}:{receives[i].KeyWords}\r\n");
                }
                responseMessage.Content = result.ToString();

            }
            return responseMessage;
        }           

修改CustomMessageHandler的OnTextRequestAsync,改成OnTextDbRequestAsync

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

釋出伺服器測試一下,沒毛病

.Net6+Furion+Senparc開發微信公衆号五:自定義MessageService

四、本章Gitee位址

https://gitee.com/huguodong520/weixinapi/tree/%E8%87%AA%E5%AE%9A%E4%B9%89MessageService/

繼續閱讀