微信第三方平台概述
公眾平台第三方平台是為了讓公眾號或小程序運營者,在面向垂直行業需求時,可以一鍵授權給第三方平台(並且可以同時授權給多家第三方),通過第三方平台來完成業務,開放給所有通過開發者資質認證后的開發者使用。
這里啰嗦一下,什么是微信第三方平台,有什么作用?
官方介紹:微信第三方平台的開放,是為了讓公眾號或小程序運營者,在面向垂直行業需求時,可以一鍵登錄授權給第三方的公眾號或小程序運營平台,通過第三方開發者提供的公眾號或小程序第三方平台來完成相關業務。
從技術上簡單來說,客戶無需提供技術人員對接。無需了解微信后台開發者相關配置,比如配置Appid,AppSecret,URL,Token等等很多東西,客戶只需要通過掃一掃授權給第三方平台就能得到第三方平台微信授權的相關運營功能。前提是第三方平台已經完全對接微信開放平台的大部分功能。
這一篇主要是講解如何通過.NET CORE2.1技術開發后台對接微信開放平台的小程序授權。
廢話不多說,大家都知道磨刀不誤砍柴工,所以建議大家都去看一遍微信開放平台文檔,文檔說明已經很詳細了。至少自己心中要有大概的了解及思路。地址 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318292&token=&lang=
開發必備工具
IDE:VS2017
運行環境:netcoreapp2.1
數據庫:Mysql
框架主要運用技術及組件:
- .NET Core 2.1
- Entity Framework Core 2.1.0
- Pomelo.EntityFrameworkCore.MySql 2.1.0-rc1-final
- Senparc.Weixin.Open 2.10.5
微信開放平台准備
1、注冊微信開放平台賬戶,並通過企業認證。
2、進入管理中心,切換到【第三方平台】創建第一個第三方平台

第三方平台可開通微信公眾號授權、小程序授權,根據貴公司的開放進度及功能開放程度進行選擇。
具體步驟
在第三方平台方創建成功並最終開發測試完畢,提交全網發布申請時,微信服務器會通過自動化測試的方式,檢測服務的基礎邏輯是否可用,在確保基礎可用的情況下,才會允許公眾號或小程序第三方平台提交全網發布。
微信后台會自動將下述公眾號配置為第三方平台方的一個額外的測試公眾號,並通過該帳號,執行如下所述的測試步驟,第三方平台方需要根據各步驟描述的自動化測試規則實現相關邏輯,才能通過接入檢測,達到全網發布的前提條件。
請注意,必須預先按照測試各步驟要求,代碼實現相關邏輯后,去點擊“全網發布”按鈕,才有可能全網發布成功。
3、以小程序授權Demo來介紹相關參數

開發參數配置 相關參數說明 訪問 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318462&lang=zh_CN
白名單IP配置允許用戶在全網未發布狀態下用於在線環境開發測試。

小程序模板配置

以上就是第三方平台的基礎配置信息。
4.基於.NET CORE 2.1搭建一個簡單的第三方平台。
新建一個空的解決方案。主要用於第三方授權及小程序業務管理。


第三方平台與微信開放平台的業務對接我們直接使用 Senparc的Open組件

安裝組件完成之后,我們需要做的是要獲取微信開放平台主動推送的 component_verify_ticket
官方文檔說明:
在公眾號第三方平台創建審核通過后,微信服務器會向其“授權事件接收URL”每隔10分鍾定時推送component_verify_ticket。第三方平台方在收到ticket推送后也需進行解密(詳細請見【消息加解密接入指引】),接收到后必須直接返回字符串success。
我們建議一個控制 WxOpenController 用來接受微信開放平台POST推送的消息。
我們按照Senparc的第三方平台的Demo,先用最簡單的方式保存component_verify_ticket到~/App_Data/OpenTicket/{AppId}.txt文本",當然啦也可以存入數據庫或其他可以持久化的地方。

