您好,登錄后才能下訂單哦!
這篇文章主要介紹了使用ThinkJs怎么搭建微信中控服務(wù),具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
本文不涉及任何接口安全、參數(shù)校驗(yàn)之類的東西,默認(rèn)對調(diào)用方無腦級的信任:joy: 目前自用的接口包括但不限于以下這些
|--- 微信相關(guān) | |--- 0. 處理微信推過來的一些消息 | |--- 1. 獲取微信SDK配置參數(shù) | |--- 2. 微信鑒權(quán)登陸 | |--- 3. 獲取微信用戶信息 | |--- 4. 獲取AccessToken | |--- 5. 批量發(fā)送模版消息 | |--- 6. 獲取模版消息列表 | |--- 7. 批量發(fā)送客服消息
背景
【需求】小項目很多很雜,而且大部分需求都是基于微信開發(fā)的,每次都查微信文檔的話就會很郁悶:unamused:...
【號多】公眾號超級多,項目中偶爾會涉及借權(quán)獲取用戶信息(在不綁定微信開放平臺的前提下,需要臨時自建各個公眾號的openid關(guān)聯(lián)關(guān)系),類似這樣同時需要不止一個公眾號配合來完成一件事的需求,就容易把人整懵逼...
【支付】微信支付的商戶號也很多,而且有時候支付需要用的商戶號,還不能用關(guān)聯(lián)的公眾號取出來的openid去支付...
【官方】微信官方文檔建議!把獲取AccessToken等微信API抽離成單獨(dú)的服務(wù)... 等等等等........所以...:joy:
創(chuàng)建ThinkJS項目
官網(wǎng)
thinkjs.org/
簡介
ThinkJS 是一款面向未來開發(fā)的 Node.js 框架,整合了大量的項目最佳實(shí)踐,讓企業(yè)級開發(fā)變得如此簡單、高效。從 3.0 開始,框架底層基于 Koa 2.x 實(shí)現(xiàn),兼容 Koa 的所有功能。
安裝腳手架
$ npm install -g think-cli
創(chuàng)建及啟動項目
$ thinkjs new demo; $ cd demo; $ npm install; $ npm start;
目錄結(jié)構(gòu)
|--- development.js //開發(fā)環(huán)境下的入口文件 |--- nginx.conf //nginx 配置文件 |--- package.json |--- pm2.json //pm2 配置文件 |--- production.js //生產(chǎn)環(huán)境下的入口文件 |--- README.md |--- src | |--- bootstrap //啟動自動執(zhí)行目錄 | | |--- master.js //Master 進(jìn)程下自動執(zhí)行 | | |--- worker.js //Worker 進(jìn)程下自動執(zhí)行 | |--- config //配置文件目錄 | | |--- adapter.js // adapter 配置文件 | | |--- config.js // 默認(rèn)配置文件 | | |--- config.production.js //生產(chǎn)環(huán)境下的默認(rèn)配置文件,和 config.js 合并 | | |--- extend.js //extend 配置文件 | | |--- middleware.js //middleware 配置文件 | | |--- router.js //自定義路由配置文件 | |--- controller //控制器目錄 | | |--- base.js | | |--- index.js | |--- logic //logic 目錄 | | |--- index.js | |--- model //模型目錄 | | |--- index.js |--- view //模板目錄 | |--- index_index.html
安裝think-wechat插件
介紹
微信中間件,基于 node-webot/wechat,支持 thinkJS 3.0
安裝
$ npm install think-wechat --save
或
$ cnpm install think-wechat --save
配置
文件:/src/config/middleware.js
const wechat = require('think-wechat') module.exports = [ ... { handle: wechat, match: '/index', options: { token: '', // 令牌,和公眾號/基本配置/服務(wù)器配置里面寫一樣的即可 appid: '', // 這里貌似可以隨便填,因?yàn)槲覀兒竺嬉脭?shù)據(jù)庫配置多個公眾號 encodingAESKey: '', checkSignature: false } }, { handle: 'payload', // think-wechat 必須要在 payload 中間件前面加載,它會代替 payload 處理微信發(fā)過來的 post 請求中的數(shù)據(jù)。 options: { keepExtensions: true, limit: '5mb' } }, ]
注:match下我這里寫的是 /index
,對應(yīng)的項目文件是 /src/controller/index.js
,對應(yīng)的公眾號后臺所需配置的服務(wù)器地址就是 http(https)://域名:端口/index
創(chuàng)建數(shù)據(jù)庫和相關(guān)表
我這里創(chuàng)建了三個微信的相關(guān)表。
配置表:wx_config
字段 | 類型 | 說明 |
---|---|---|
id | int | 主鍵 |
name | varchar | 名稱 |
appid | varchar | appid |
secret | varchar | secret |
用戶表:wx_userinfo
字段 | 類型 | 注釋 |
---|---|---|
id | int | 主鍵 |
subscribe | int | 用戶是否訂閱該公眾號標(biāo)識,值為0時,代表此用戶沒有關(guān)注該公眾號,拉取不到其余信息。 |
nickname | varchar | 用戶的昵稱 |
sex | int | 用戶的性別,值為1時是男性,值為2時是女性,值為0時是未知 |
language | varchar | 用戶所在省份 |
city | varchar | 用戶所在城市 |
province | varchar | 用戶所在省份 |
country | varchar | 用戶所在國家 |
headimgurl | longtext | 用戶頭像,最后一個數(shù)值代表正方形頭像大?。ㄓ?、46、64、96、132數(shù)值可選,0代表640*640正方形頭像),用戶沒有頭像時該項為空。若用戶更換頭像,原有頭像URL將失效。 |
subscribe_time | double | 用戶關(guān)注時間,為時間戳。如果用戶曾多次關(guān)注,則取最后關(guān)注時間 |
unionid | varchar | 只有在用戶將公眾號綁定到微信開放平臺帳號后,才會出現(xiàn)該字段。 |
openid | varchar | 用戶的標(biāo)識,對當(dāng)前公眾號唯一 |
wx_config_id | int | 對應(yīng)配置的微信號id |
模版消息日志表:wx_template_log
字段 | 類型 | 注釋 |
---|---|---|
id | int | 主鍵 |
template_id | varchar | 模版id |
openid | varchar | 用戶的標(biāo)識,對當(dāng)前公眾號唯一 |
url | varchar | 跳轉(zhuǎn)url |
miniprogram | varchar | 跳轉(zhuǎn)小程序 |
data | varchar | 發(fā)送內(nèi)容json字符串 |
add_time | double | 添加時間戳 |
send_time | double | 發(fā)送時間戳 |
send_status | varchar | 發(fā)送結(jié)果 |
wx_config_id | double | 對應(yīng)配置的微信號id |
uuid | varchar | 本次發(fā)送的uuid,業(yè)務(wù)系統(tǒng)可通過uuid查詢模版消息推送結(jié)果 |
處理微信推送消息
文件目錄
/src/controller/index.js
文件內(nèi)容
module.exports = class extends think.Controller { /* * 入口:驗(yàn)證開發(fā)者服務(wù)器 * 驗(yàn)證開發(fā)者服務(wù)器,這里只是演示,所以沒做簽名校驗(yàn),實(shí)際上應(yīng)該要根據(jù)微信要求進(jìn)行簽名校驗(yàn) */ async indexAction() { let that = this; if (that.method != 'REPLY') { return that.json({code: 1, msg: '非法請求', data: null}) } const {echostr} = that.get(); return that.end(echostr); } /* * 文字 * 用于處理微信推過來的文字消息 */ async textAction() { let that = this; let {id, signature, timestamp, nonce, openid} = that.get(); let {ToUserName, FromUserName, CreateTime, MsgType, Content, MsgId} = that.post(); ..... that.success('') } /* * 事件 * 用于處理微信推過來的事件消息,例如點(diǎn)擊菜單等 */ async eventAction() { let that = this; let {id, signature, timestamp, nonce, openid} = that.get(); let {ToUserName, FromUserName, CreateTime, MsgType, Event, EventKey, Ticket, Latitude, Longitude, Precision} = that.post(); switch (Event) { case 'subscribe': // 關(guān)注公眾號 ... break; case 'unsubscribe': // 取消關(guān)注公眾號 ... break; case 'SCAN': // 已關(guān)注掃碼 ... break; case 'LOCATION': // 地理位置 ... break; case 'CLICK': // 自定義菜菜單 ... break; case 'VIEW': // 跳轉(zhuǎn) ... break; case 'TEMPLATESENDJOBFINISH':// 模版消息發(fā)送完畢 ... break; } that.success('') } }
注:支持的action包括: textAction
、 imageAction
、 voiceAction
、 videoAction
、 shortvideoAction
、 locationAction
、 linkAction
、 eventAction
、 deviceTextAction
、 deviceEventAction
。
公眾號后臺配置
注:后面跟的id參數(shù)是為了區(qū)分是哪個公眾號推過來的消息,在上面的接口參數(shù)中也有體現(xiàn)
微信相關(guān)API的編寫
目錄結(jié)構(gòu)
|--- src | |--- controller //控制器目錄 | | |--- index.js // 處理微信推送的消息,上面有寫到 | | |--- common.js // 一些公共方法 | | |--- open // 開放給其他業(yè)務(wù)服務(wù)的api接口 | | | |--- wx.js | | |--- private // 放一些內(nèi)部調(diào)用的方法,調(diào)用微信api的方法主要在這里面 | | | |--- wx.js
這個目錄結(jié)構(gòu)可能不太合理,后期再改進(jìn)吧:grin:
公共方法
// src/controller/common.js import axios from 'axios' import {baseSql} from "./unit"; module.exports = class extends think.Controller { // 獲取appinfo async getWxConfigById(id) { let that = this; let data = await that.cache(`wx_config:wxid_${id}`, async () => { // 數(shù)據(jù)庫內(nèi)取 let info = await that.model('wx_config', baseSql).where({id: id}).find(); if (!think.isEmpty(info)) { return info } }) return data || {} } // 獲取access_token async getAccessToken(id) { let that = this; let accessToken = await that.cache(`wx_access_token:wxid_${id}`, async () => { let {appid, secret} = await that.getWxConfigById(id); let {data} = await axios({ method: 'get', url: `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}` }); return data.access_token }); return accessToken } }
接口過濾器
所有開放出來的接口的前置方法,俗稱過濾器?所有開放的接口必傳get參數(shù)是 wxid
,對應(yīng)數(shù)據(jù)庫表wx_config里面 id
// src/controller/open/wx.js async __before() { let that = this; let wxid = that.get('wxid'); if (think.isEmpty(wxid)) { return that.json({code: 1, msg: 'wxid不存在'}) } that.wxConfig = await that.controller('common').getWxConfigById(wxid); if (think.isEmpty(that.wxConfig)) { return that.json({code: 1, msg: 'wxid不存在'}) } }
接口 - 獲取AccessToken
代碼
// src/controller/open/wx.js async get_access_tokenAction() { let that = this; let accessToken = await that.controller('common').getAccessToken(that.wxConfig.id); return that.json({code: 0, msg: '', data: {access_token: accessToken}}) }
文檔
接口 - 獲取微信sdk的config
代碼
// src/controller/open/wx.js async get_wxsdk_configAction() { let that = this; let {url} = that.get(); if (think.isEmpty(url)) { return that.json({code: 1, msg: '參數(shù)不正確'}) } let sdkConfig = await that.controller('private/wx').getSdkConfig(that.wxConfig.id, url); return that.json({code: 0, msg: '', data: sdkConfig}) } // src/controller/private/wx.js const sha1 = require('sha1'); const getTimestamp = () => parseInt(Date.now() / 1000) const getNonceStr = () => Math.random().toString(36).substr(2, 15) const getSignature = (params) => sha1(Object.keys(params).sort().map(key => `${key.toLowerCase()}=${params[key]}`).join('&')); async getSdkConfig(id, url) { let that = this; let {appid} = await that.controller('common').getWxConfigById(id); let shareConfig = { nonceStr: getNonceStr(), jsapi_ticket: await that.getJsapiTicket(id), timestamp: getTimestamp(), url: url } return { appId: appid, timestamp: shareConfig.timestamp, nonceStr: shareConfig.nonceStr, signature: getSignature(shareConfig) } }
文檔
接口 - 獲取UserInfo
代碼
// src/controller/open/wx.js async get_userinfoAction() { let that = this; let {openid} = that.get(); if (think.isEmpty(openid)) { return that.json({code: 1, msg: '參數(shù)不正確'}) } let userInfo = await that.controller('private/wx').getUserInfo(that.wxConfig.id, openid); if (think.isEmpty(userInfo)) { return that.json({code: 1, msg: 'openid不存在', data: null}) } return that.json({code: 0, msg: '', data: userInfo}) } // src/controller/private/wx.js async getUserInfo(id, openid) { let that = this; let userInfo = await that.cache(`wx_userinfo:wxid_${id}:${openid}`, async () => { //先取數(shù)據(jù)庫 let model = that.model('wx_userinfo', baseSql); let userInfo = await model.where({wx_config_id: id, openid: openid}).find(); if (!think.isEmpty(userInfo) && userInfo.subscribe == 1 && userInfo.unionid != null) { return userInfo } //如果數(shù)據(jù)庫內(nèi)沒有,取新的存入數(shù)據(jù)庫 let accessToken = await that.controller('common').getAccessToken(id); let url = `https://api.weixin.qq.com/cgi-bin/user/info?access_token=${accessToken}&openid=${openid}&lang=zh_CN`; let {data} = await axios({method: 'get', url: url}); if (data.openid) { //命中修改,沒有命中添加 let resId = await model.thenUpdate( Object.assign(data, {wx_config_id: id}), {openid: openid, wx_config_id: id}); return await model.where({id: resId}).find(); } }) return userInfo }
文檔
接口 - 批量發(fā)送文字客服消息
代碼
// src/controller/open/wx.js async send_msg_textAction() { let that = this; let {list} = that.post(); if (think.isEmpty(list)) { return that.json({code: 1, msg: '參數(shù)不正確'}) } that._sendMsgTextList(that.wxConfig.id, list); return that.json({code: 0, msg: '', data: null}) } async _sendMsgTextList(wxid, list) { let that = this; let apiWxController = that.controller('private/wx'); for (let item of list) { let data = await apiWxController.sendMsgText(wxid, item.openid, item.text) } } // src/controller/private/wx.js async sendMsgText(id, openid, content) { let that = this; let accessToken = await that.controller('common').getAccessToken(id); let url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}` let {data} = await axios({ method: 'post', url: url, data: {"msgtype": 'text', "touser": openid, "text": {"content": content}} }) return data; }
文檔
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“使用ThinkJs怎么搭建微信中控服務(wù)”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。