大家作為前端可能活多或少的都寫過結合微信jsdk開發的微信h5網頁,對授權登錄這塊以及部分sdk可能都有通過后臺接口來調用,那么大家有沒有想過,這些后臺接口都是怎么寫的呢?那么今天這篇文章就帶大家來了解下是如何寫的!
注意:sunny-ngrok的原型是ngrok,不過ngrok是國外的,sunny-ngrok是國內的一個私服,速度更快了,主要作用是域名轉發,模擬公網ip和端口,甚至可以配置給客戶在公網上展示項目。地址:http://www.ngrok.cc/ ,進去后注冊開通隧道,有免費的。 記住:一個微信號只能注冊一種微信產品,但是可以管理多個。
這是我的隧道:(免費的如果啟動不了就直接用這個吧) 使用sunny-ngrok嘗試一次轉發: 下載工具,啟動工具,輸入隧道id,回車
說明127.0.0.1:3000已經被轉發到zhifieji.vipgz4.idcfengye.com的公網ip上了。
建個weixin目錄,npm初始化:
npm?init?-y
把下面的內容復制到package.json里:
{ ??"name":?"weixin-lesson", ??"version":?"1.0.0", ??"description":?"微信開發", ??"main":?"index.js", ??"directories":?{ ????"doc":?"doc" ??}, ??"scripts":?{ ????"sunny":?"./bin/sunny?clientid?62d16df91a118fd3", ????"ngrok":?"./bin/ngrok?http?3000", ????"test":?"echo?\"Error:?no?test?specified\"?&&?exit?1" ??}, ??"repository":?{ ????"type":?"git", ????"url":?"git@gitlab.kaikeba.com:web_dev/weixin-lesson.git" ??}, ??"author":?"", ??"license":?"ISC", ??"dependencies":?{ ????"axios":?"^0.18.0", ????"co-wechat":?"^2.3.0", ????"co-wechat-oauth":?"^2.0.1", ????"crypto":?"^1.0.1", ????"express":?"^4.16.4", ????"jsonwebtoken":?"^8.4.0", ????"koa":?"^2.6.2", ????"koa-bodyparser":?"^4.2.1", ????"koa-compress":?"^3.0.0", ????"koa-jwt":?"^3.5.1", ????"koa-route":?"^3.2.0", ????"koa-router":?"^7.4.0", ????"koa-socket":?"^4.4.0", ????"koa-static":?"^5.0.0", ????"koa-views":?"^6.1.5", ????"koa-websocket":?"^5.0.1", ????"koa-xml-body":?"^2.1.0", ????"moment":?"^2.23.0", ????"mongoose":?"^5.4.4", ????"promise-redis":?"0.0.5", ????"pug":?"^2.0.3", ????"redis":?"^2.8.0", ????"request":?"^2.88.0", ????"request-promise":?"^4.2.2", ????"socket.io":?"^2.2.0", ????"watch":?"^1.0.2", ????"wechat":?"^2.1.0", ????"wechat-oauth":?"^1.5.0", ????"xml2js":?"^0.4.19" ??} }
然后安裝依賴
npm?install #?或 yarn
再在weixin目錄下建立seed目錄,seed目錄下建立index.js和index.html。
index.js:
const?Koa?=?require('koa') const?Router?=?require('koa-router') const?static?=?require('koa-static') const?bodyParser?=?require('koa-bodyparser'); const?app?=?new?Koa() app.use(bodyParser()) const?router?=?new?Router() app.use(static(__dirname?+?'/')) app.use(router.routes());?/*啟動路由*/ app.use(router.allowedMethods()); app.listen(3000);
index.html:
<!DOCTYPE?html> <html> <head> ????<title>全棧開發微信公眾號</title> ????<meta?charset="UTF-8"> ????<meta?name="viewport"?content="width=device-width,initial-scale=1,user-scalable=0"> ????<script?src="https://unpkg.com/vue@2.1.10/dist/vue.min.js"></script> ????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script> ????<script?src="https://unpkg.com/cube-ui/lib/cube.min.js"></script> ????<script?src="https://cdn.bootcss.com/qs/6.6.0/qs.js"></script> ????<script?src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script> ????<link?rel="stylesheet"?href="https://unpkg.com/cube-ui/lib/cube.min.css"> ????<style> ????????/*?.cube-btn?{ ????????????margin:?10px?0; ????????}?*/ ????</style> </head> <body> ????<div?id="app"> ????????<cube-input?v-model="value"></cube-input> ????????<cube-button?@click='click'>Click</cube-button> ????</div> ????<script> ????????var?app?=?new?Vue({ ????????????el:?'#app', ????????????data:?{ ????????????????value:?'input' ????????????}, ????????????methods:?{ ????????????????click:?function?()?{ ????????????????????console.log('click') ????????????????} ????????????}, ????????????mounted:?function?()?{ ????????????}, ????????}); ????</script> </body> </html>
在seed目錄打開終端,執行nodemon(需要安裝), 3000端口打開127.0.0.1
npm?install?-g?nodemon nodemon
前面說通過ngrok把3000端口轉發到了zhifieji.vipgz4.idcfengye.com上,我們打開這個網址試下:
微信自帶消息自動回復功能,可以在公眾平臺設置,但是很死板,無法動態回復消息
進入微信開發者工具,申請公眾平臺測試賬號 有一些配置,填寫轉發的域名,token隨意,要和服務器的用的一樣
接口配置的URL就是轉發的網址加了/wechat,再去提交接口配置信息(要多試試,才能提交成功)。
再就是在項目seed目錄里配置,新建一個conf.js,把前面的appid、appsecret、token帶上:
module.exports={ ????appid:?'wx77f481fc8a9113a4', ????appsecret:?'2b84470b9fb0f8166a8518c5b40edaf9', ????token:?'qweqwe' }
在index.js里引入,使用一個庫co-wechat,所以index.js將變成下面:
const?Koa?=?require('koa') const?Router?=?require('koa-router') const?static?=?require('koa-static') const?bodyParser?=?require('koa-bodyparser'); const?app?=?new?Koa() const?conf?=?require('./conf')//引入conf app.use(bodyParser()) const?router?=?new?Router() app.use(static(__dirname?+?'/')) const?wechat?=?require('co-wechat')//使用co-wechat庫 router.all('/wechat',?wechat(conf).middleware( ????async?message?=>?{ ????????console.log('wechat:',?message) ????????return?'Hello?World?'?+?message.Content ????} )) app.use(router.routes());?/*啟動路由*/ app.use(router.allowedMethods()); app.listen(3000);
知識點:co-開頭的庫是代表著滿足異步要求的庫
成功后,這個時候呢,可以關注下面的測試號二維碼
發送1,會回復Hello World 1(如果是設置的沒有這中獲取用戶發送的信息的方法,所以有時候也需要api),如下圖: 
相關文檔:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
獲取token:
const?axios?=?require('axios') const?tokenCache?=?{ ????access_token:'', ????updateTime:Date.now(), ????expires_in:7200 } router.get('/getTokens',async?ctx?=>?{//獲取token ????const?wxDomain?=??`https://api.weixin.qq.com` ????const?path?=?`/cgi-bin/token` ????const?param?=?`?grant_type=client_credential&appid={conf.appid}&secret={conf.appsecret}` ????const?url?=?wxDomain?+?path?+?param ????const?res?=?await?axios.get(url) ????Object.assign(tokenCache,res.data,{ ????????updateTime:Date.now() ????}) ????ctx.body?=?res.data })
獲取用戶信息
router.get('/getFollowers',async?ctx?=>?{//獲取用戶信息 ????const?url?=?`https://api.weixin.qq.com/cgi-bin/user/get?access_token=${tokenCache.access_token}` ????const?res?=?await?axios.get(url) ????console.log('getFollowers:',res) ????ctx.body?=?res.data })
以上為原生的寫法,實際上我們十有庫可以用的。
使用 co-wechat-api
yarn?add?co-wechat-api
const?WechatAPI?=?require('co-wechat-api') const?api?=?new?WechatAPI( ????conf.appid, ????conf.appsecret, ????//?//?取Token ????//?async?()?=>?await?ServerToken.findOne(), ????//?//?存Token ????//?async?token?=>?await?ServerToken.updateOne({},?token,?{?upsert:?true?}) ) router.get('/getFollowers',?async?ctx?=>?{ ????let?res?=?await?api.getFollowers() ????res?=?await?api.batchGetUsers(res.data.openid,?'zh_CN')//加上后會返回詳細信息 ????ctx.body?=?res })
全局票據需要基于mongodb或者redires,我們用mongodb。 新建個mongoose.js
const?mongoose?=?require('mongoose') const?{ ????Schema }?=?mongoose mongoose.connect('mongodb://localhost:27017/weixin',?{ ????useNewUrlParser:?true },?()?=>?{ ????console.log('Mongodb?connected..') }) exports.ServerToken?=?mongoose.model('ServerToken',?{ ????accessToken:?String });
index.js里改造上面用co-wechat-api的:
const?{?ServerToken?}?=?require('./mongoose')//全局票據來源 const?WechatAPI?=?require('co-wechat-api') const?api?=?new?WechatAPI( ????conf.appid, ????conf.appsecret, ????//?取Token ????async?()?=>?await?ServerToken.findOne(), ????//?存Token ????async?token?=>?await?ServerToken.updateOne({},?token,?{?upsert:?true?}) ) router.get('/getFollowers',?async?ctx?=>?{ ????let?res?=?await?api.getFollowers() ????res?=?await?api.batchGetUsers(res.data.openid,?'zh_CN') ????ctx.body?=?res })
再在index.html中加上一個按鈕和一個按鈕點擊方法:
<cube-button?@click='getFollowers'>getFollowers</cube-button>
?async?getFollowers(){ ??????const?res?=?await?axios.get('/getFollowers') ??????console.log('res',res) },
動動小手點擊一下:(這個getFollwers拿到了數據)
就類似于這個,手機微信掃碼微信公眾平臺前臺發送1或者2,餅圖自動統計1和2發送的次數。  后臺(模擬器)會顯示前臺(手機微信在測試訂閱號)的推送,而且更新echart。 代碼為下面的vote部分,后面會放出代碼。
首先要知道有三個端,瀏覽器,服務器,微信服務器。
1.瀏覽器向服務器發送認證請求
2.服務器讓瀏覽器重定向微信認證界面
3.瀏覽器向微信服務器請求第三方認證(微信認證)
4.微信服務器毀掉給服務器一個認證code
5.服務器用code向微信服務器申請認證令牌
6.微信服務器返給服務器一個令牌
最后當服務器得到令牌認證成功后,發給瀏覽器一個指令,刷新界面
刷新后就會有一個用戶信息
使用微信開發者工具,選擇公眾號網頁,用來預覽。
PS:以上代碼中
- 消息推動我放在vote目錄了
- 剩余的api調用方法放在了seed目錄
配置js接口安全域名,就是我們轉發的公網域名(不用帶協議):zhifieji.vipgz4.idcfengye.com 再就是每個微信接口api那里也要授權域名,即下圖的修改位置,修改的和上面一樣:(zhifieji.vipgz4.idcfengye.com)
把前面的項目中seed目錄拷貝一份叫做seed_up,我們給予前面的在seed_up中繼續干! index.js;
const?OAuth?=?require('co-wechat-oauth')//引入一個oauth庫 const?oauth?=?new?OAuth(conf.appid,conf.appsecret) /** ?*?生成用戶URL ?*/ router.get('/wxAuthorize',?async?(ctx,?next)?=>?{ ????const?state?=?ctx.query.id ????console.log('ctx...'?+?ctx.href) ????let?redirectUrl?=?ctx.href ????redirectUrl?=?redirectUrl.replace('wxAuthorize',?'wxCallback') ????const?scope?=?'snsapi_userinfo' ????const?url?=?oauth.getAuthorizeURL(redirectUrl,?state,?scope) ????console.log('url'?+?url) ????ctx.redirect(url) }) /** ?*?用戶回調方法 ?*/ router.get('/wxCallback',?async?ctx?=>?{ ????const?code?=?ctx.query.code ????console.log('wxCallback?code',?code) ????const?token?=?await?oauth.getAccessToken(code) ????const?accessToken?=?token.data.access_token ????const?openid?=?token.data.openid ????console.log('accessToken',?accessToken) ????console.log('openid',?openid) ????ctx.redirect('/?openid='?+?openid) }) /** ?*?獲取用戶信息 ?*/ router.get('/getUser',?async?ctx?=>?{ ????const?openid?=?ctx.query.openid ????const?userInfo?=?await?oauth.getUser(openid) ????console.log('userInfo:',?userInfo) ????ctx.body?=?userInfo })
index.html:
<!DOCTYPE?html> <html> <head> ????<title>全棧開發微信公眾號</title> ????<meta?charset="UTF-8"> ????<meta?name="viewport"?content="width=device-width,initial-scale=1,user-scalable=0"> ????<script?src="https://unpkg.com/vue@2.1.10/dist/vue.min.js"></script> ????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script> ????<script?src="https://unpkg.com/cube-ui/lib/cube.min.js"></script> ????<script?src="https://cdn.bootcss.com/qs/6.6.0/qs.js"></script> ????<script?src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script> ????<link?rel="stylesheet"?href="https://unpkg.com/cube-ui/lib/cube.min.css"> ????<style> ????????/*?.cube-btn?{ ????????????margin:?10px?0; ????????}?*/ ????</style> </head> <body> ????<div?id="app"> ????????<cube-input?v-model="value"></cube-input> ????????<cube-button?@click='click'>Click</cube-button> ????????<cube-button?@click='getTokens'>getTokens</cube-button> ????????<cube-button?@click='getFollowers'>getFollowers</cube-button> ????????<cube-button?@click='auth'>微信登錄</cube-button> ????????<cube-button?@click='getUser'>獲取用戶信息</cube-button> ????</div> ????<script> ????????var?app?=?new?Vue({ ????????????el:?'#app', ????????????data:?{ ????????????????value:?'input' ????????????}, ????????????methods:?{ ????????????????click:?function?()?{ ????????????????????console.log('click') ????????????????}, ????????????????async?getTokens(){ ????????????????????const?res?=?await?axios.get('/getTokens') ????????????????????console.log('res:',res) ????????????????}, ????????????????async?getFollowers(){ ????????????????????const?res?=?await?axios.get('/getFollowers') ????????????????????console.log('res',res) ????????????????}, ????????????????async?auth(){ ????????????????????window.location.href?=?'/wxAuthorize' ????????????????}, ????????????????async?getUser(){ ????????????????????const?qs?=?Qs.parse(window.location.search.substr(1)) ????????????????????const?openid=?qs.openid ????????????????????const?res?=?await?axios.get('/getUser',{ ????????????????????????params:{ ????????????????????????????openid ????????????????????????} ????????????????????}) ????????????????????console.log('res',res) ????????????????} ????????????}, ????????????mounted:?function?()?{ ????????????}, ????????}); ????</script> </body> </html>
全局票據(一樣用到mongoose,從上次的修改) mongoose.js:
const?mongoose?=?require('mongoose') const?{ ????Schema }?=?mongoose mongoose.connect('mongodb://localhost:27017/weixin',?{ ????useNewUrlParser:?true },?()?=>?{ ????console.log('Mongodb?connected..') }) exports.ServerToken?=?mongoose.model('ServerToken',?{ ????accessToken:?String }); //以下為seed_up新增 schema?=?new?Schema({ ????access_token:?String, ????expires_in:?Number, ????refresh_token:?String, ????openid:?String, ????scope:?String, ????create_at:?String }); //?自定義getToken方法 schema.statics.getToken?=?async?function?(openid)?{ ????return?await?this.findOne({ ????????openid:?openid ????}); }; schema.statics.setToken?=?async?function?(openid,?token)?{ ????//?有則更新,無則添加 ????const?query?=?{ ????????openid:?openid ????}; ????const?options?=?{ ????????upsert:?true ????}; ????return?await?this.updateOne(query,?token,?options); }; exports.ClientToken?=?mongoose.model('ClientToken',?schema);
繼續改下index.js:
const?{?ServerToken,ClientToken?}?=?require('./mongoose')//全局票據來源 const?oauth?=?new?OAuth(conf.appid,?conf.appsecret, ????async?function?(openid)?{ ????????return?await?ClientToken.getToken(openid) ????}, ????async?function?(openid,?token)?{ ????????return?await?ClientToken.setToken(openid,?token) ????} )
寫出來效果如下:完美
[video(video-KTwIIMGP-1632587503822)(type-tencent)(url-https://v.qq.com/txp/iframe/player.html?vid=e3278ajafl6)(image-http://puui.qpic.cn/vpic/0/e3278ajafl6.png/0)(title-)]
準備:
index.html:
<cube-button?@click='getJSConfig'>獲取jsconfig</cube-button> async?getJSConfig(){ ??console.log('wx',wx) ??const?res?=?await?axios.get('/getJSConfig',{ ??????params:{ ??????????url:window.location.href ??????} ??}) ??console.log('config',res) ??res.data.jsApiList?=?['onMenuShareTimeline'] ??wx.config(res.data) ??wx.ready(function?()?{ ??????console.log('wx.ready......') ??}) }
index.js:
/** ?*?獲取JSConfig ?*/ router.get('/getJsConfig',async?ctx?=>?{ ????console.log('getJSSDK...',ctx.query) ????const?res?=?await?api.getJsConfig(ctx.query) ????ctx.body?=?res })
如果能走到wx.ready(),說明這個時候可以使用別的功能那個api了。
在wx.ready()后加,當然在ready()里加最為合理:
//獲取網絡狀態 wx.getNetworkType({ ?????success:?function?(res)?{ ?????????//?返回網絡類型2g,3g,4g,wifi ?????????const?networkType?=?res.networkType ?????????console.log('getNetworkType...',?networkType) ?????} })
獲取到我是wifi環境,很完美!其余的jssdk調用方法也是如此!
還有一點,通常我們十前后端分離的開發項目,所以我把項目改成了前后端分離。
1、新建了個weixin_pro的項目 2、將weixin項目的package.json復制到weixin_pro 3、分一個cube-ui目錄為前端代碼 4、分一個quiz目錄為后端代碼 5、weixin_pro下安裝依賴,為后端依賴 6、cube-ui下安裝依賴為前端依賴 7、分別啟動前端代碼后后端代碼
運行效果如下:
[video(video-v2FEoxvx-1632588608459)(type-tencent)(url-https://v.qq.com/txp/iframe/player.html?vid=l3278ig1v72)(image-http://puui.qpic.cn/vpic/0/l3278ig1v72.png/0)(title-)]
前后端分離前的代碼:https://gitee.com/huqinggui/weixin.git 前后端分離后的代碼:https://gitee.com/huqinggui/weixin_pro.git
|