CustomThirdPartyMessageHandlers 用於接收微信開放平台推送的消息並解析。有了解過Senparc相關組件的盆友們,估計應該都知道這個類的用處,這里不多說了,詳細說明看文檔。
核心代碼,與Senparc官網的代碼暫時沒多大區別。我們主要是為后面處理小程序的授權及業務做准備工作。
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"SenparcWeixinSetting": {
//公眾號
"Token": "***",
"EncodingAESKey": "**********************",
"WeixinAppId": "*********************",
"WeixinAppSecret": "**************************",
//開放平台
"Component_Appid": "***************************",
"Component_Secret": "***********************************"
}
}
WxOpenController.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Senparc.Weixin;
using Senparc.Weixin.Entities;
using Senparc.Weixin.MP.MvcExtension;
using Senparc.Weixin.Open.Entities.Request;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ZY.WxOpen.WebHost.MessageHandlers.ThirdPartyMessageHandlers;
using ZY.WxOpen.WebHost.Utilities;
namespace ZY.WxOpen.WebHost.Controllers
{
public class WxOpenController : Controller
{
readonly Func<string> _getRandomFileName = () => DateTime.Now.ToString("yyyyMMdd-HHmmss") + Guid.NewGuid().ToString("n").Substring(0, 6);
//微信的全局配置,根據Senparc的官網Demo進行配置
private readonly SenparcWeixinSetting _senparcWeixinSetting;
//第三方平台的APPid
private string componentAppid;
//第三方平台的配置的token
private string token;
//第三方平台的配置的加密key
private string encodingAESKey;
private readonly IHostingEnvironment _env;
public WxOpenController(IHostingEnvironment env,
IOptions<SenparcWeixinSetting> senparcWeixinSetting)
{
_env = env;
_senparcWeixinSetting = senparcWeixinSetting.Value;
componentAppid = _senparcWeixinSetting.Component_Appid;
token = _senparcWeixinSetting.Token;
encodingAESKey = _senparcWeixinSetting.EncodingAESKey;
}
[ActionName("Index")]
public ActionResult Index()
{
return Content("測試");
}
[HttpPost]
[ActionName("Index")]
public ActionResult Post(PostModel postModel/*,[FromBody]string requestXml*/)
{
#region 打包 PostModel 信息
postModel.AppId = componentAppid;
postModel.Token = token;//根據自己后台的設置保持一致
postModel.EncodingAESKey = encodingAESKey;//根據自己后台的設置保持一致
#endregion
//配置日志輸出
var logPath = Server.GetMapPath(string.Format("~/App_Data/Open/{0}/", DateTime.Now.ToString("yyyy-MM-dd")));
if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath);
}
string body = new StreamReader(Request.Body).ReadToEnd();
byte[] requestData = Encoding.UTF8.GetBytes(body);
Stream inputStream = new MemoryStream(requestData);
try
{
var messageHandler = new CustomThirdPartyMessageHandler(inputStream, postModel);
#region 記錄 Request 日志
//測試時可開啟此記錄,幫助跟蹤數據,使用前請確保App_Data文件夾存在,且有讀寫權限。
var requestDocumentFileName = Path.Combine(logPath, string.Format("{0}_Request_{1}.txt", _getRandomFileName(), messageHandler.RequestMessage.AppId));
var ecryptRequestDocumentFileName = Path.Combine(logPath, string.Format("{0}_Request_Ecrypt_{1}.txt", _getRandomFileName(), messageHandler.RequestMessage.AppId));
using (FileStream fs = new FileStream(requestDocumentFileName, FileMode.CreateNew, FileAccess.ReadWrite))
{
messageHandler.RequestDocument.Save(fs);
}
using (FileStream fs = new FileStream(ecryptRequestDocumentFileName, FileMode.CreateNew, FileAccess.ReadWrite))
{
messageHandler.EcryptRequestDocument.Save(fs);
}
#endregion
//執行微信處理過程
messageHandler.Execute();
#region 記錄 Response 日志
//測試時可開啟,幫助跟蹤數據
//if (messageHandler.ResponseDocument == null)
//{
// throw new Exception(messageHandler.RequestDocument.ToString());
//}
var responseDocumentFileName = Path.Combine(logPath, string.Format("{0}_Response_{1}.txt", _getRandomFileName(), messageHandler.RequestMessage.AppId));
var ecryptResponseDocumentFileName = Path.Combine(logPath, string.Format("{0}_Response_Final_{1}.txt", _getRandomFileName(), messageHandler.RequestMessage.AppId));
if (messageHandler.ResponseMessageText != null)
{
using (FileStream fs = new FileStream(responseDocumentFileName, FileMode.CreateNew, FileAccess.ReadWrite))
{
byte[] bytes = Encoding.UTF8.GetBytes(messageHandler.ResponseMessageText);
try
{
//設定書寫的開始位置為文件的末尾
fs.Position = fs.Length;
//將待寫入內容追加到文件末尾
fs.Write(bytes, 0, bytes.Length);
}
catch (Exception ex)
{
Console.WriteLine("文件打開失敗{0}", ex.ToString());
}
}
}
#endregion`
return new FixWeixinBugWeixinResult(messageHandler.ResponseMessageText);//為了解決官方微信5.0軟件換行bug暫時添加的方法,平時用下面一個方法即可
}
catch (Exception ex)
{
#region 異常處理
WeixinTrace.Log("MessageHandler錯誤:{0}", ex.Message);
using (var fs = new FileStream(Server.GetMapPath("~/App_Data/Error_" + _getRandomFileName() + ".txt"), FileMode.CreateNew, FileAccess.ReadWrite))
{
using (TextWriter tw = new StreamWriter(fs))
{
tw.WriteLine("ExecptionMessage:" + ex.Message);
tw.WriteLine(ex.Source);
tw.WriteLine(ex.StackTrace);
//tw.WriteLine("InnerExecptionMessage:" + ex.InnerException.Message);
tw.WriteLine("ExecptionMessage:" + ex.ToString());
if (ex.InnerException != null)
{
tw.WriteLine("========= InnerException =========");
tw.WriteLine(ex.InnerException.Message);
tw.WriteLine(ex.InnerException.Source);
tw.WriteLine(ex.InnerException.StackTrace);
}
tw.Flush();
//tw.Close();
}
}
return Content("");
#endregion
}
}
}
}
CustomThirdPartyMessageHandler.cs
using Senparc.Weixin.Open; using System.IO; using Senparc.Weixin; using Senparc.Weixin.Open.Entities.Request; using ZY.WxOpen.WebHost.Utilities; namespace ZY.WxOpen.WebHost.MessageHandlers.ThirdPartyMessageHandlers { /// <summary> /// 主要用於消息的處理 /// </summary> public class CustomThirdPartyMessageHandler : ThirdPartyMessageHandler { public CustomThirdPartyMessageHandler(Stream inputStream, PostModel encryptPostModel) : base(inputStream, encryptPostModel) { } /// <summary> /// 接收微信開放平台每10分鍾推送的ComponentVerifyTicket,注意:ComponentVerifyTicket很重要 /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> public override string OnComponentVerifyTicketRequest(RequestMessageComponentVerifyTicket requestMessage) { var openTicketPath = Server.GetMapPath("~/App_Data/OpenTicket"); if (!Directory.Exists(openTicketPath)) { Directory.CreateDirectory(openTicketPath); } //記錄ComponentVerifyTicket(也可以存入數據庫或其他可以持久化的地方) using (FileStream fs = new FileStream(Path.Combine(openTicketPath, string.Format("{0}.txt", RequestMessage.AppId)), FileMode.OpenOrCreate, FileAccess.ReadWrite)) { using (TextWriter tw = new StreamWriter(fs)) { tw.Write(requestMessage.ComponentVerifyTicket); tw.Flush(); //tw.Close(); } } return base.OnComponentVerifyTicketRequest(requestMessage); } /// <summary> /// 授權取消 /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> public override string OnUnauthorizedRequest(RequestMessageUnauthorized requestMessage) { WeixinTrace.SendCustomLog("提示", $"授權取消" + requestMessage.AuthorizerAppid); //如果需要同步用戶是否取消授權,可在此接收信息並進行取消授權業務處理 //取消授權 return base.OnUnauthorizedRequest(requestMessage); } } }
通過以上兩個核心的類,拿到微信開放平台的大門鑰匙ComponentVerifyTicket,准備工作完成了,后續就是我們需要做的業務對接。
下一篇會講解授權及業務對接。
小程序或者公眾號授權給第三方平台的技術實現流程比較簡單,以公眾號為例,如下圖所示:

如感興趣請多關注或者點擊鏈接加入群聊【微信小程序】:https://jq.qq.com/?_wv=1027&k=5PnrL3m 或 搜QQ群號:397185987
