• .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/

  • 相关阅读:
    【转】java的string中,关于split空串总会返回单个元素的数组
    【转】Java实现将文件或者文件夹压缩成zip
    单例模式
    数据库隔离级别
    ckeditor+ckfinder
    extremecomponents
    在linux环境下重启oracle数据库,解决密码过期的问题
    20180918 begin
    hadoop免登录
    CentOS环境下通过YUM安装软件,搭建lnmp环境
  • 原文地址:https://www.cnblogs.com/huguodong/p/16305579.html
Copyright © 2020-2023  润新知