既然是.net下微信開發,天然少不了Senparc,能夠說這個框架的存在, 至少節省了微信相關工做量的80%。事實上,項目開始前,還糾結了下是Java仍是core,之因此最終選擇core,除了情懷外,更重要的即是這個微信開發框架的存在。本項目的整合方式,極大程度上參考了Senparc官方的示例,官方示例能夠說很全面、詳細了。前端
appsettings.json中添加以下配置節: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,減小調試信息及提升性能。api
增長自定義消息處理器,繼承至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>中能夠重載的方法不少,主要是響應微信終端動做的一系列方法,好比用戶發送文本、用戶點擊連接、用戶發送圖片、發送位置等,若是你須要處理對應事件,那就重載對應方法,我這裏偷懶了,搞了個全部類型消息默認回覆。緩存
增長控制器,以下:服務器
[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方法作鑑權,不然別人隨便操你的菜單,那可不是好玩兒的。app
GetMenu,獲取當前微信菜單,這個沒必要多說。框架
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");