微信开放平台公众号第三方平台全攻略

微信开放平台现在已经开放了公众号第三方平台的功能,这个由官方推出的正式第三方平台功能解决了广大开发者的最后一线顾虑,可以让开发者的第三方平台托管更多的公众账号,对这些公众账号进行更好的服务。

但是目前看来,官方对第三方平台申请后调试的说明文档写的非常不详细,我的第三方平台提交审核通过后,第一时间完全不知道下一步该如何继续下去,经过在网上不断的查资料摸索后,终于搞定了这个调试过程。

第一步:创建公众号第三方平台

公众号第三方平台管理界面

填写第三方平台的参数,需要填写的参数很多,可以参考官方资料中心里的“公众号第三方平台的申请资料说明”,根据指导来填写。
创建新第三方平台时所需填写信息

参数中重要的一个是“授权事件接收URL”,因为这个参数是后文进行调试的关键入口。官方指导页推荐的是以URL目录截尾的地址,但我推荐使用XXXX/index.php的方式明确指出接收微信服务器推送的授权事件接收脚本,因为不明确指明的话,我就碰倒了微信服务器推送事件失败的情况,引以为鉴。

除此之外,白名单IP地址列表也很重要,涉及到后续调试时获取ticket、pre_auth_code等时的可以发起请求的IP地址,这些参数在本机调试是不行的,因为不在白名单IP列表中,因此调试过程必须在白名单IP列表的服务器内进行。

提交完资料后,等待审核通过即可获得属于你的第三方平台的AppID和AppSecret,然后就可以进入调试步骤了。

公众号第三方平台调试流程

第二步:获取ticket值

完整的说,ticket值叫做component_verify_ticket,是构成微信开放平台API请求中的参数之一,是我们需要获取的第一个参数,没有这个参数,就无法进行后续步骤。

那么如何获取这个component_verify_ticket呢?微信官方给出的文档完全没有说清楚这一步,因此这么至关重要的一步着实浪费了不少精力和时间,简单的来说,获取component_verify_ticket的流程可以以下图表示:

获取component_verify_ticket流程

值得注意的是,为了保证安全性,所有第三方平台和公众号之间的消息往来都需要进行加密,因此收到任何事件推送时,都需要对接收到的消息进行解密,才能获取到真正的信息。

在消息加密和解密方面,官方文档提供了较好的说明,也提供了各类SDK的样例代码,但是这些样例代码只是针对第三方平台与公众号之间的消息往来,对于ticket事件的消息,有一个会导致解密出错的地方。

ticket事件和普通消息事件有几个相同的地方,推送的消息中都包含以GET方式发送的三个参数:时间戳(timestamp)、随机值(nonce)、签名(msg_signature)以及消息本体内容。以php代码为例。在获得推送的时候,分别以$_GET方法和file_get_contents(‘php://input’)方法获取即可,代码如下:

$timestamp = empty($_GET['timestamp']) ? "" : trim($_GET['timestamp']);
$nonce = empty($_GET['nonce']) ? "" : trim($_GET['nonce']);
$msg_sign = empty($_GET['msg_signature']) ? "" : trim($_GET['msg_signature']);
$encryptMsg = file_get_contents('php://input');

加密消息$encryptMsg是xml结构的文本,包含了AppId和Encrypt两个节点,其中AppId节点和官方示例中的不一样。接收到的ticket事件xml文本:

<xml>
<AppId><![CDATA[wx1234567890]]></AppId>
<Encrypt><![CDATA[YQCryOiCVwjqHi/xAVoTkbERF...XdSzP7JCjerNS5yZy6A==]]></Encrypt>
</xml>

官方示例中只判断接收到的事件消息是否含有ToUserName节点,如果没有ToUserName节点,则返回错误。因此,在使用官方示例进行ticket事件解密时,要将AppId节点更改为ToUserName节点,做法很简单,我们将Encrypt节点中的加密信息提取出来,放入一个新构造的包含ToUserName节点的xml文本中即可。

$format = "<xml><ToUserName><![CDATA[wxd69058ab7f046aac]]></ToUserName><Encrypt><![CDATA[%s]]></Encrypt></xml>";
$from_xml = sprintf($format, $encrypt);

对新构成的xml文本进行解密,对照着官方样例中的例子即可,参照代码如下所示,记住一定要将得到的$this->ticket变量存入数据库,否则一切就努力就白费了。

//开始消息解密,解密内容存入msg变量
$msg = '';
$errCode = $pc->decryptMsg( $msg_sign, $timestamp, $nonce, $from_xml, $msg );
if ($errCode == 0) {
	// 从解密内容中获取ComponentVerifyTicket
	$xml = new DOMDocument();
	$xml->loadXML($msg);
	$array_e = $xml->getElementsByTagName('ComponentVerifyTicket');
	$this->ticket = $array_e->item(0)->nodeValue;
	$dateline = time();
	echo 'success';
} else {
	echo '解密后失败:'.$errCode;
	print($errCode . "\n");
}

第三步:获取token

这里,token完整称为component_access_token,它是用来获取预授权码的关键参数之一。

有了ticket后,获取component_access_token比较直观,api的地址为:https://api.weixin.qq.com/cgi-bin/component/api_component_token,参数需要用POST方式发送,这里有最需要注意的一点:POST数据必须以json格式发送

$url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
$data = array(
	'component_appid' => $appId,
	'component_appsecret' => $secret,
	'component_verify_ticket' => $ticket
);
$data = json_encode( $data );
$ch = curl_init(); //用curl发送数据给api
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, FALSE );
$response = curl_exec( $ch );
curl_close( $ch );
$result = json_decode( $response, true );

返回结果包含token值以及过期时间,过期时间一般为7200秒,到期前一定要更新ticket:

{
"component_access_token":"hVaQgLxL1gMfNEx7WGns4GRCdVvTBSjTw9Y6H6YV-eUNFJNAheRCZle8ndEzLN59LP-6npcua2s0Xe5ZfMr9Bp2N6S4LVLJ3JXD-AtsOZC0",
"expires_in":7200
}

第四步:获取预授权码

有了第二、第三步的基础,获取预授权码就只差一步之遥了。获取预授权码的方式在官网有介绍,api地址为:https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token,以此url为基础上,使用POST方式将json格式数据发送到api来获取预授权码pre_auth_code。

$url = 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=' . $token;
$data = array(
	'component_appid' => 'wx1234567890'
	);
$postdata = json_encode( $data );
$ch = curl_init();
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, FALSE );
$response = curl_exec( $ch );
curl_close( $ch );
$result = json_decode( $response, true );

和获取token一样,预授权码也有有效期,一般为1800秒,记得及时更新。返回结果如下:

{
"pre_auth_code":"preauthcode@@@EUw90Bnh5WPIKe2gjvULNQElGnyPoc32q5ogx2l34g15NxXWS3EvYPFlB1gZWTfM",
"expires_in":1800
}

第五步:公众号授权第三方平台

到这里,整个公众号授权的前提工作都已经完成了,我们已经获得了所有必需的参数,下面我们通过编写一个非常简单的页面来实现授权的流程:

echo '<a href="https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=' . $AppId . '&pre_auth_code=' . $pre_auth_code . '&redirect_uri=" . $redirect_uri . '>点击授权</a>';

用你自己的参数填入上述变量中,记得上传至白名单IP列表中的服务器,给需要授权给第三方平台的公众号打开页面,显示如下界面:
获取微信公众号授权

登录需要授权的公众号,即可完成授权流程。至此,我们大功告成。