寫過了兩個微信的頁面,遇到了挺多不會的問題,當時也是自己邊查資料,邊實踐完成了簡單的需求,剛好現在有空,把之前的東西整理一遍。
與普通的手機頁面不同的是,微信頁面提供給你了呼叫微信APP內建功能的介面,可以實現更復雜的功能。
jssdk的前端使用
前端頁面呼叫jssdk首先要通繫結“公眾號設定”的“功能設定”裡填寫“JS介面安全域名”
然後在頁面中引入http://res.wx.qq.com/open/js/...
呼叫 wx.config({...}) 來驗證許可權配置
然後可根據需要 呼叫微信所提供的介面
後端返回介面
在前端呼叫時wx.config({...})中需要的引數需要我們自己進行返回
wx.config({
debug: true, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
appId: '', // 必填,公眾號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名
jsApiList: [] // 必填,需要使用的JS介面列表
});
其中 timestamp
, nonceStr
, signature
,是需要後端計算返回的。
簽名獲取方法
簽名生成規則如下:參與簽名的欄位包括noncestr(隨機字串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。對所有待簽名引數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串string1。這裡需要注意的是所有引數名均為小寫字元。對string1作sha1加密,欄位名和欄位值都採用原始值,不進行URL 轉義。
而其中的 jsapi_ticket 是通過 access_token 來獲取的,且兩者都有過期時間(7200秒)其中 jsapi_ticket 更是限制了獲取次數。
所以為了儲存兩者,使用redis資料庫儲存在記憶體中是個很好的選擇(可快速讀取,並設定過期時間)。
token獲取方法:
/**
* 獲取token
* @return {promise} res 值為token
*/
function getToken () {
return redis.getVal('token') // 首先讀取 redis 是否存在token
.then(function (res) {
if (res === null) { // 若不存在,則返回savetoken() 獲取
// console.log('不存在token 呼叫saveToken')
return saveToken ()
} else { // 若存在 則直接返回
// console.log('存在token 直接返回')
return res
}
})
.catch(function (err) { // 捕獲 錯誤
console.log(err)
})
}
/**
* 從服務端獲取token 並儲存在redis中
* @return {promise} 值 為 token
*/
function saveToken () {
return new Promise((resolve, reject) => {
let reqUrl = config.gettoken_url // https://api.weixin.qq.com/cgi-bin/token?
let params = {
grant_type: 'client_credential',
appid: config.appid,
secret: config.appsecret
}
let options = {
method: 'get',
url: reqUrl + qs.stringify(params)
}
request(options, function (err, res, body) {
if (res) {
let bodys = JSON.parse(body)
let expires = bodys.expires_in
let token = bodys.access_token
redis.setKey('token', token, expires) // 將token 儲存到 redis
.catch(function (err) {
console.log(err)
})
resolve(token)
} else {
reject(err)
}
})
})
}
在配置檔案中配置好所需要的appid和appsecret,首先檢視redis中是否存在,如果存在就直接返回,沒有的話,就呼叫saveToken去獲取並儲存在redis中
jsapi_ticket 獲取方法
同理,jsapi_ticket 也採用同樣的方式去獲取
/**
* 獲取ticket
* @return {promise} res 值為ticket
*/
function getJsTicket() { // 獲取token
return redis.getVal('ticket') // 首先讀取 redis 是否存在ticket
.then(function (res) {
if (res === null) { // 若不存在,則返回saveJsTicket() 獲取
// console.log('不存在ticket 呼叫saveJsTicket')
return saveJsTicket ()
} else { // 若存在 則直接返回
// console.log('存在ticket 直接返回')
return res
}
})
.catch(function (err) { // 捕獲 錯誤
console.log(err)
})
}
/**
* 從服務端獲取ticket 並儲存在redis中
* @return {promise} 值 為 ticket
*/
function saveJsTicket () {
return new Promise((resolve, reject) => {
getToken().then(function (token) {
let reqUrl = config.ticket_start + token + config.ticket_end
let options = {
method: 'get',
url: reqUrl
}
request(options, function (err, res, body) {
if (res) {
let bodys = JSON.parse(body) // 解析微信伺服器返回的
let ticket = bodys.ticket // 獲取 ticket
let expires = bodys.expires_in // 獲取過期時間
redis.setKey('ticket', ticket, expires) // 將ticket 儲存到 redis
.catch(function (err) {
console.log(err)
})
resolve(ticket)
} else {
reject(err)
}
})
}).catch(function (err) {
console.log(err)
})
})
}
簽名演算法
在獲取jsapi_ticket後就可以生成JS-SDK許可權驗證的簽名了
/**
* 1. appId 必填,公眾號的唯一標識
* 2. timestamp 必填,生成簽名的時間戳
* 3. nonceStr 必填,生成簽名的隨機串
* 4. signature 必填,簽名
*/
const crypto = require('crypto')
const getJsTicket = require('./getJsTicket')
const config = require('../../config') // 微信設定
// sha1加密
function sha1(str) {
let shasum = crypto.createHash("sha1")
shasum.update(str)
str = shasum.digest("hex")
return str
}
/**
* 生成簽名的時間戳
* @return {字串}
*/
function createTimestamp () {
return parseInt(new Date().getTime() / 1000) + ''
}
/**
* 生成簽名的隨機串
* @return {字串}
*/
function createNonceStr () {
return Math.random().toString(36).substr(2, 15)
}
/**
* 對引數物件進行字典排序
* @param {物件} args 簽名所需引數物件
* @return {字串} 排序後生成字串
*/
function raw (args) {
var keys = Object.keys(args)
keys = keys.sort()
var newArgs = {}
keys.forEach(function (key) {
newArgs[key.toLowerCase()] = args[key]
})
var string = ''
for (var k in newArgs) {
string += '&' + k + '=' + newArgs[k]
}
string = string.substr(1)
return string
}
/**
* @synopsis 簽名演算法
*
* @param jsapi_ticket 用於簽名的 jsapi_ticket
* @param url 用於簽名的 url ,注意必須動態獲取,不能 hardcode
*
* @returns {物件} 返回微信jssdk所需引數物件
*/
function sign (jsapi_ticket, url) {
var ret = {
jsapi_ticket: jsapi_ticket,
nonceStr: createNonceStr(),
timestamp: createTimestamp(),
url: url
}
var string = raw(ret)
ret.signature = sha1(string)
ret.appId = config.appid
return ret
}
/**
* 返回微信jssdk 所需引數物件
* @param {字串} url 當前訪問URL
* @return {promise} 返回promise類 val為物件
*/
function jsSdk (url) {
return getJsTicket()
.then(function (ticket) {
return sign(ticket, url)
})
.catch(function (err) {
console.log(err)
})
}
function routerSdk (req, res, next) {
let clientUrl = req.body.url
if (clientUrl) {
jsSdk(clientUrl)
.then(function (obj) {
res.json(obj)
})
} else {
res.end('no url')
}
}
module.exports = routerSdk
以上基本就完成了後端返回簽名的過程(省略了redis部分)。具體細節可參考我當時的練手專案中的程式碼。
至此,前端就可以使用jssdk來完成功能的呼叫了。
ps:某次使用錄音介面做了一個功能,但是發現,微信伺服器只會儲存3天資料,需要自己下載到自己的伺服器才行,不知道諸位有沒做過類似的需求,給我提供下指導啥的,感激不盡~
後記
後來又寫過一個獲取使用者資訊的頁面,感覺也是挺常用的就寫個demo出來看看吧(沒有做access_token的儲存,好像是沒有獲取次數限制)。
router.get('/', function(req, res, next){
console.log("oauth - login")
// 第一步:使用者同意授權,獲取code
let router = 'get_wx_access_token'
// 這是編碼後的地址
let return_uri = encodeURIComponent(base_url + router)
console.log('回撥地址:' + return_uri)
let scope = 'snsapi_userinfo'
res.redirect('https://open.weixin.qq.com/connect/oauth2/authorize?appid='+appid+'&redirect_uri='+return_uri+'&response_type=code&scope='+scope+'&state=STATE#wechat_redirect')
})
// 第二步:通過code換取網頁授權access_token
router.get('/get_wx_access_token', function(req,res, next){
console.log("get_wx_access_token")
console.log("code_return: "+req.query.code)
let code = req.query.code
request.get(
{
url:'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' + appid + '&secret=' + appsecret+'&code=' + code + '&grant_type=authorization_code',
},
function(error, response, body){
if(response.statusCode === 200){
// 第三步:拉取使用者資訊(需scope為 snsapi_userinfo)
// console.log(JSON.parse(body))
let data = JSON.parse(body)
let access_token = data.access_token
let openid = data.openid
request.get(
{
url:'https://api.weixin.qq.com/sns/userinfo?access_token='+access_token+'&openid='+openid+'&lang=zh_CN',
},
function(error, response, body){
if(response.statusCode == 200){
// 第四步:根據獲取的使用者資訊進行對應操作
let userinfo = JSON.parse(body)
console.log(JSON.parse(body))
console.log('獲取微信資訊成功!')
小測試,實際應用中,可以由此建立一個帳戶
res.send("\
<h1>"+userinfo.nickname+" 的個人資訊</h1>\
<p><img src='"+userinfo.headimgurl+"' /></p>\
<p>"+userinfo.city+","+userinfo.province+","+userinfo.country+"</p>\
")
}else{
console.log(response.statusCode)
}
}
)
}else{
console.log(response.statusCode)
}
}
)
})