1、前言 既然是.net下微信開發,自然少不了Senparc,可以說這個框架的存在, 至少節省了微信相關工作量的80%。事實上,項目開始前,還糾結了下是Java還是core,之所以最終選擇core,除了情懷外,更重要的便是這個微信開發框架的存在。本項目的整合方式,極大程度上參考了Senparc官方的 ...
1、前言
既然是.net下微信開發,自然少不了Senparc,可以說這個框架的存在, 至少節省了微信相關工作量的80%。事實上,項目開始前,還糾結了下是Java還是core,之所以最終選擇core,除了情懷外,更重要的便是這個微信開發框架的存在。本項目的整合方式,極大程度上參考了Senparc官方的示例,官方示例可以說很全面、詳細了。
2、整合方式
1)增加Senparc配置節
appsettings.json中添加如下配置節:
"SenparcSetting": { "IsDebug": true, "DefaultCacheNamespace": "Fuck" //分散式緩存 //"Cache_Redis_Configuration": "#{Cache_Redis_Configuration}#", //"Cache_Memcached_Configuration": "#{Cache_Memcached_Configuration}#", //"SenparcUnionAgentKey": "#{SenparcUnionAgentKey}#" }, "SenparcWeixinSetting": { "IsDebug": true, "Token": "Fuck", "EncodingAESKey": "FuckKey", "WeixinAppId": "FuckAppId", "WeixinAppSecret": "FuckAppSecret" },
SenparcSetting部分是Senparc底層的通用配置,目前我項目中暫未用到,如果用到則對應配置,如緩存的命名空間,用來防止多應用可能的緩存key衝突,分散式緩存連接等。
SenparcWeixinSetting是公眾號相關的配置,Token、EncodingAESKey、WeixinAppId、WeixinAppSecret均分別對應公眾號後臺的賬戶信息,不多贅述。生產環境中,記得把上述IsDebug配置為false,減少調試信息及提高性能。
2) 微信消息處理器
增加自定義消息處理器,繼承至MessageHandler<DefaultMpMessageContext>:
public class CustomMessageHandler : MessageHandler<DefaultMpMessageContext> { public CustomMessageHandler(Stream inputStream, PostModel postModel, int maxRecordCount = 0, bool onlyAllowEcryptMessage = false) : base(inputStream, postModel, maxRecordCount, onlyAllowEcryptMessage) { OnlyAllowEcryptMessage = true; //在指定條件下,不使用消息去重 base.OmitRepeatedMessageFunc = requestMessage => { var textRequestMessage = requestMessage as RequestMessageText; if (textRequestMessage != null && textRequestMessage.Content == "容錯") { return false; } return true; }; } public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage) { var responseMessage = this.CreateResponseMessage<ResponseMessageText>(); responseMessage.Content = "您好,歡迎關註XXXX!"; return responseMessage; } }
重寫的DefaultResponseMessage方法表示,系統收到微信用戶收到的任何消息時,都自動回覆"您好,歡迎關註XXXX!"的文本消息。MessageHandler<DefaultMpMessageContext>中可以重載的方法很多,主要是響應微信終端動作的一系列方法,比如用戶發送文本、用戶點擊鏈接、用戶發送圖片、發送位置等,如果你需要處理對應事件,那就重載對應方法,我這裡偷懶了,搞了個所有類型消息預設回覆。
3)系統與微信通信
增加控制器,如下:
[AllowAnonymous] public class WeixinController : Controller { private readonly IWebHostEnvironment _env; private readonly SenparcWeixinSetting _weixinSetting; public WeixinController(IWebHostEnvironment env, IOptions<SenparcWeixinSetting> weixinSetting) { _env = env; _weixinSetting = weixinSetting.Value; } [HttpGet] [ActionName("Index")] public Task<ActionResult> Get(string signature, string timestamp, string nonce, string echostr) { return Task.Factory.StartNew(() => { if (CheckSignature.Check(signature, timestamp, nonce, _weixinSetting.Token)) { return echostr; //返回隨機字元串則表示驗證通過 } else { return $"failed:{signature},{CheckSignature.GetSignature(timestamp, nonce, _weixinSetting.Token)}。如果你在瀏覽器中看到這句話,說明此地址可以被作為微信公眾賬號後臺的Url,請註意保持Token一致。"; } }) .ContinueWith<ActionResult>(task => Content(task.Result)); } /// <summary> /// 最簡化的處理流程 /// </summary> [HttpPost] [ActionName("Index")] public async Task<ActionResult> Post(PostModel postModel) { if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, _weixinSetting.Token)) { return new WeixinResult("參數錯誤!"); } postModel.Token = _weixinSetting.Token; postModel.EncodingAESKey = _weixinSetting.EncodingAESKey; postModel.AppId = _weixinSetting.WeixinAppId; var cancellationToken = new CancellationToken(); var messageHandler = new CustomMessageHandler(Request.GetRequestMemoryStream(), postModel, 10) { DefaultMessageHandlerAsyncEvent = DefaultMessageHandlerAsyncEvent.SelfSynicMethod }; messageHandler.GlobalMessageContext.ExpireMinutes = 3; //messageHandler.SaveRequestMessageLog(); await messageHandler.ExecuteAsync(cancellationToken); //messageHandler.SaveResponseMessageLog(); return new FixWeixinBugWeixinResult(messageHandler); } [HttpPost] public ActionResult CreateMenu() { var menuFileInfo = _env.ContentRootFileProvider.GetFileInfo("menu.json"); using (var stream = menuFileInfo.CreateReadStream()) { using (StreamReader streamReader = new StreamReader(stream)) { var menuContent = streamReader.ReadToEnd(); MenuFull_ButtonGroup buttonGroup = JsonSerializer.Deserialize<MenuFull_ButtonGroup>(menuContent); var tokenResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(_weixinSetting.WeixinAppId, _weixinSetting.WeixinAppSecret); if (tokenResult.errcode != ReturnCode.請求成功) { return Json(tokenResult); } var menuResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.CreateMenu(tokenResult.access_token, buttonGroup); if (menuResult.errcode != ReturnCode.請求成功) { return Json(menuResult); } return Json("設置成功"); } } } /// <summary> /// 獲取菜單介面 /// </summary> /// <returns></returns> [HttpGet] public ActionResult GetMenu() { var tokenResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(_weixinSetting.WeixinAppId, _weixinSetting.WeixinAppSecret); if (tokenResult.errcode != ReturnCode.請求成功) { return Json(tokenResult); } var menuResult = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetMenu(tokenResult.access_token); return Json(menuResult); } }
構造函數中,註入微信相關配置SenparcWeixinSetting,get方法,用來響應微信官方的URL校驗,註意該方法公佈出去的reset地址需要跟公眾號後臺配置的token校驗地址一致。關於微信的token校驗,相比前幾年的一個變化是,開發者需要在功能變數名稱對應根路徑下放置一個微信後臺提供下載的TXT文件,聽起來繞是吧,那我往簡單說,就是http://yourdomain/xxxx.txt需要能訪問到公眾號後臺下載的那個xxxx.txt。可以根據具體部署情況靈活處理此要求,比如可以在反向代理層,也可以在應用中去處理,比如我這兒就是直接放在系統應用中處理,具體來說,如果在core中引用了UseStaticfile中間件,則core預設把wwwroot作為功能變數名稱根目錄公佈出去,我們的前端文件就是這麼被公佈出去的,所以在開啟Staticfile的情況下,直接把XXXX.txt文件放置到wwwroot目錄中即可通過微信文件校驗。說句題外話,微信這種校驗方式,其實和Let's encrypt數字證書的校驗是一樣的,目的就是為了證明你確實是你聲明的那個功能變數名稱對應的伺服器。
Post方法,用來接收微信伺服器推送過來的微信終端的消息,其中就用到了上述自定義消息處理器。
CreateMenu用來提供創建微信菜單的api,我的做法是把微信菜單定義在menu.json中,然後代碼讀取並調用微信相關方法創建。之所以這樣是因為菜單功能可能經常變化,所以做成配置化。生產環境中,記得給CreateMenu方法做鑒權,否則別人隨便操你的菜單,那可不是好玩兒的。
GetMenu,獲取當前微信菜單,這個不必多說。
4)微信相關服務&中間件註冊
Startup.ConfigService中添加如下片段:
//微信相關服務 services.AddSenparcGlobalServices(Configuration) .AddSenparcWeixinServices(Configuration);
這是註冊Senparc微信相關服務
Startup.Config中添加如下片段註冊Senparc相關中間件:
IRegisterService register = RegisterService.Start(env, senparcSetting.Value) .UseSenparcGlobal(); register.RegisterTraceLog(() => ConfigTraceLog(monitorService)); register.UseSenparcWeixin(senparcWeixinSetting.Value, senparcSetting.Value) .RegisterMpAccount(senparcWeixinSetting.Value, "Fuck XXXXXX");