微信開發–帶引數的二維碼
1 接入
首先進入微信公眾號 -> 基本配置
下面是基本配置的頁面,在URL中填寫伺服器地址,這個地址就是接受微信推送事件的一個介面,我是使用thinkPHP框架開發的程式,在其中一個Module(Decoration)的Action目錄下新建一個類,比如叫: WechatAction.class.php ,在該Action中新建一個public方法,比如叫: URLRedirect() ,那麼在這個URL中填寫的就是 http://[IP]:[port]/index.php/Decoration/Wechat/UrlRedirect ,然後填寫Token,Token隨意填,EncodingAESKey要不要都行,然後點選確認,微信會往這個URL上傳送一個get請求,裡面包含很多引數,其中大部分都是讓我們自己核對這次訪問是不是微信伺服器請求的,我自己沒有驗證,他的要求是如果我們核對成功,即原樣返回get請求中的一個引數echostr,這裡的返回不是return,也不是ajaxReturn,而使用echo,如果用thinkPHP開發的話,直接使用
echo I('echostr'); 即可。然後介面即驗證成功了。
2 帶引數二維碼的作用
微信的帶引數二維碼有兩種,一種是臨時二維碼,一種是永久二維碼,但是永久二維碼的生成是有個數限制的,我這次要實現的功能是使用者未登入的情況下在網站上使用產品,比如獲得某商品的詳細報價,但是又不想註冊,然而又想儲存這個報價單,這個時候網頁可以生成一張二維碼,使用者只要用微信掃一掃這個二維碼,官方公眾號就會給這個使用者傳送一天圖文訊息,圖文訊息點開後就是使用者剛剛獲得的報價單,而且可以隨時點選檢視並且分享給朋友進行比價。所以臨時二維碼即可正常使用。
上面是我是怎麼使用的,下面介紹一下整個互動的流程:
當用戶掃描這個二維碼,如果使用者關注了公眾號,使用者會直接進入與公眾號的會話頁面,微信伺服器會給我們在上一步設定的伺服器URL中推送一條訊息,其中可以攜帶一個我們自定義的引數。如果使用者未關注公眾號,則使用者首先會跳轉到公眾號關注頁面,使用者點選關注後,會直接進入公眾號的會話頁面,微信伺服器這時也會給我們設定的URL推送一個事件訊息,攜帶我們自定義引數,我們可以根據這個引數和事件型別做控制下一步動作。
3 具體開發過程
3.1 獲取access_token
這個access_token是我們程式呼叫微信介面的憑證,目前的有效期是7200秒,所以我們需要定時更新access_token。
獲得方法:
方法 : GET
url :https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
其中的引數APPID和APPSECRET是我們公眾號的APPID和APPSECRET,在微信公眾號 -> 基本配置中可以查到,呼叫成功會返回如下JSON資料:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
其中access_token就是呼叫介面憑證,expire_in是token有效時間。
我本人是把access_token存在資料庫中,同時儲存過期時間,然後封裝公用函式 getWechatAccessToken() ,每次先檢查access_token是否過期,如果過期則重新獲取,否則直接使用資料庫儲存的access_token即可,我忘了在哪兒看加過,這個access_token每天的獲取次數應該是有限制的。下面是 getWechatAccessToken() 的具體實現:
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 //獲取access_token
function
getWechatAccessToken(){
$wechatInfo
= M(
'wechat_info'
)->select();
$wechatInfo
=
array_reduce
(
$wechatInfo
, create_function(
'$result, $v'
,
'$result[$v["conf_name"]] = $v;return $result;'
));
$expireTime
=
$wechatInfo
[
'PUBLIC_WECHAT_ACCESSTOKEN_EXPIRES'
][
'conf_value'
];
//前面不用管,是我資料庫相應設定
if
(time() <
$expireTime
){
//access_token未過期
return
$wechatInfo
[
'PUBLIC_WECHAT_ACCESSTOKEN'
][
'conf_value'
];
}
else
{
//access_token過期,重新獲取
$baseUrl
= C(
'WECHAT_PUBLIC_GET_ACCESS_TOKEN'
);
$url
=
str_replace
(
"##APPSECRET##"
,
$wechatInfo
[
'PUBLIC_WECHAT_APPSECRET'
][
'conf_value'
],
str_replace
(
"##APPID##"
,
$wechatInfo
[
'PUBLIC_WECHAT_APPID'
][
'conf_value'
],
$baseUrl
));
$result
=
file_get_contents
(
$url
);
$result
= json_decode(
$result
, true);
if
(
array_key_exists
(
'errorcode'
,
$result
)){
//失敗重試一次
return
false;
}
else
{
M(
'wechat_info'
)->where(
array
(
'conf_name'
=>
'PUBLIC_WECHAT_ACCESSTOKEN'
))->save(
array
(
'conf_value'
=>
$result
[
'access_token'
]));
M(
'wechat_info'
)->where(
array
(
'conf_name'
=>
'PUBLIC_WECHAT_ACCESSTOKEN_EXPIRES'
))->save(
array
(
'conf_value'
=> time()+
$result
[
'expires_in'
]-200));
return
$result
[
'access_token'
];
}
}
}
C('WECHAT_PUBLIC_GET_ACCESS_TOKEN') = https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
封裝好這個之後,我們每次就可以安心的使用了。
.2 建立臨時二維碼
3.2.1 獲取ticket3
請求方式: POST
介面:https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
POST資料: {"expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": 123}}}
介面URL中的TOKEN即我們在3.1中獲取的access_token,post資料中expire_seconds是二維碼的有效時間,最多為30天,action_name臨時二維碼的話固定就是QR_SCENE,scene_id即我們自定義引數,是個32位非0整數,我在應用中把它設為訂單的ID,微信伺服器推送事件的時候會把這個值返回給我們設定的介面中,然後我會根據這個值去拿相應的訂單資料展示在網頁上,這是後話。
下面是封裝的生成臨時二維碼的方法:
1 2 3 4 5 6 7 8 9 |
//建立臨時二維碼
function
getTemporaryQrcode( $orderId ){
$accessToken
= getWechatAccessToken();
$url
= str_replace ( "##TOKEN##" ,
$accessToken , C( 'WECHAT_PUBLIC_GET_TEMPORARY_TICKET' ));
$qrcode
= '{"expire_seconds": 1800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": ' . $orderId . '}}}' ;
$result
= api_notice_increment( $url ,
$qrcode );
$result
= json_decode( $result , true);
return
urldecode( $result [ 'url' ]);
}
|
其中的方法 api_notice_increment() 是我封裝的一個POST方法函式,我試過很多POST的方法,可能由於微信介面對POST方法和引數的限制比較嚴格,這個浪費了好久時間,最後在網上找到了一個可以使用的封裝好的POST方法,建議大家先自己試試,如果微信返回錯誤嗎,就用這個吧,起碼我測試微信這個介面的時候用postman測試返回的都是錯誤,而且一定要用JSON字串,一定要是非常嚴格的JSON字串。下面是這個方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function
api_notice_increment( $url ,
$data ){
$ch
= curl_init();
$header
= "Accept-Charset: utf-8" ;
curl_setopt( $ch , CURLOPT_URL,
$url );
curl_setopt( $ch , CURLOPT_CUSTOMREQUEST,
"POST" );
curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt( $ch , CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt( $ch , CURLOPT_HTTPHEADER,
$header );
curl_setopt( $ch , CURLOPT_USERAGENT,
'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)' );
curl_setopt( $ch , CURLOPT_FOLLOWLOCATION, 1);
curl_setopt( $ch , CURLOPT_AUTOREFERER, 1);
curl_setopt( $ch , CURLOPT_POSTFIELDS,
$data );
curl_setopt( $ch , CURLOPT_RETURNTRANSFER, true);
$tmpInfo
= curl_exec( $ch );
if
(curl_errno( $ch )) {
curl_close(
$ch );
return
$ch ;
} else {
curl_close(
$ch );
return
$tmpInfo ;
}
}
|
getTemporaryQrcode() 中有一個在配置檔案中的引數給大家看下,其實就是微信介面連結:
C('WECHAT_PUBLIC_GET_TEMPORARY_TICKET') = https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=##TOKEN##
這個介面的返回值是:
{"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}
其中ticket是讓我們用來進行下一步呼叫的憑證,expire_seconds是二維碼的有效期,url是我們生成的二維碼掃描後開啟的連結。所以如果我們自己實現了生成二維碼的方法,就不用再進行下一步呼叫,我本人即在這一步就停止了,直接返回url的值,然後利用這個url的值生成二維碼存在本地即可。PHP生成二維碼可以使用phpqrcode,挺好用的。下一步也大致提一下:
3.2.2 獲取二維碼地址
請求方式: GET
介面:https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
這個介面的返回值是一張圖片,可以直接展示或者下載,我們有具體使用過,所以也不知道應該怎麼展示。
3.3 使用者掃描二維碼之後發生的事情
3.3.1 掃描後發生了什麼
上面提到了,使用者掃描我們生成的臨時二維碼,如果使用者未關注公眾號,則首先會跳轉到公眾號的關注頁面,點選關注後,會進入公眾號的會話頁面,同時會給我們設定的介面推送一個事件。如果使用者已經關注了,使用者微信會直接跳轉到公眾號會話頁面,然後微信伺服器會給我們設定的介面推送一個事件。
使用者關注與否微信伺服器給我們推送的事件是差不多的,只是新關注使用者推送的事件中scene_id前面會加一個字首。下面是微信公眾平臺文件的說明:
使用者未關注時,進行關注後的事件推送
1 2 3 4 5 6 7 8 |
< xml >< ToUserName > <![CDATA[toUser]]> </ ToUserName > //開發者微訊號
< FromUserName > <![CDATA[FromUser]]> </ FromUserName > //傳送者賬號(openid)
< CreateTime >123456789</ CreateTime > //訊息建立時間(整型)
< MsgType > <![CDATA[event]]> </ MsgType > //訊息型別
event
< Event > <![CDATA[subscribe]]> </ Event > //事件型別(subscribe)
< EventKey > <![CDATA[qrscene_123123]]> </ EventKey > //事件KEY值,qrscene_為字首,後面為二維碼引數值
< Ticket > <![CDATA[TICKET]]> </ Ticket > //二維碼ticke值,可以用來換取二維碼圖片
</ xml >
|
使用者已關注時的事件推送
1 2 3 4 5 6 7 8 9 |
< xml >
< ToUserName > <![CDATA[toUser]]> </ ToUserName > //開發者微訊號
< FromUserName > <![CDATA[FromUser]]> </ FromUserName >
//傳送者賬號(openid)
< CreateTime >123456789</ CreateTime > //訊息建立時間
< MsgType > <![CDATA[event]]> </ MsgType >
//訊息型別event
< Event > <![CDATA[SCAN]]> </ Event >
//事件型別 event
< EventKey > <![CDATA[SCENE_VALUE]]> </ EventKey >
//事件key值,是一個32位無符號整數,即建立二維碼時的二維碼scene_id
< Ticket > <![CDATA[TICKET]]> </ Ticket >
//二維碼的ticke,可以用來換取二維碼圖片
</ xml >
|
3.3.2 我們要做些什麼
我們需要在自己填寫的URL介面中接收這個事件,然後拿到我們需要的東西做我們想幹的事兒。因為我要實現的功能比較簡單,只需要拿到scene_id即可,因為這是我要展示給使用者看的訂單資料。下面是我寫的接收和處理部分,比較簡單,主要看一下應該怎麼接收微信推送的事件:
1 2 3 4 5 6 7 8 9 10 11 12 |
public
function urlRedirect(){
$postStr
= $GLOBALS [ "HTTP_RAW_POST_DATA" ];
$postObj
= simplexml_load_string( $postStr ,
'SimpleXMLElement' , LIBXML_NOCDATA);
$fromUsername
= (string) $postObj ->FromUserName;
$EventKey
= trim((string) $postObj ->EventKey);
$keyArray
= explode ( "_" ,
$EventKey );
if
( count ( $keyArray ) == 1){
//已關注者掃描
$this ->sendMessage( $fromUsername ,
$EventKey );
} else {
//未關注者關注後推送事件
$this ->sendMessage( $fromUsername ,
$keyArray [1]);
}
}
|
我沒有使用其他引數,只是根據不同的推送事件拿到我想要的訂單ID,然後這時候其實相當於你在這裡用公眾號的客服在跟掃碼的這個使用者對話,上段程式碼中呼叫的sendMessage()是使用客戶賬號給掃碼使用者傳送一個圖文訊息,因為我在拿scen_id的同時也拿到了使用者的openid,可以利用這個給使用者傳送訊息。
下面是sendMessage()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//給使用者傳送圖文訊息,點選跳轉到報價頁面
public
function sendMessage( $openid , $orderId ){
$url
= str_replace ( '##TOKEN##' , getWechatAccessToken(), C( 'WECHAT_SEND_MESSAGE' ));
$redirectUrl
= str_replace ( "##ORDERID##" ,
$orderId ,
str_replace ( "##OPENID##" ,
$openid , C( 'WECHAT_REDIRECT_URL_PRE' )));
$orderInfo
= M( 'order' )->where( array ( 'orderid'
=> $orderId ))->field( array ( 'totalMoney' ,
'savedMoney' ,
'roomarea' ))->find();
$description
= str_replace ( "##ROOMAREA##" ,
intval ( $orderInfo [ 'roomarea' ] * 1.25), C( 'WECHAT_MESSAGE_BRIEF' ));
$description
= str_replace ( "##TOTALBUDGET##" ,
$orderInfo [ 'totalMoney' ],
$description );
$description
= str_replace ( "##MARKETBUDGET##" ,
$orderInfo [ 'totalMoney' ]+ $orderInfo [ 'savedMoney' ],
$description );
$description
= str_replace ( "##SAVEMONEY##" ,
$orderInfo [ 'savedMoney' ],
$description );
$dataStr
= '{"touser":"'
. $openid .
'","msgtype":"news","news":{"articles":[{"title":"'
. C( 'WECHAT_MESSAGE_TITLE' ) .
'","description":"'
. $description
. '","url":"' .
$redirectUrl . '","picurl":"'
. C( 'WECHAT_MESSAGE_PICURL' ) .
'""}]}}' ;
api_notice_increment( $url ,
$dataStr );
}
|
其中 C('WECHAT_SEND_MESSAGE') = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=##TOKEN##' 至於下面的一大段str_replace,就是在組給使用者傳送的文字而已,需要注意$dataStr的格式,這裡面要求JSON字串比較嚴格,必須所有的字串都用雙引號括起來。微信介面對POST引數的限制真心嚴格。
下面是微信公眾平臺開發者文件中要求傳送圖文訊息的POST data格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{
"touser" : "OPENID" ,
"msgtype" : "news" ,
"news" :{
"articles" : [
{
"title" : "Happy Day" ,
"description" : "Is Really A Happy Day" ,
"url" : "URL" ,
"picurl" : "PIC_URL"
},
{
"title" : "Happy Day" ,
"description" : "Is Really A Happy Day" ,
"url" : "URL" ,
"picurl" : "PIC_URL"
}
]
}
}
|
其中url是使用者點選這個訊息之後開啟的地址,這個時候我就組了一個自己網站的地址,是一個get請求地址,裡面攜帶引數是使用者的openid和訂單id,這樣使用者點選開圖文訊息就可以看到自己剛才下單的內容了,因為需要在網頁上展示使用者的微信頭像和暱稱,所以我把openid也放到引數裡,在頁面載入前先拿到使用者的個人資訊和訂單資料,再展示網頁。這樣流程:使用者未登入下單 -> 生成微信二維碼 -> 使用者掃碼關注公眾號 -> 檢視訂單詳細資訊 就完成了。而且因為這個圖文訊息開啟後的連結攜帶的引數是這個使用者的額openid和其下單的訂單ID,不管分享到哪兒,用什麼瀏覽器開啟都是可以訪問的,且展示的也是這個使用者的頭像和暱稱資訊,這也是我要實現的一個效果。