微信JSSDK使用指南

    这篇文章主要来讲说微信JSSDK的一些配置和微信分享的使用,包括从前端sdk文件引入到server端和微信server的交互。javascript

另外Tangide已经把一些工做作好了。我会简要说一说怎么把它用起来。在开发的过程当中主要參考了微信官方开发文档,由于微信提供的功能略多,我会按期更新文档,假设你已经熟悉了微信JSSDK的开发流程可以直接跳过前面的步骤,直接跳转到Tangide与微信JSSDK的使用结合 :)。php


1. 首先得有个公众号

微信把公众号细分为服务号订阅号css

接口权限各不一样样,假设你记不住你的公众号有没有相应功能的权限,可以到开发人员中心->接口权限表相应查看。html

2. 配置JS接口安全域名

微信限制了JS接口使用的域名。详细在:公众号设置->功能设置页面。好在可以同一时候配置3个,而且可以改动: js-api前端

这意味着配置可以复用了。反复劳动下降了:)。java

3. 引入JS文件

http://res.wx.qq.com/open/js/jweixin-1.0.0.js这个是微信官方提供的sdk。可以在h5页面放个<script>标签把它引入进来。node

4. 注入权限验证配置

微信为了验证调用SDK的合法性,需要咱们的server与微信server交互获取配置信息。express

wx.config({
    debug: true, // 开启调试模式,调用的所有api的返回值会在clientalert出来。若要查看传入的參数。可以在pc端打开,參数信息会经过log打出。仅在pc端时才会打印。
    appId: '', // 必填。公众号的惟一标识
    timestamp: , // 必填。生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '',// 必填。签名,见附录1
    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});

通常来讲仅仅有是否开启调式模式由client决定,其它的都是经过http请求server得到,如下咱们就来看看怎样从服务获取配置信息。json

5. 服务端获取配置信息

这是整个过程相对关键的一步,你必需要缓存好两个token: jsapi_ticket以及access_tokenaccess_token有效时间为2个小时。向微信server请求频率限制为:天天2000次。微信还有其它一些參数的频率限制,你可以看这里。api


如下来讲说获取配置信息的详细步骤:

get config uml

来看看server端是怎么处理的:

nodejsserver端:

var fs = require('fs'); var path = require('path'); var urlParser = require('url'); var TokenManager = require('./token_manager'); var Handler = function() { }; Handler.doit = function(req, res) { try { var query = urlParser.parse(req.url, true).query; if(!query || !query.url) { throw new Error('invalid params'); } var realUrl= atob(decodeURI(query.url)); } catch(e) { res.status(401).json({code: -1, message: e}); res.end(); return; } Handler.tokens.getJsApiTicket(realUrl, function(err, config) { if(!!err) { res.status(500).json({code: -1, message: err}); } else { res.status(200).json({code: 0, message: 'success', data: config}); } res.end(); }); }; Handler.init = function(app, express, config) { Handler.opts = { appid: config.appid, secret:config.secret }; Handler.tokens = TokenManager.create(opts); app.post('/weixin/get-config.php', Handler.doit); }; module.exports = Handler; ////////////////////////////////////////////////////////////////////////////// //token manager var TokenManager = function(opts) { this.opts = opts; if(!opts.appid || !opts.secret) { thow new Error('appid and secret should not be empty!!!'); } var tokenPath = path.join(__dirname, 'tokens.json'); this.loadToken = opts.loadToken || function(type, cb) { fs.readFile(tokenPath, 'utf8', function(err, data) { if(!!err) { return cb(err); } try { data = data || JSON.stringify({}); data = JSON.parse(data); } catch(e) { return cb(e); } cb(null, data[type] || {}); }); }; this.saveToken = opts.saveToken || function(token, cb) { fs.readFile(tokenPath, 'utf8', function(err, data) { if(!!err) { return cb(err); } data = data || {}; var type = Object.keys(token)[0]; data[type] = token[type]; fs.writeFile(tokenPath, JSON.stringify(data), cb); }); }; }; var isValid = function() { return !!this.token && (new Date().getTime()) < this.expireTime; }; var load = function(stream, cb) { var chunks = []; stream.on('data', function(chunk) { chunks.push(chunk); }); stream.on('error', function(err) { cb(err); }); stream.on('end', function() { cb(null, Buffer.concat(chunks)); }); }; var request = function(url, cb) { require('https').get(url, function(res) { load(res, cb); }).on('error', function(err) { cb(err); }); }; var wrapper = function(cb) { return function(err, rawData) { cb = cb || function() {}; if(!!err) { return cb(err); } try { var data = rawData.toString('utf-8'); data = JSON.parse(data); } catch(err) { return cb(err); } if(data && data.errcode) { err = new Error(data.errmsg); err.name = 'WeChatAPIError'; err.code = data.errcode; return cb(err, data); } cb(null, data); }; }; TokenManager.prototype.getAccessToken = function(cb) { var self = this; this.loadToken('accessToken', function(err, token) { if(err) { cb(err); } else if(isValid.call(token)) { cb(null, token.token); } else { var url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential' + this.opts.appid + this.opts.secret; request(url, wrapper(function(err, data) { if(err) { return cb(err); } console.assert(data && data.token); var expireTime = (new Date().getTime()) + (data.expires_in - 10) * 1000; var token = { accessToken: { 'token': data.token, 'expireTime': expireTime } }; self.saveToken(token, function(err) { if(err) { return cb(err); } cb(null, data.token); }); }); } }); }; TokenManager.prototype.getJsApiTicket = function(cb) { var self = this; this.loadToken('jsApiTicket', function(err, token) { if(err) { cb(err); } else if(isValid.call(token)) { cb(null, token.token); } else { self.getAccessToken(function(err, token) { if(err) { return cb(err); } console.assert(token); var url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?

