.Net6+Furion+Sqlsugar+SenparcSdk开发微信公众号系列之五:自定义MessageService来处理消息

一、目的

在胜派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/

posted @ 2022-05-25 08:49  HuTiger  阅读(1279)  评论(0编辑  收藏  举报