先進(jìn)章:開(kāi)始開(kāi)發(fā)(準(zhǔn)備階段)
1. 接入指南
接入微信公眾平臺(tái)開(kāi)發(fā),開(kāi)發(fā)者需要按照如下步驟完成:
- 填寫(xiě)服務(wù)器配置

說(shuō)明:URL是開(kāi)發(fā)者用來(lái)接收微信消息和事件的接口URL,該接口盡量寫(xiě)成兩個(gè)請(qǐng)求方式,1:get請(qǐng)求,用于驗(yàn)證如下地址有效性,2:post請(qǐng)求, 用于接收消息和事件,Token 可以隨便定義用于驗(yàn)證接口簽名有效性, EncodingAESKey是加密的密鑰,下面加密方式選兼容模式或者安全模式的時(shí)候開(kāi)發(fā)者可根據(jù)該密鑰對(duì)數(shù)據(jù)進(jìn)行加解密
-
驗(yàn)證如上URL服務(wù)器地址的有效性
根據(jù)官方文檔的說(shuō)明,微信驗(yàn)證接口會(huì)帶下面幾個(gè)參數(shù)
參數(shù) | 描述 |
---|---|
signature | 微信加密簽名,signature結(jié)合了開(kāi)發(fā)者填寫(xiě)的token參數(shù)和請(qǐng)求中的timestamp參數(shù)、nonce參數(shù) |
timestamp | 時(shí)間戳 |
nonce | 隨機(jī)數(shù) |
echostr | 隨機(jī)字符串 |
服務(wù)端接口接收到這些參數(shù)后進(jìn)行簽名驗(yàn)證,如下代碼:
exports.check = function (req, res, next) {
// 在這里驗(yàn)證簽名
var signature = req.query['signature'],
timestamp = req.query['timestamp'],
nonce = req.query['nonce'],
echostr = req.query['echostr'];
var sha1 = crypto.createHash('sha1'),
sha1Str = sha1.update([config.weixin.token, timestamp, nonce].sort().join('')).digest('hex');
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end((sha1Str === signature) ? echostr : '');
return res;
};
-
依據(jù)接口文檔實(shí)現(xiàn)業(yè)務(wù)邏輯
這里就是根據(jù)業(yè)務(wù)需求,進(jìn)行接口調(diào)用的編程了,下面我會(huì)一一介紹
2. 獲取access_token
access_token是公眾號(hào)的全局唯一接口調(diào)用憑據(jù),公眾號(hào)調(diào)用各接口時(shí)都需使用access_token。該接口一天只能請(qǐng)求2000次,開(kāi)發(fā)者需要進(jìn)行妥善保存。access_token的存儲(chǔ)至少要保留512個(gè)字符空間。access_token的有效期目前為2個(gè)小時(shí),需定時(shí)刷新,重復(fù)獲取將導(dǎo)致上次獲取的access_token失效。
如下代碼事例:
exports.get_token = function (fn) {
redis.get(weixin_token, function (err, token_str) {
if (token_str) {
return fn(err, JSON.parse(token_str));
} else {
request.get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + app_id + "&secret=" + app_secret, function (err, response, body) {
if (JSON.parse(body).errcode == 45009) {
return fn(err)
} else {
redis.set(weixin_token, body, function (err) {
redis.expire(weixin_token, 7000, function () {
return fn(err, JSON.parse(body));
});
});
}
});
}
});
};
上面的事例代碼中,首先我會(huì)從redis中獲取到access_token,因?yàn)槲易畛醌@取access_token的時(shí)候?qū)懭氲絩edis中了,官方給的有效時(shí)間是7200秒,我放在redis中的有效時(shí)間是7000秒,所以我這里的token不會(huì)過(guò)期,過(guò)期后會(huì)重新調(diào)用接口獲取并寫(xiě)入redis
第二章:自定義菜單
實(shí)例代碼中只寫(xiě)入創(chuàng)建接口的調(diào)用,查詢(xún)和刪除就不舉例了。
-
自定義菜單創(chuàng)建接口
如下代碼事例以及說(shuō)明:
get_token(function(err, obj){
var access_token = obj.access_token;
request.post({
url: "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + access_token,
json: {
"button":[
{
"type":"view",
"name":"工作臺(tái)",
"url":"http://worktile.com"
},
{
"name":"解決方案",
"sub_button": [
{
"type":"view",
"name":"研發(fā)",
"url":"https://pro.worktile.com/solution/dev"
}
{
"type":"view",
"name":"較佳實(shí)踐",
"url":"https://worktile.com/can"
}
]
},
{
"name":"更多",
"sub_button":[
{
"type":"view",
"name":"下載應(yīng)用",
"url":"http://a.app.qq.com/o/simple.jsp?pkgname=com.worktile"
},
{
"type":"click",
"name":"合作",
"key":"work_together"
}]
}]
}
}, function(err, res, body){
console.log(body)
})
});
這里get_token方法就是上面先進(jìn)章中#獲取access_token ,而且我這里是作為腳本執(zhí)行的,這樣方便以后隨便修改自定義菜單內(nèi)容
第三章:消息管理
-
接收消息
接收的消息分為普通消息和事件消息,統(tǒng)一有先進(jìn)章中 接入指南 填寫(xiě)的RUL接口來(lái)接收處理
exports.receive = function (req, res, next) {
// 在這接收消息
var xml = '';
req.setEncoding('utf8');
req.on('data', function (chunk) {
xml += chunk;
});
req.on('end', function () {
toJSON(xml, res);
});
};
說(shuō)明:receive方法就是接收用戶(hù)發(fā)給公眾號(hào)的消息,內(nèi)容格式是xml,toJSON就是我解析xml為json的方法,如下
//解析器
var toJSON = function (xml, res) {
var msg = {};
xml2js.parseString(xml, function (err, result) {
var data = result.xml;
msg.ToUserName = data.ToUserName[0];
msg.FromUserName = data.FromUserName[0];
msg.CreateTime = data.CreateTime[0];
msg.MsgType = data.MsgType[0];
switch (msg.MsgType) {
case 'text' :
msg.Content = data.Content[0];
msg.MsgId = data.MsgId[0];
res.setHeader("Content-Type", "text/plain");
res.send("");
return handle_text(msg, res);
break;
case 'image' :
msg.PicUrl = data.PicUrl[0];
msg.MsgId = data.MsgId[0];
msg.MediaId = data.MediaId[0];
res.setHeader("Content-Type", "text/plain");
res.send("");
break;
case 'voice' :
msg.MediaId = data.MediaId[0];
msg.Format = data.Format[0];
msg.MsgId = data.MsgId[0];
res.setHeader("Content-Type", "text/plain");
res.send("");
break;
case 'video' :
msg.MediaId = data.MediaId[0];
msg.ThumbMediaId = data.ThumbMediaId[0];
msg.MsgId = data.MsgId[0];
res.setHeader("Content-Type", "text/plain");
res.send("");
break;
case 'location' :
msg.Location_X = data.Location_X[0];
msg.Location_Y = data.Location_Y[0];
msg.Scale = data.Scale[0];
msg.Label = data.Label[0];
msg.MsgId = data.MsgId[0];
res.setHeader("Content-Type", "text/plain");
res.send("");
break;
case 'link' :
msg.Title = data.Title[0];
msg.Description = data.Description[0];
msg.Url = data.Url[0];
msg.MsgId = data.MsgId[0];
res.setHeader("Content-Type", "text/plain");
res.send("");
break;
case 'event' :
msg.Event = data.Event[0];
if (data.EventKey && _.isArray(data.EventKey) && data.EventKey.length > 0) {
msg.EventKey = data.EventKey[0];
return handle_event(msg, res);
}
res.setHeader("Content-Type", "text/plain");
res.send("");
break;
}
});
};
說(shuō)明:這里我用戶(hù)發(fā)過(guò)來(lái)的消息xml解釋稱(chēng)json數(shù)據(jù)msg, 根據(jù)不同的類(lèi)型做不同的處理,如上文本,圖片,音頻,視頻,鏈接,事件等消息
var handle_text = function (msg, res) {
var text = msg.Content;
if(text.trim() == "研發(fā)"){
var data = {
"touser":msg.FromUserName,
"msgtype":"news",
"news":{
"articles": [
{
"title":"重磅!Worktile 推出研發(fā)管理解決方案",
"description":"項(xiàng)目進(jìn)度清晰掌握,快速跟進(jìn)產(chǎn)品Bug,多維度統(tǒng)計(jì)報(bào)表,文件文檔有序管理",
"url":"https://pro.worktile.com/solution/dev",
"picurl":"https://wt-prj.oss.aliyuncs.com/b327e3a5666048279583e8e026ac6b87/4bb6e53c-8516-4466-b278-4f3b596e46db.png"
}
]
}
};
sendMessageToUser(data);
}else if(text.trim() == "電商"){
var data = {
"touser":msg.FromUserName,
"msgtype":"news",
"news":{
"articles": [
{
"title":"Worktile 『電商解決方案』上線(xiàn)!",
"description":"降低運(yùn)營(yíng)成本,提高團(tuán)隊(duì)效率。日常運(yùn)營(yíng)、大促籌備、售后跟蹤、研發(fā)管理……盡在掌握。",
"url":"https://pro.worktile.com/solution/ecommerce",
"picurl":"https://cdn.worktile.com/solution/ecommerce.png"
}
]
}
};
sendMessageToUser(data);
}
};
說(shuō)明:這是對(duì)文本消息的處理,如上,如果接收到 研發(fā) 字樣的消息,公眾號(hào)會(huì)給該用戶(hù)發(fā)送一條新聞消息
var handle_event = function (msg, res) {
console.log("weixin receive message ===", msg)
if (msg.Event == 'CLICK' && msg.EventKey == 'work_together') {
var text = "Hello,謝謝對(duì) Worktile 的關(guān)注啦,請(qǐng)?jiān)L問(wèn)worktile官方網(wǎng)站了解。。。。。";
var data = {
touser : msg.FromUserName,
msgtype: "text",
text : {
content: text
}
};
sendMessageToUser(data);
}
};
說(shuō)明:這是對(duì)事件消息的處理,如上,如果接收到 msg.Event == 'CLICK' && msg.EventKey == 'work_together' 事件的消息,公眾號(hào)會(huì)給該用戶(hù)發(fā)送一條文本消息,當(dāng)然事件消息有很多,如:subscribe關(guān)注公眾號(hào)消息,unsubscribe取消關(guān)注,掃描帶參數(shù)二維碼事件,還有如上說(shuō)的自定義菜單事件, 上報(bào)地理位置事件等
var sendMessageToUser = function ( data) {
get_token(function (err, obj) {
request.post({
url : "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + obj.access_token,
headers: {"Content-Type": "application/json"},
json : data
}, function (err, res, body) {
console.log("sendmessage....", body)
})
});
};
這個(gè)發(fā)放就是調(diào)用微信接口給用戶(hù)發(fā)送消息,那接下來(lái)咱們就看下發(fā)送消息
-
發(fā)送消息
發(fā)送消息分為,發(fā)送被動(dòng)消息,發(fā)送客服消息,發(fā)送模版消息- 被動(dòng)消息如上接收消息后根據(jù)消息判斷發(fā)送給用戶(hù)的消息即是被動(dòng)消息
-
客服消息,是公眾號(hào)收到用戶(hù)來(lái)的消息客服根據(jù)內(nèi)容回復(fù)給用戶(hù)的消息
如果用戶(hù)并沒(méi)有給公眾號(hào)發(fā)消息,此時(shí)客服是無(wú)法給用戶(hù)發(fā)送消息的,這是微信做的一個(gè)限制
如下代碼:wtutil.get_token(function (err, obj) { //var text = "你好,這是一條消息,多謝支持..."; //var data = { // touser : "oy4hbwbd0MOMmn8aUtQWMcNxs8PI", // msgtype: "text", // text : { // content: text // } //}; var data = { touser : "oy4hbwbd0MOMmn8aUtQWMcNxs8PI", msgtype: "image", "image": { "media_id":"ZqQGrsR6ivb273zLApNfkEdAP3UI8nHJTJ9ekelfJ8OhKUF6UG-o6YbOBv4uWf4R" } }; request.post({ url : "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + obj.access_token, headers: {"Content-Type": "application/json"}, json : data }, function (err, res, body) { console.log(body); }) });
msgtype: 是消息類(lèi)型,上面注釋掉的是文本消息,下面是個(gè)圖片消息,touser是用戶(hù)的openid,這里我只是取過(guò)來(lái)直接使用的,這里跟上面接收消息后處理給用戶(hù)發(fā)消息有寫(xiě)重復(fù),就不多介紹了
模版消息相對(duì)來(lái)說(shuō)復(fù)雜一下,首先要從公眾號(hào)添加或者申請(qǐng)消息模版,如下圖

那么有了消息模版之后就可以拿到模版ID,然后給用戶(hù)發(fā)送模版消息了
get_token(function (err, obj) {
var data = {
"touser" : "oy4hbwbd0MOMmn8aUtQWMcNxs8PI",
"template_id": "z6yV_lOIAM-LQbsrG-B3hTQvwt8_4Y3wVU2PH9UW16c",
"url" : "https://worktile.com",
"topcolor" : "#FF00FF",
"data" : {
"first" : {
"value": "測(cè)試哈哈哈,顏色可以自定義",
"color": "#33FF00"
},
"one": {
"value": "one",
"color": "#173177"
},
"two": {
"value": "two",
"color": "#FF0033"
},
"three": {
"value": "three",
"color": "#173177"
},
"remark" : {
"value": "remark,了解更多詳情,關(guān)注我。。。。",
"color": "#33FF00"
}
}
};
request.post({
url : "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + obj.access_token,
headers: {"Content-Type": "application/json"},
json : data
}, function (err, res, body) {
console.log(body, "----")
})
});
需要說(shuō)明的是參數(shù)中data里面的key(first, one, two,three, remark)是模版中定義的,這里需要根據(jù)模版內(nèi)容來(lái)寫(xiě),還有可以設(shè)置每個(gè)字段的顏色值等屬性
到此消息管理這部分已經(jīng)說(shuō)的差不多了,咱們繼續(xù)介紹。。。
第四章:微信網(wǎng)頁(yè)開(kāi)發(fā)
這個(gè)章節(jié)跟前面幾章不同,前面幾章介紹的是公眾號(hào)開(kāi)發(fā)的一些東西,這個(gè)章節(jié)介紹的是網(wǎng)頁(yè)開(kāi)發(fā),主要針對(duì)h5應(yīng)用或者是頁(yè)面的開(kāi)發(fā),Worktile微信版就是微信網(wǎng)頁(yè)開(kāi)發(fā)完成的,下面咱們一步步的介紹。
-
微信網(wǎng)頁(yè)授權(quán)
微信網(wǎng)頁(yè)授權(quán)采用的是Oauth2.0的授權(quán)方式:
先進(jìn)步:訪(fǎng)問(wèn)如下鏈接獲取codehttps://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
redirect_uri是你h5地址,授權(quán)成功后會(huì)把code加入到地址上,類(lèi)似于:https://weixin.worktile.com?code=xxx這樣
第二步:通過(guò)code換取網(wǎng)頁(yè)授權(quán)access_token
請(qǐng)求接口(get請(qǐng)求)https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
返回的結(jié)果如下
{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" }
access_token是用戶(hù)授權(quán)的token,openid是用戶(hù)對(duì)于該公眾號(hào)的唯一標(biāo)示,refresh_token:可以調(diào)用刷新token的接口獲取較新的token
第三步:獲取用戶(hù)信息(需scope為 snsapi_userinfo)
請(qǐng)求接口(get請(qǐng)求)https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
通過(guò)以上3個(gè)步驟就可以獲取用戶(hù)的信息,進(jìn)行用戶(hù)的管理操作了
-
debug: true, // 開(kāi)啟調(diào)試模式,調(diào)用的所有api的返回值會(huì)在客戶(hù)端alert出來(lái),若要查看傳入的參數(shù),可以在pc端打開(kāi),參數(shù)信息會(huì)通過(guò)log打出,僅在pc端時(shí)才會(huì)打印。 appId: '', // 必填,公眾號(hào)的唯一標(biāo)識(shí) timestamp: , // 必填,生成簽名的時(shí)間戳 nonceStr: '', // 必填,生成簽名的隨機(jī)串 signature: '',// 必填,簽名,見(jiàn)附錄1 jsApiList: [] // 必填,需要使用的JS接口列表 });
`如上代碼中的timestamp, nonceStr, signature需要服務(wù)端做好簽名返回給頁(yè)面,這里可以使用異步調(diào)用的方式,如下為服務(wù)端簽名代碼`
var sign = function (jsapi_ticket, url) { var ret = { jsapi_ticket: jsapi_ticket, nonceStr: createNonceStr(), timestamp: createTimestamp(), url: url }; var string = raw(ret); jsSHA = require('jssha'); shaObj = new jsSHA(string, 'TEXT'); ret.signature = shaObj.getHash('SHA-1', 'HEX'); return ret; };
ret就是我們需要的簽名結(jié)果,其中jsapi_ticket是調(diào)用ticket接口獲取的,官方文檔中也有說(shuō)明https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token= access_token&type=jsapi, rul是獲取簽名的當(dāng)前網(wǎng)頁(yè)地址, nonceStr:隨機(jī)字符串,timestamp:當(dāng)前10位時(shí)間戳
var raw = function (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; };
這個(gè)方法是對(duì)簽名對(duì)象的一個(gè)字符串格式化算法
-
通過(guò)ready接口處理成功驗(yàn)證
wx.ready(function(){ // config信息驗(yàn)證后會(huì)執(zhí)行ready方法,所有接口調(diào)用都必須在config接口獲得結(jié)果之后,config是一個(gè)客戶(hù)端的異步操作,所以如果需要在頁(yè)面加載時(shí)就調(diào)用相關(guān)接口,則須把相關(guān)接口放在ready函數(shù)中調(diào)用來(lái)確保正確執(zhí)行。對(duì)于用戶(hù)觸發(fā)時(shí)才調(diào)用的接口,則可以直接調(diào)用,不需要放在ready函數(shù)中。 });
-
調(diào)用接口
根據(jù)config里面 jsApiList的配置可以調(diào)用js-sdk的接口,如分享朋友圈,分享微信好友等。。。wx.onMenuShareTimeline({ title: '', // 分享標(biāo)題 link: '', // 分享鏈接 imgUrl: '', // 分享圖標(biāo) success: function () { // 用戶(hù)確認(rèn)分享后執(zhí)行的回調(diào)函數(shù) }, cancel: function () { // 用戶(hù)取消分享后執(zhí)行的回調(diào)函數(shù) } });
以上就是微信公眾號(hào)開(kāi)發(fā)的一些內(nèi)容,算是入門(mén)篇。其實(shí)關(guān)于微信的開(kāi)發(fā)還有很多可以做的事情,而且隨著需求越來(lái)越多,技術(shù)越來(lái)越完善,相信之后關(guān)于微信的開(kāi)發(fā)一定還會(huì)有更多驚喜!