type=jsapi&access_token=' + token; request(url, wrapper(function(err, data) { if(err) { return cb(err); } console.assert(data && data.token); var expireTime = (new Date().getTime()) + (data.expires_in - 10) * 1000; var token = { jsApiTicket: { 'token': data.ticket, 'expireTime': expireTime } }; self.saveToken(token, function(err) { if(err) { return cb(err); } cb(null, data.token); }); }); } }); }; var createNonceStr = function(length) { return Math.random().toString(36).substr(2, length || 15); }; var createTimestamp = function() { return parseInt(new Date().getTime()/1000, 10) + ''; }; var crypto = require('crypto'); var genSignature = function() { var raw = function() { var keys = Object.keys(arguments); var newArgs = {}; keys.sort().forEach(function(key) { newArgs[key.toLowerCase()] = arguments[key]; }); var str = ''; for(var k in newArgs) { str += '&' + k + '=' + newArgs[k]; } return str.substr(1); }; var str = raw.apply(null, arguments); return crypto.createHash('sha1').update(str).digest('hex'); }; TokenManager.prototype.getJSSDKConfig = function(url, cb) { var self = this; this.getJsApiTicket(function(err, ticket) { if(err) { return cb(err); } console.assert(ticket); var nonceStr = createNonceStr(); var timestamp= createTimestamp(); var signature= genSignature(nonceStr, timestamp, ticket, url); var ret = { appId: self.opts.appid, nonceStr: nonceStr, timestamp: timestamp, signature: signature, url: url, jsApiList: [ 'checkJsApi', 'chooseImage', 'previewImage', 'uploadImage', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo' ] }; cb(null, ret); }); }; module.exports.create = function(opts) { if(!this.inst) { this.inst = new TokenManager(opts); } return inst; };

    代码比較长,将就看吧!假设看过微信开发文档中的关于受权的大体流程,以上代码就没有什么问题了。需要注意的是,默认token保存在本地文件,假设有其它cluster需求可以自行注入函数来处理。

phpserver端

