最近参与微信服务号小项目的开发,关于微信分享,我是只知其功能,并没深入了解其中的弯弯道道。虽然项目中不是我负责微信分享这一块(因为我也不太会),但是团队在这个功能上,那可是说多了都是泪,耗费了超级多的时间:一句话就是加班加点的挖坑,制造解决不了的bug,然后加班加点的找问题所在,去解决。主要是我们对于微信分享这块的了解并不深入,没想到的太多。
当时一直分享不成功,我们前端一直以为是我们的js代码写的有问题,谁知道最大的问题是配置问题:
一、不再同一个服务号。
因为微信端,为了识别用户,每个用户针对每个公众号会产生一个安全的OpenID,如果需要在多公众号、移动应用之间做用户共通,则需前往微信开放平台,将这些公众号和应用绑定到一个开放平台账号下,绑定后,一个用户虽然对多个公众号和应用有多个不同的OpenID,但他对所有这些同一开放平台账号下的公众号和应用,只有一个UnionID,可以在用户管理-获取用户基本信息(UnionID机制)文档了解详情。
二、分享的链接有&符,我们没有做处理
三、JS接口安全域名设置,没有设置白名单。
今天团队的大神,特别针对微信分享这个功能,对于其中会遇到的问题进行了总结和讲解,我这才对微信分享,有了更深入的认识和了解。记性不好,听完怕忘,得来个听后记录啊。
虽然微信提供了JSSDK,但是这不意味着你可以用自定义的按钮来直接打开微信的分享界面,这套JSSDK只是把微信分享接口的内容定义好了,实际还是需要用户点击右上角的菜单按钮进行主动的分享,用户点开分享界面之后,出现的内容就会是你定义的分享标题、图片和链接。
JSSDK使用步骤:
1.步骤一:绑定域名
2.步骤二:引入js
3.步骤三:通过config接口注入权限验证配置
4.步骤四:通过ready接口处理成功验证
5. 步骤五:通过error接口处理失败问题 // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
写到这里,其实已经跟前端没多大事了,剩下的就是关于公众号的配置。但是当时我们前端还一直以为是自己js的错误。。(js详见最后)。
配置大纲:
详细步骤:
1.设置JS接口安全域名。
这里填写的是一级域名,不带www和http。最多可以设置三个域名。设置完后点击确定。(多说一句,相比以前的分享没有任何域名限制,这里设置安全域名,目的是为了当发现此公众平台发现诱导分享行为时,可以根据此域名追溯到所有分享出去的链接,以及通过这些链接增加的粉丝。这样,微信就可以牢牢控制了你的微信平台,一旦发现违规,让分享链接失效,删除掉诱导行为增加的粉丝,是瞬间就可以完成的。因此,微信平台的开发者,一定要合理来使用分享功能,不要因小失大。等到你的微信平台被封,估计哭都来不及)
2.在开发者中心中获取你的AppID和AppSecret,接下来在获取令牌时,需要这两个信息。
3.获取令牌
unction wx_get_token() { $token = S('access_token'); if (!$token) { $res = file_get_contents('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' .'你的AppID'.'&secret=' .'你的AppSecret'); $res = json_decode($res, true); $token = $res['access_token']; // 注意:这里需要将获取到的token缓存起来(或写到数据库中) // 不能频繁的访问https://api.weixin.qq.com/cgi-bin/token,每日有次数限制 // 通过此接口返回的token的有效期目前为2小时。令牌失效后,JS-SDK也就不能用了。 // 因此,这里将token值缓存1小时,比2小时小。缓存失效后,再从接口获取新的token,这样 // 就可以避免token失效。 // S()是ThinkPhp的缓存函数,如果使用的是不ThinkPhp框架,可以使用你的缓存函数,或使用数据库来保存。 S('access_token', $token, 3600); } return $token; } 注意:返回的access_token长度至少要留够512字节。接口返回值: {"access_token":"ACCESS_TOKEN","expires_in":7200} {"access_token":"vdlThyTfyB0N5eMoi3n_aMFMKPuwkE0MgyGf_0h0fpzL8p_hsdUX8VGxz5oSXuq5dM69lxP9wBwN9Yzg-0kVHY33BykRC0YXZZZ-WdxEic4","expires_in":7200}
4.获取jsapi的ticket。jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。
function wx_get_jsapi_ticket(){ $ticket = ""; do{ $ticket = S('wx_ticket'); if (!empty($ticket)) { break; } $token = S('access_token'); if (empty($token)){ wx_get_token(); } $token = S('access_token'); if (empty($token)) { logErr("get access token error."); break; } $url2 = sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", $token); $res = file_get_contents($url2); $res = json_decode($res, true); $ticket = $res['ticket']; // 注意:这里需要将获取到的ticket缓存起来(或写到数据库中) // ticket和token一样,不能频繁的访问接口来获取,在每次获取后,我们把它保存起来。 S('wx_ticket', $ticket, 3600); }while(0); return $ticket; } 接口返回值: {"errcode":0,"errmsg":"ok","ticket":"sM4AOVdWfPE4DxkXGEs8VMKv7FMCPm-I98-klC6SO3Q3AwzxqljYWtzTCxIH9hDOXZCo9cgfHI6kwbe_YWtOQg","expires_in":7200}
5.签名,将jsapi_ticket、noncestr、timestamp、分享的url按字母顺序连接起来,进行sha1签名。 noncestr是你设置的任意字符串。 timestamp为时间戳。
$timestamp = time(); $wxnonceStr = "任意字符串"; $wxticket = wx_get_jsapi_ticket(); $wxOri = sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s", $wxticket, $wxnonceStr, $timestamp, '要分享的url(从http开始,如果有参数,包含参数)' ); $wxSha1 = sha1($wxOri);
js代码:
var shareData = { shareTitle:"", shareContent:"", shareUrl:location.href, imgUrl:"", success:function(){ alert('分享成功!'); } }; function wxConfig () { $.ajax({
// 此处的location.href必须做处理,一个大坑,因为如果url中有&这个特殊符号,微信端分享就不会成功。
url: "http://***?url=" + encodeURIComponent(location.href), method: 'get', success: function (data) { var wxdataObj = {}; if(typeof data == "string" && data.indexOf("{")!=-1){ wxdataObj = JSON.parse(data); }else{ wxdataObj = data; } wx.config({ debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: wxdataObj.AppId||wxdataObj.appId, // 必填,公众号的唯一标识 timestamp: wxdataObj.Timestamp||wxdataObj.timestamp, // 必填,生成签名的时间戳 nonceStr: wxdataObj.NonceStr||wxdataObj.nonceStr, // 必填,生成签名的随机串 signature: wxdataObj.Signature||wxdataObj.signature, // 必填,签名,见附录1 jsApiList: ['chooseImage', 'uploadImage','onMenuShareTimeline', 'onMenuShareAppMessage',"hideAllNonBaseMenuItem","showAllNonBaseMenuItem"], // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.error(function (res) { //console.log('res', res); }); }, error: function (data) { //console.log('error', data); } }); wx.ready(function () { wx.onMenuShareTimeline({ title: shareData.shareTitle, // 分享标题 link: shareData.shareUrl, // 分享链接 imgUrl: shareData.shareIcon, // 分享图标 success: shareData.success// 用户确认分享后执行的回调函数 }); wx.onMenuShareAppMessage({ title: shareData.shareTitle, // 分享标题 desc: shareData.shareContent, // 分享描述 link: shareData.shareUrl, // 分享链接 imgUrl: shareData.shareIcon, // 分享图标 success: shareData.success// 用户确认分享后执行的回调函数 });
// 此处是微信禁用 if(location.href.indexOf("tinysite.html")==(-1)){ wx.hideAllNonBaseMenuItem(); } }); }; function isWeiXin(){ var ua = navigator.userAgent.toLowerCase(); if(ua.match(/MicroMessenger/i)=="micromessenger") { return true; } else { return false; } }//判断是否在微信打开 //若果是微信就拉取微信权限 if(isWeiXin()){ wxConfig (); } //从url里取值var urlData = getUrlData(["userId","fromSource"]); //使用 urlData.userId,urlData.fromSource function getUrlData (arr){ var dataArr = {}; for(var i = 0; i < arr.length; i++){ var urlList = decodeURIComponent(location.href).split("&"); for(var j = 0; j < urlList.length; j++){ if(urlList[j].indexOf(arr[i]+"=") != (-1)){ var data = urlList[j].slice(urlList[j].indexOf(arr[i]+"=")+arr[i].length+1); dataArr[arr[i]] = data; } } } return dataArr; }
如果是多个页面都分享自定义的标题和描述:
var share = { title: document.title, discription: document.discription, imgurl: "", url: location.href, success: function () { alert('分享成功!'); } }; if (data) { for (v in data) { share[v] = data[v] || share[v]; } }