在胜派SDK的官方Demo中,我发现他把所有处理消息请求的方法都放在了CustomMessageHandler,这就导致了CustomMessageHandler异常的臃肿,维护起来也挺麻烦。
所以我就想把处理消息这块封提取出来,这样代码就清爽了很多,而且代码维护也变得简单。如图所示,拿到消息之后直接丢给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
发布服务器测试一下,没毛病