<?php class JSSDK { private $appId; private $appSecret; public function __construct($appId, $appSecret) { $this->appId = $appId; $this->appSecret = $appSecret; } public function getSignPackage($url) { $jsapiTicket = $this->getJsApiTicket(); if(!$url) { $url = "http://$_SERVER[HTTP_HOST]" . '/weixin/demo/index.html'; } $timestamp = time(); $nonceStr = $this->createNonceStr(); // 这里參数的顺序要依照 key 值 ASCII 码升序排序 $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$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; } private function getFileName($name) { //$fileName = sys_get_temp_dir() . "/weixin-" . $name; $fileName = dirname(__FILE__) . "/weixin-". $name; return $fileName; } private function saveData($name, $content) { $fileName = $this->getFileName($name); $fp = fopen($fileName, "wb+"); if($fp) { fwrite($fp, $content); fclose($fp); } return; } private function loadData($name, $defaultContent) { $fileName = $this->getFileName($name); $content = file_get_contents($fileName); if($content) { return $content; } else { return $defaultContent; } return; } private function getJsApiTicket() { $data = json_decode($this->loadData("jsapi_ticket.json", '{"jsapi_ticket":"", "expire_time":0}')); if ($data->expire_time < time()) { $accessToken = $this->getAccessToken(); $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken"; $res = json_decode($this->httpGet($url)); $ticket = $res->ticket; if ($ticket) { $data->expire_time = time() + 3600; $data->jsapi_ticket = $ticket; $this->saveData("jsapi_ticket.json", json_encode($data)); } } else { $ticket = $data->jsapi_ticket; } return $ticket; } private function getAccessToken() { $data = json_decode($this->loadData("access_token.json", '{"access_token":"", "expire_time": 0}')); if ($data->expire_time < time()) { $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret"; $res = json_decode($this->httpGet($url)); if(!isset($res->access_token)) { print_r($res); exit; } $access_token = $res->access_token; if ($access_token) { $data->expire_time = time() + 3000; $data->access_token = $access_token; $this->saveData("access_token.json", json_encode($data)); } } else { $access_token = $data->access_token; } return $access_token; } private function httpGet($url) { $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_TIMEOUT, 500); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_URL, $url); $res = curl_exec($curl); curl_close($curl); return $res; } } session_start(); if(!isset($_REQUEST['url'])) { echo "invalid arguments"; exit; } $appID = "你的公众号appid"; $appSecret = "你的公众号secret"; $url = $_REQUEST['url']; $url = base64_decode($url); $jssdk = new JSSDK($appID, $appSecret); $signPackage = $jssdk->getSignPackage($url); $signPackage['jsApiList'] = array('checkJsApi', 'chooseImage', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'previewImage', 'uploadImage', 'downloadImage', 'getNetworkType'); echo json_encode($signPackage); ?

>

php版本号流程都是同样。仅仅是语言差别罢了。

6. 写个demo来验证一下

由于微信团队限制没有提供主动分享的接口,咱们仅仅能准备好要分享的数据。等到用户点击右上角的分享button的时候,才把咱们以前准备好的数据分享出去。

demo包括一个可以动态设置文本的input标签,你可以动态改动其内容,而后点击微信右上角的分享给朋友查看效果。

<!DOCTYPE HTML>
<html> <head> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> <meta http-equiv="pragma" content="no-cache"/> <meta http-equiv="Expires" content="0" /> <meta http-equiv="Content-Type" content="type=text/html; charset=utf-8"/> <title>wechat-jssdk demo</title> <style> body { text-align: center; } input { width: 200px; height: 80px; font-size:40px; } </style> <script type="text/javascript" src = "http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <script type="text/javascript"> function get(uri) { return http(uri, 'GET', null); } function http(uri, method, data, headers) { alert('promise: ' + typeof Promise); return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open(method, uri, true); if(headers) { for(var p in headers) { xhr.setRequestHeader(p, headers[p]); } } xhr.addEventListener('readystatechange', function(e) { if(xhr.readyState === 4) { if(String(xhr.status).match(/^2\d\d$/)) { try { var data = JSON.parse(xhr.responseText); resolve(data); } catch(e) { reject(e); } } else { reject(xhr); } } }); xhr.send(data); }); } function wechatShare(cfg) { var inter = setInterval(function() { try { wx.config(cfg); clearInterval(inter); } catch(e) { console.log('wait ready'); } }, 200); var titleItem = document.getElementById('shareTitle'); titleItem.onchange = function() { var shareTitle = titleItem.value; alert('shareTitle: ' + shareTitle); wx.onMenuShareAppMessage({ title: shareTitle, // 分享标题 desc: 'TangIDE test onMenuShareAppMessage', // 分享描写叙述 link: window.location.href, // 分享连接 imgUrl: 'http://img1.bdstatic.com/static/common/widget/search_box_home/logo/home_white_logo_0ddf152.png', type: 'link', // 分享类型,music、video或link,不填默以为link success: function () { // 用户确认分享后运行的回调函数 }, cancel: function () { // 用户取消分享后运行的回调函数 } }); }; wx.ready(function() { /* wx.onMenuShareAppMessage({ title: '1234567', // 分享标题 desc: 'TangIDE test onMenuShareAppMessage', // 分享描写叙述 link: window.location.href, // 分享连接 imgUrl: 'http://img1.bdstatic.com/static/common/widget/search_box_home/logo/home_white_logo_0ddf152.png', type: 'link', // 分享类型,music、video或link,不填默以为link success: function () { // 用户确认分享后运行的回调函数 }, cancel: function () { // 用户取消分享后运行的回调函数 } }); */ }); wx.error(function(res) { }); } function regConfig() { var uri = 'http://www.i5r.com.cn/weixin/php/json_config.php' + '?url=' + window.btoa(location.href); get(uri).then(function(data) { data.debug = true; wechatShare(data); }).catch(function(err) { alert('request error:' + err); }); } window.onload = function() { setTimeout(regConfig, 600); }; </script> </head> <body> <p><input placeholder="分享标题" type="text" id="shareTitle" required="required"/></p> </body> </html>

    config信息验证后会运行ready方法。所有接口调用都必须在config接口得到结果以后,config是一个client的异步操做。因此假设需要在页面载入时就调用相关接口。则须把相关接口放在ready函数中调用来确保正确运行。对于用户触发时才调用的接口。则可以直接调用,不需要放在ready函数中。因此说假设需要分享的东西不变,就直接放在ready事件处理函数去作号了(如上面demo的ready事件处理函数),假设要分享的东西一直在变,就像demo演示的那样动态的改动要分享的title

demo演示(微信环境)

7. TangIDE与微信SDK的结合

    上面的这些代码看似有点多,只是幸亏强大的tangIDE已经帮咱们完毕很是多工做了:
- [1] 提供微信配置控件,本身主动完毕微信JSSDK环境搭建

        打开TangIDE在左側控件栏。SNS控件->微信设置直接拖入你需要调用jssdk接口的场景就能够。
- [2] 各类SNS组件支持
        打开TangIDE在左側控件栏。SNS控件选择想要的组件直接拖入你需要调用的场景就能够。


end

相关文章
相关标签/搜索