然微信官方开放了js-sdk,但是在某些老版本的微信上存在兼容性问题,比如在安卓微信6.0.1版本上分享接口就无法使用,官方给出的解决方案就是升级微信。这不坑爹呢!我又不能强制我的用户去升级微信版本,总不能就抛弃他们吧?所以我们需要:
破解版js接口
点击查看项目 该接口为一大牛 @Alienfe 所做,当时在微信屏蔽私有接口的情况下,这个js库帮了大忙,破解了微信的限制,使得我们能够继续使用私有接口。但是在最新微信6.1版本,该库已经失效。
官方sdk
既然官方提供了接口,我们以后肯定是切换到上面的,代码重构少不了的,该接口的开发文档在这里: 官方js-sdk文档 说实话,该文档虽然功能强大,但是配置麻烦,需要请求授权后拿到授权码放到前端js里,简直蛋疼无比,而且不知道未认证的公众号能否使用。
这里就不详细介绍两个接口的使用方法了,这里主要说明一下如何合理使用两个接口,前面说过了,破解版js接口在6.1以上无效,而官方接口在低版本上存在兼容性问题,所以这里我们需要同时使用两个接口,以保证所有微信用户都能够正常分享。
那么,我的策略是:
1.微信版本<6.0.2 使用破解版接口 2.微信版本>=6.0.2 使用官方sdk
就以我的PHP项目为例,在后端,我通过 userAgent 提取微信版本号:
//判断微信版本是否高于6.0.2,低版本用旧接口,高版本用jssdk
preg_match('/MicroMessenger\/(.*?)[^\d\.]/',$_SERVER['HTTP_USER_AGENT'],$m);
if (version_compare($m[1], '6.0.2') == 1) {
$this->assign('wxHighVersion',true);
} else {
$this->assign('wxHighVersion',false);
}
wxHighVersion 是绑定到视图模板的变量名,以便我们在模板决定用哪个接口。模板部分:
<script>
//自定义分享参数
var wxData = {
"imgUrl": "",//图片
"link": "",//分享链接
"title": "",//定义分享标题
"desc":""
};
</script>
<?php if(!$wxHighVersion): ?>
<script src="/js/WeixinApi.js"></script>
<script>
//使用破解版接口
</script>
<?php else: ?>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script>
//使用官方接口
</script>
<?php endif; ?>
这样,我就实现了全版本兼容,只是重构代码需要细心+耐心。
我们要扩展thinkphp的微信类库(注这个是自己扩展改进来用于TP上用)。其实这个主要是继承高级接口类,分别写上缓存方法与以前调用其他接口时一样。代码中,我把官方的sample的PHP代码的方法复制过来用。
该例子中我用库表,实际中推荐你用memcached
缓存表一览:
data
<?php
namespace Wx;
class WechatJSAPI extends WechatJSON{
//缓存access token
public function cache($key, $value = null, $timeout = 7000) {
$cache = M('access_token');
$token = $cache->where(array('uid'=> 0))->find();
if (empty($value)) {
if ($token && $token['expires_time'] > time()) {
return $token['access_token'];
}
return false;
}
$data = array(
'uid' => 0,
'access_token' => $value,
'expires_time' => time() + $timeout,
);
if($token) {
$cache->where('uid=0')->save($data);
} else {
$cache->data($data)->add();
}
return false;
}
//获取签名包数据
public function getSignPackage() {
$jsapiTicket = $this->getJsApiTicket();
if( ! $jsapiTicket) return false;
$url = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$timestamp = time();
$nonceStr = $this->createNonceStr();
// 这里参数的顺序要按照 key 值 ASCII 码升序排序
$string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url";
$signature = sha1($string);
$signPackage = array(
"appId" => $this->_APPID,
"nonceStr" => $nonceStr,
"timestamp" => $timestamp,
"url" => $url,
"signature" => $signature,
"rawString" => $string
);
return $signPackage;
}
//生成随机字符串
private function createNonceStr($length = 16) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
//获取ticket
private function getJsApiTicket() {
$t = M('access_token');
$cache = $t->where(array('uid'=> 0))->find();
if ($cache && $cache['ticket_expires_time'] > time() && $cache['ticket']) {
return $cache['ticket'];
}
$res = $this->call('/ticket/getticket', array('type' => self::API_TYPE_JS), self::GET, self::API_TYPE_CGI);
if($res) {
$data = array(
'ticket' => $res['ticket'],
'ticket_expires_time' => time() + 7200,
);
$t->where('uid=0')->save($data);
return $res['ticket'];
}
return false;
}
//AJAX时刷新ticket
public function refreshTicket() {
$res = $this->call('/ticket/getticket', array('type' => self::API_TYPE_JS), self::GET, self::API_TYPE_CGI);
if($res) {
$t = M('access_token');
$cache = $t->where(array('uid'=> 0))->find();
if($res['ticket'] !== $cache['ticket']) {
$data = array(
'ticket' => $res['ticket'],
'ticket_expires_time' => time() + 7200,
);
$t->where('uid=0')->save($data);
return true;
}
}
return false;
}
}
然后在TP里的view新建一个layout(我以最基本要的HTML为例)
Layout/jsapi.phtml:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
{__CONTENT__}
</body>
<script type="text/javascript">
window.jQuery || document.write("<script src='__PUBLIC__/js/jquery-1.10.2.min.js'>"+"<"+"/script>");
</script>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script>
wx.config({
debug: false,
appId: '{$signPackage["appId"]}',
timestamp: {$signPackage["timestamp"]},
nonceStr: '{$signPackage["nonceStr"]}',
signature: '{$signPackage["signature"]}',
jsApiList: [
'checkJsApi',
'onMenuShareTimeline',
'onMenuShareAppMessage'
]
});
wx.ready(function () {
wx.checkJsApi({
jsApiList: [
'getNetworkType',
'previewImage'
],
success: function (res) {
//alert(JSON.stringify(res));
}
});
var shareObj = {
title: '{$shapeObje.title}',
desc: '{$shapeObje.desc}',
link: '{$shapeObje.link}',
imgUrl: '{$shapeObje.imgUrl}',
trigger: function (res) {
alert('用户点击发送给朋友');
},
success: function (res) {
alert('已分享');
},
cancel: function (res) {
alert('已取消');
},
fail: function (res) {
alert(JSON.stringify(res));
}
};
//分享朋友
wx.onMenuShareAppMessage(shareObj);
//分享朋友圈
wx.onMenuShareTimeline(shareObj);
});
jQuery.getJSON('index/ticket', function(data) {
alert(data);
if(data) {
alert('ticket update');
}
});
wx.error(function(res){
var str = res.errMsg;
var reg = /invalid signature$/;
var r = str.match(reg);
//当提示签名有误时就AJAX方式更新ticket后再刷新当前页
if(r !== null) {
jQuery(function(){
$.getJSON('http://www.demo.com/home/index/ticket', function(data) {
if(data) {
alert('ticket update');
location = location;
window.navigate(location);
}
});
});
}
});
</script>
</html>
TP中的控制器,初化设置相关数据替换模板和远程更新ticket方法
<?php
namespace Home\Controller;
use Think\Controller;
use Wx\WechatJSAPI;
class IndexController extends Controller {
protected $api;
public function _initialize() {
$package = $this->getJsPackage();
$this->assign('signPackage',$package);
$this->assign('shapeObje',array(
'title' => '测试标题',
'desc' => '测试描述',
'link' => "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]",
'imgUrl' => "yun_qi_img/No-Event.php",
));
}
public function indexAction(){
$this->display();
}
protected function getJsPackage() {
$config = C('WECHAT_CONF');
$this->api = WechatJSAPI::getInstance(array(
WechatJSAPI::APP_ID => $config[WechatJSAPI::APP_ID],
WechatJSAPI::APP_SECRET => $config[WechatJSAPI::APP_SECRET]
));
return $this->api->getSignPackage();
}
public function ticketAction() {
if(IS_AJAX) {
$res = $this->api->refreshTicket();
$this->ajaxReturn($res);
}
}
}
后记,测试出ticket的缓存时间,与access_token无关!也就是只要ticket还没过期,你更新了access_token再调/ticket/getticket接口,ticket值还是一样的。重点是怎么缓存ticket和更新ticket较为合理,我上面的方法就利用JS-SDK的wx.error(function(res){})来正则匹出是invalid signature时就AJAX一下更新ticket的URL,然后再刷新。
你如果有什么好建议欢迎一起探讨!