微信公眾號(hào)開發(fā)入門

? 本文是主要是針對(duì)了解微信公眾號(hào)開發(fā)或者進(jìn)行過一些簡單的開發(fā),但是不成體系的開發(fā)者副编。前后端在參與公眾號(hào)開發(fā)期間监婶,主要承擔(dān)的是各自的開發(fā)工作香到,前后端邏輯隔離較大瑞妇。本文將從申請(qǐng)測試賬號(hào)開始毙驯,選擇常用的公眾號(hào)功能,帶大家體驗(yàn)完整的微信公眾號(hào)開發(fā)的流程锰霜。

? 本文較長搀军,至少需要1-2個(gè)小時(shí)的練習(xí)時(shí)間拖叙,可以收藏起來利用碎片時(shí)間學(xué)習(xí)惧盹。

? 本文后端代碼使用nodejs

需要的前期準(zhǔn)備:

  1. 對(duì)微信公眾號(hào)開發(fā)的基本了解
  2. nodejs基礎(chǔ)知識(shí)
  3. 可通過公網(wǎng)訪問的服務(wù)器(沒有的可以去百度一個(gè)內(nèi)網(wǎng)穿透的工具)【 http://www.ngrok.cc/

演示代碼所在github倉庫地址:https://github.com/shb190802/wechat

(一盾戴、)前期準(zhǔn)備

  1. 打開ngrok網(wǎng)站宪巨,注冊(cè)一個(gè)免費(fèi)的內(nèi)網(wǎng)穿透隧道(不要http驗(yàn)證用戶名和密碼I狻Mι怼!)(有自己的服務(wù)器可忽略1堕扶、2步驟
1.png
  1. 下載下載ngrok客戶端,并啟動(dòng)隧道(請(qǐng)閱讀ngrok文檔)

    2.png
  1. 使用koa搭建本地webserver

    • 新建空白目錄奶是,打開cmd豌骏,在當(dāng)前目錄下輸入【npm init -y】初始化目錄
    > npm init -y
    
    • 安裝后邊要使用到的第三方組件:koa、koa-router、koa-body洒琢、koa-static秧秉、crypto(加密)、axios衰抑、superagent(請(qǐng)求)象迎、xml2js
    > npm i -S koa koa-router koa-body koa-static crypto axios superagent xml2js
    
    • 新增app.js
    const Koa = require('koa')
    const KoaRouter = require('koa-router')
    
    const app = new Koa()
    const router = new KoaRouter()
    
    router.get('/', ctx => {
     ctx.body = 'Hello World!'
    })
    
    app.use(router.routes())
    app.use(router.allowedMethods())
    app.listen(3000, err => {
     console.log(err || 'run in port 3000!')
    })
    
    • 啟動(dòng)服務(wù)

      由于后期會(huì)經(jīng)常修改文件,這里使用supervisor來做熱啟動(dòng)

    > supervisor app.js
    
    • 訪問ngrok贈(zèng)送域名(顯示【Hello World呛踊!】即表示內(nèi)網(wǎng)穿透成功)
    3.png
  1. 去微信申請(qǐng)一個(gè)測試的微信公眾號(hào)

? 打開: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 砾淌,使用微信登錄,申請(qǐng)一個(gè)測試的公眾號(hào)

4.png
5.png

? 你會(huì)得到一個(gè)測試appId和appSecret谭网。底部為可以體驗(yàn)的接口權(quán)限列表汪厨。

? 到此,環(huán)境準(zhǔn)備工作已經(jīng)結(jié)束愉择,你現(xiàn)在擁有一個(gè)擁有大部分測試權(quán)限的公眾號(hào)和一個(gè)可以給外網(wǎng)提供服務(wù)器的公網(wǎng)服務(wù)器骄崩。

(二、)后端服務(wù)

1.基礎(chǔ)支持-獲取access_token

? 請(qǐng)?zhí)崆伴喿x文檔【開始開發(fā)-獲取access token】 https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

? 此類接口不需要服務(wù)端響應(yīng)微信消息薄辅,所以本次開發(fā)中不將其納入測試服務(wù)中要拂,會(huì)單獨(dú)簡歷一個(gè)文件夾測試此類接口。生產(chǎn)環(huán)境請(qǐng)使用定時(shí)任務(wù)更新access token并自己保存站楚。

? 新增文件/wechat-api/1.access_token.js脱惰,輸入以下內(nèi)容:

const axios = require('axios')
const fs = require('fs')
const { appId, secret } = require('../config')

function getAccessToken () {
    let url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${secret}`
    axios({
        method: 'GET',
        url: url
    }).then(res => {
        console.log(res.data)
        if (!res.data.errcode) {
            fs.writeFileSync('./token.txt', res.data.access_token, 'utf8')
        }
    })
}
getAccessToken()

? 重新打開一個(gè)控制臺(tái),進(jìn)入wechat-api目錄窿春,輸入【node 1.acces_token.js】

> node 1.access_token.js
10.png

控制臺(tái)有以上輸入拉一,并且wechat-api目錄下,新增了一個(gè)token.txt即表示access_token獲取成功旧乞,之后的微信相關(guān)api調(diào)用蔚润,均需要使用到access_token。

2.基礎(chǔ)支持-獲取微信服務(wù)器IP地址

? 公眾號(hào)基于安全考慮尺栖,要保證收到的消息請(qǐng)求來自微信嫡纠,則可以使用此接口獲取微信的服務(wù)器列表。

? 請(qǐng)?zhí)崆安榭次臋n【開始開發(fā)-獲取微信服務(wù)器IP地址】

? 新增文件/wechat-api/2.get_wechat_service_list.js延赌,輸入以下內(nèi)容:

const axios = require('axios')
const fs = require('fs')

function getCallBackIp () {
    let token = fs.readFileSync('./token.txt', 'utf8')
    const url = `https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=${token}`
    axios({
        method: 'GET',
        url: url
    }).then(res => {
        console.log(res.data)
    })
}
getCallBackIp()

? 在控制臺(tái)輸入:

> node 2.get_wechat_service_list.js
11.png

輸出以上內(nèi)容除盏,接口調(diào)用成功!

3.接收消息-驗(yàn)證接口真實(shí)性

? 請(qǐng)?zhí)崆伴喿x文檔【開始開發(fā)-接入指南】 https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

  • 配置接口地址和token

    • URL為你的申請(qǐng)的URL+ /msg接口
    • token隨便寫一個(gè)字符串
7.png
  • 新增文件 utils.js 挫以,增加兩個(gè)工具方法(sha1驗(yàn)簽者蠕,隨機(jī)字符串)

    const { createHash } = require('crypto')
    // sha1校驗(yàn)
    module.exports.sha1 = word => {
      let hash = createHash('sha1')
      hash.update(word)
      return hash.digest('hex')
    }
    // 獲取隨機(jī)字符串
    module.exports.random = len => {
      let res = ''
      do {
          res += Math.random().toString(36).split('.')[1]
      } while (res.length < len)
      console.log(res)
      return res.substr(0, len)
    }
    
  • 新增config.js,將token掐松、appId踱侣、secret寫入

8.png
  • 新增controller/msg.js粪小,輸入:

    const { sha1 } = require('../utils')
    const { token } = require('../config')
    // 驗(yàn)證消息接口
    function verify (query) {
      let { signature, timestamp, nonce, echostr } = query
      let tempStr = [token, timestamp, nonce].sort().join('')
      let res = ''
      console.log(tempStr, sha1(tempStr), signature)
      if (sha1(tempStr) === signature) {
          res = echostr || true
      }
      return res
    }
    
    module.exports.token = (ctx, next) => {
      ctx.body = verify(ctx.query)
    }
    
  • app.js 引用msg,并將/msg 交給token處理

    const { token } = require('./controller/msg')
    
    ...
    
    router.get('/msg', token) // 驗(yàn)證微信接口地址
    
  • 點(diǎn)擊驗(yàn)證抡句,提示驗(yàn)證結(jié)果
9.png

配置接口信息完成8庠佟!玉转!

4.接收消息-接收普通消息

? 請(qǐng)先閱讀文檔【接收消息-接收普通消息】

? 此處使用依舊使用/msg接口,不過使用post方式接收

  • 修改/controller/msg.js殴蹄,新增msg方法究抓,用來處理接收到的消息

    module.exports.msg = (ctx, next) => {
      console.log(ctx.request.body)
      ctx.body = ''
    }
    
  • 修改app.js,使用koaBody中間件處理post信息

    const KoaBody = require('koa-body')
    const { token, msg } = require('./controller/msg')
    ...
    router.get('/msg', token) // 驗(yàn)證微信接口地址
    router.post('/msg', msg) // 接收微信接口地址
    
    app.use(KoaBody())
    ...
    
  • 掃碼關(guān)注測試公眾號(hào)袭灯,并發(fā)送一個(gè)文本消息

12.png
13.jpg

服務(wù)端收到xml消息

14.png
  • 使用xml2js格式化xml數(shù)據(jù)判斷接到的數(shù)據(jù)類型及內(nèi)容刺下。

    修改/controller/msg.js

    const { parseStringPromise } = require('xml2js')
    ...
    
    module.exports.msg = async (ctx, next) => {
      let res = ''
      if (verify(ctx.query)) {
          let data = await parseStringPromise(ctx.request.body)
          console.log(data)
          switch (data.xml.MsgType[0]) {
              case 'text':
                  console.log('msgType is text content is:', data.xml.Content[0])
                  break
              case 'image':
                  console.log('msgType is img')
                  break
          }
      }
      ctx.body = res
    }
    

    再次發(fā)送消息,后臺(tái)提示:

15.png

其他接收其他消息類型此處不再贅述了稽荧。自己根據(jù)文檔練習(xí)一下橘茉。

5.接收消息-接收事件推送

? 請(qǐng)先閱讀文檔【接收消息-接收事件推動(dòng)】

? 修改/controller/msg.js 增加對(duì)事件處理

...
case 'event':
    console.log('msgType is event', data.xml.Event[0] === 'subscribe' ? '我關(guān)注了' : '我取消關(guān)注了')
    break
...
16.png

6.發(fā)送消息-自動(dòng)回復(fù)用戶消息

? 請(qǐng)先閱讀開發(fā)文檔【發(fā)送消息-自動(dòng)回復(fù)】

  • 新增文件/libs/response.js ,增加兩個(gè)自動(dòng)回復(fù)的工具方法
module.exports = {
    text (opt) {
        return `<xml>
        <ToUserName><![CDATA[${opt.ToUserName}]]></ToUserName>
        <FromUserName><![CDATA[${opt.FromUserName}]]></FromUserName>
        <CreateTime>${~~(new Date / 1000)}</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[${opt.Content}]]></Content>
    </xml>`
    },
    image (opt) {
        return `<xml>
        <ToUserName><![CDATA[${opt.ToUserName}]]></ToUserName>
        <FromUserName><![CDATA[${opt.FromUserName}]]></FromUserName>
        <CreateTime>${~~(new Date / 1000)}</CreateTime>
        <MsgType><![CDATA[image]]></MsgType>
        <Image>
            <MediaId><![CDATA[${opt.MediaId}]]></MediaId>
        </Image>
    </xml>`
    }
}
  • 修改/controller/msg.js 增加自動(dòng)回復(fù)代碼
switch (data.xml.MsgType[0]) {
    case 'text':
        console.log('msgType is text content is:', data.xml.Content[0])
        res = text({
            FromUserName: data.xml.ToUserName[0],
            ToUserName: data.xml.FromUserName[0],
            Content: '回復(fù):' + data.xml.Content[0]
        })
        break
    case 'event':
        console.log('msgType is event', data.xml.Event[0] === 'subscribe' ? '我關(guān)注了' : '我取消關(guān)注了')
        if (data.xml.Event[0] === 'subscribe') {
            res = text({
                FromUserName: data.xml.ToUserName[0],
                ToUserName: data.xml.FromUserName[0],
                Content: '謝謝關(guān)注'
            })
        }
        break
}
17.jpg

7.發(fā)送消息-模板消息

? 請(qǐng)先閱讀文檔【發(fā)送消息-模板消息】

  • 新增一條模板消息內(nèi)容為【 {{User.DATA}}: 你好姨丈! 您的尾號(hào)為{{CardNumber.DATA}}的銀行卡于{{Date.DATA}}在中原大飯店{{Type.DATA}}人民幣{{Money.DATA}}畅卓,卡上余額{{Left.DATA}}】
18.png
  • 新增/wechat-api/template-message.js
const axios = require('axios')
const fs = require('fs')

function send () {
    let token = fs.readFileSync('./token.txt', 'utf8')
    let url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}`
    let data = {
        "touser": "ovmcWwEQR3fjMYQxkv0S1R2FBwpg",
        "template_id": "ps4BXtj4gqhfQb6V8RSppDPVoXV19G5V7dFnOeoBUJk",
        "url": "http://suohb.com",
        "topcolor": "#FF0000",
        "data": {
            "User": {
                "value": "黃先生",
                "color": "#173177"
            },
            "Date": {
                "value": "06月07日 19時(shí)24分",
                "color": "#173177"
            },
            "CardNumber": {
                "value": "0426",
                "color": "#173177"
            },
            "Type": {
                "value": "消費(fèi)",
                "color": "#173177"
            },
            "Money": {
                "value": "人民幣260.00元",
                "color": "#173177"
            },
            "DeadTime": {
                "value": "06月07日19時(shí)24分",
                "color": "#173177"
            },
            "Left": {
                "value": "6504.09",
                "color": "#173177"
            }
        }
    }
    axios({
        method: 'post',
        url,
        data
    }).then(res => {
        console.log(res.data)
    })
}
send()
  • 控制臺(tái)輸入
> node 3.template_message.js
19.png
20.jpg

其他模板消息接口,自己根據(jù)文檔練習(xí)一下蟋恬。

8.用戶管理

? 請(qǐng)先閱讀文檔【用戶管理】

  • 新增/wechat-api/2.user_manage.js
const axios = require('axios')
const fs = require('fs')

// 增加標(biāo)簽
function add_tag () {
    let token = fs.readFileSync('./token.txt', 'utf8')
    let url = `https://api.weixin.qq.com/cgi-bin/tags/create?access_token=${token}`
    let data = {
        "tag": {
            "name": "廣東"http://標(biāo)簽名
        }
    }
    axios({
        method: 'post',
        url,
        data
    }).then(res => {
        console.log(res.data)
    })
}
add_tag()
  • 控制臺(tái)輸入

    > node 4.user_manage.js
    
21.png
  • 修改/wechat-api/user-manage.js 【批量獲取用戶信息】
// 批量獲取用戶信息
function batchUserInfo () {
    let token = fs.readFileSync('./token.txt', 'utf8')
    let url = `https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=${token}`
    let data = {
        "user_list": [
            {
                "openid": "ovmcWwEQR3fjMYQxkv0S1R2FBwpg",
                "lang": "zh_CN"
            }
        ]
    }
    axios({
        method: 'post',
        url,
        data
    }).then(res => {
        console.log(res.data)
    })
}
batchUserInfo()
  • 控制臺(tái)輸入

    > node 4.user_manage.js
    
22.png

其他模板消息接口翁潘,自己根據(jù)文檔練習(xí)一下(github上有部分代碼)。

9.推廣支持-生成帶參數(shù)二維碼

? 請(qǐng)先閱讀文檔【推廣支持-生成帶參數(shù)二維碼】

? 帶參數(shù)的二維碼歼争,可以識(shí)別用戶關(guān)注的途徑拜马,針對(duì)性進(jìn)行處理

  • 新增/wechat-api/5.qrcode.js
const axios = require('axios')
const fs = require('fs')

function getTicket () {
    let token = fs.readFileSync('./token.txt', 'utf8')
    let url = `https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=${token}`
    let data = { "expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": { "scene": { "scene_id": 1 } } }
    axios({
        method: 'post',
        url,
        data
    }).then(res => {
        console.log(res.data)
        console.log('qrcodeUrl', `https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${res.data.ticket}`)
    })
}
getTicket()
  • 控制臺(tái)輸入
> node 5.qrcode.js
24.png
  • 先取消關(guān)注,然后使用手機(jī)訪問qrcodeUrl沐绒,關(guān)注信息有了EventKey字段
23.png

其他模板消息接口俩莽,自己根據(jù)文檔練習(xí)一下。

10.推廣支持-長鏈接轉(zhuǎn)短鏈接

? 請(qǐng)先閱讀文檔

  • 新增文件/wechat-api/6.short_url.js
const axios = require('axios')
const fs = require('fs')

function short () {
    let token = fs.readFileSync('./token.txt', 'utf8')
    let url = `https://api.weixin.qq.com/cgi-bin/shorturl?access_token=${token}`
    let data = {
        action: 'long2short',
        long_url: 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQFS8TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAySVRLVm9MLURjbTAxZGY1bU52MVMAAgTPig1fAwSAOgkA'
    }
    axios({
        method: 'post',
        url,
        data
    }).then(res => {
        console.log(res.data)
    })
}
short()
  • 控制臺(tái)輸入
> node 6.short_url.js
25.png

11.自定義菜單

  • 新增8.menu.js
const axios = require('axios')
const fs = require('fs')

function createMenu () {
    const token = fs.readFileSync('./token.txt')
    let url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${token}`
    let data = {
        "button": [
            {
                "type": "click",
                "name": "今日歌曲",
                "key": "V1001_TODAY_MUSIC"
            },
            {
                "name": "菜單",
                "sub_button": [
                    {
                        "type": "view",
                        "name": "搜索",
                        "url": "http://www.soso.com/"
                    },
                    {
                        "type": "click",
                        "name": "贊一下我們",
                        "key": "V1001_GOOD"
                    }]
            }]
    }
    axios({
        method: 'POST',
        url,
        data
    }).then(res => {
        console.log(res.data)
    })
}
createMenu()
  • 控制臺(tái)輸入
> node 8.menu.js
42.png
43.jpg

其他菜單接口乔遮,自己根據(jù)文檔練習(xí)一下扮超。

12.素材管理

  • 新增/wechat-api/10.material.js和img.jpg
const fs = require('fs')
const FormData = require('form-data')
// const axios = require("axios") // axios 上傳素材一直報(bào)錯(cuò),暫時(shí)沒有解決方式
// const request = require('request') // reqeust團(tuán)隊(duì)后期不在維護(hù)了蹋肮,所以此處也不使用
const superagent = require('superagent')


// 上傳臨時(shí)素材
function upload () {
    let token = fs.readFileSync('./token.txt', 'utf8')
    let url = `https://api.weixin.qq.com/cgi-bin/media/upload?access_token=${token}&type=image`

    superagent.post(url).attach('media', fs.createReadStream('./img.jpg')).then(res => {
        console.log(res.text)
        let data = JSON.parse(res.text)
        console.log(`https://api.weixin.qq.com/cgi-bin/media/get?access_token=${token}&media_id=${data.media_id}`)
    })
}
upload()
  • 控制臺(tái)執(zhí)行
> node 10.material.js
44.png
  • 手機(jī)訪問對(duì)應(yīng)素材
45.jpg

其他接口瞒津,自己根據(jù)文檔練習(xí)一下。

(三括尸、)網(wǎng)頁服務(wù)

1.網(wǎng)頁授權(quán)

? 首先閱讀相關(guān)文檔

1.1獲取code

  • 配置網(wǎng)頁授權(quán)回調(diào)域名
27.png
  • 新增/controller/web.js
module.exports.callback = (ctx, next) => {
    const { code } = ctx.query
    ctx.body = `callback page code is:${code}`
}
  • 修改app.js 新增callback路由
const { callback } = require('./controller/web')
...
router.get('/callback', callback) // 微信回調(diào)地址

  • 按規(guī)范配置一個(gè)微信重定向的地址(請(qǐng)使用自己申請(qǐng)的域名地址加上/callback和測試公眾號(hào)的appid)
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx222fa789edad09c5&redirect_uri=http%3A%2F%2Fsuohb.free.idcfengye.com%2Fcallback&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
26.png
  • 使用手機(jī)或微信開發(fā)者工具訪問
28.jpg

1.2使用code置換openId

  • 修改/controller/web.js
const axios = require('axios')
const { appId, secret } = require('../config')

module.exports.callback = async (ctx, next) => {
    const { code } = ctx.query
    let getOpenIdUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${secret}&code=${code}&grant_type=authorization_code`
    let res = await axios.get(getOpenIdUrl)
    ctx.body = `callback page res is:${JSON.stringify(res.data)}`
}
  • 使用手機(jī)或微信開發(fā)者工具訪問
29.jpg

1.3使用access_token置換用戶信息

? 注:此處的access_token跟基礎(chǔ)access_token不同巷蚪,屬于網(wǎng)頁使用的access_token

  • 修改/controller/web.js
...
let getUserInfoUrl = `https://api.weixin.qq.com/sns/userinfo?access_token=${res.data.access_token}&openid=${res.data.openid}&lang=zh_CN`
res = await axios.get(getUserInfoUrl)
...
30.jpg

1.4跳轉(zhuǎn)到前端頁面

  • 新增static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首頁</title>
</head>
<body>
    HOME_PAGE
</body>
</html>
  • 修改app.js 使用koa-static代理靜態(tài)資源路徑到static目錄
const KayStatic = require("koa-static")
...
app.use(KayStatic('./static'))
  • 修改/controller/web.js,獲取用戶信息之后重定向到前端頁面
...
ctx.response.redirect(`/index.html?openId=${res.data.openid}&nickname=${res.data.nickname}`)
...
  • 使用手機(jī)或微信開發(fā)者工具訪問
31.jpg
  • 頁面地址:
http://suohb.free.idcfengye.com/index.html?openId=ovmcWwEQR3fjMYQxkv0S1R2FBwpg&nickname=%E6%9C%A8%E5%85%AE

2.JS-SDK使用權(quán)限簽名算法

2.1獲取jssdk_ticket

? 請(qǐng)先閱讀相關(guān)文檔

  • 新增/wechat-api/7.jssdk_ticket.js濒翻,由于ticket跟token類型屁柏,所以也是本地存儲(chǔ)起來啦膜,使用定時(shí)任務(wù)來更新
const axios = require('axios')
const fs = require('fs')

function getTicket () {
    const token = fs.readFileSync('./token.txt')
    let url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=jsapi`
    axios({
        method: 'GET',
        url: url
    }).then(res => {
        console.log(res.data)
        if (!res.data.errcode) {
            fs.writeFileSync('./ticket.txt', res.data.ticket, 'utf8')
        }
    })
}
getTicket()
  • 控制臺(tái)輸入
> node 7.jssdk_ticket.js
32.png

2.2新增權(quán)限簽名接口

  • 配置js接口安全域名
34.png
  • 修改/controller/web.s
const axios = require('axios')
const { appId, secret } = require('../config')
const fs = require('fs')
const { sha1, random } = require('../utils')
...
module.exports.jsapi = (ctx, next) => {
    let { url } = ctx.request.body
    let jsapi_ticket = fs.readFileSync('./wechat-api/ticket.txt', 'utf8')
    let noncestr = random(16)
    let timestamp = + new Date()

    let data = {
        jsapi_ticket,
        noncestr,
        timestamp,
        url
    }
    let signature = Object.keys(data).sort().map(item => `${item}=${data[item]}`).join('&')
    signature = sha1(signature)

    ctx.body = { nonceStr: noncestr, timestamp, signature, appId }
}
  • 修改app.js 增加/jsapi接口
router.post('/jsapi', jsapi) // 獲取jsapi權(quán)限
  • 修改/static/index.html
<div id="log" style="word-break: break-all;">HOME_PAGE</div>
<script src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    function getSignature() {
        return axios({
            method: 'post',
            url: 'http://suohb.free.idcfengye.com/jsapi',
            data: {
                url: window.location.href.split('#')[0]
            }
        }).then(res => {
            document.querySelector("#log").innerHTML = JSON.stringify(res.data, '  ')
        })
    }

    getSignature()
</script>
33.png

3.config注入權(quán)限驗(yàn)證配置并判斷客戶端支持情況(wx.checkJsApi)

  • 修改/static/index.html
let jsApiList = ['updateAppMessageShareData', 'updateTimelineShareData', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone', 'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'pauseVoice', 'stopVoice', 'onVoicePlayEnd', 'uploadVoice', 'downloadVoice', 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'translateVoice', 'getNetworkType', 'openLocation', 'getLocation', 'hideOptionMenu', 'showOptionMenu', 'hideMenuItems', 'showMenuItems', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 'closeWindow', 'scanQRCode', 'chooseWXPay', 'openProductSpecificView', 'addCard', 'chooseCard', 'openCard']
...
function wxConfig(data) {
    return new Promise((resolve, reject) => {
        wx.config({
            ...data,
            jsApiList
        })
        wx.error(err => {
            reject(err)
        })
        wx.ready(() => {
            resolve()
        })
    })
}

getSignature().then(res => {
    return wxConfig(res)
}).then(() => {
    wx.checkJsApi({
        jsApiList, // 需要檢測的JS接口列表,所有JS接口列表見附錄2,
        success: function (res) {
            document.querySelector("#log").innerHTML = JSON.stringify(res)
        }
    });
})

4.分享接口

  • 修改index.html
//需在用戶可能點(diǎn)擊分享按鈕前就先調(diào)用
function share() {
    let shareData = {
        title: '分享的標(biāo)題',
        desc: '分享的描述呀L视鳌I摇!',
        link: window.location.href,
        imgUrl: `${window.location.origin}/images/share.png`
    }
    wx.updateAppMessageShareData({
        ...shareData,
        success: function () { }
    })
    wx.ready(function () {
        wx.updateTimelineShareData({
            ...shareData,
            success: function () { }
        })
    });
}

getSignature().then(res => {
    return wxConfig(res)
}).then(() => {
    ...
    share()
})
  • 在手機(jī)端訪問頁面裸删,并分享給好友
35.jpg

5.圖像接口-拍照或從手機(jī)選擇圖片

  • 修改/static/index.html
<button onclick="chooseImage()">chooseImage</button>
<div id="log" style="word-break: break-all;">HOME_PAGE</div>
<script>
...
    function chooseImage() {
        wx.chooseImage({
            count: 1, // 默認(rèn)9
            sizeType: ['original', 'compressed'], // 可以指定是原圖還是壓縮圖八拱,默認(rèn)二者都有
            sourceType: ['album', 'camera'], // 可以指定來源是相冊(cè)還是相機(jī),默認(rèn)二者都有
            success: function (res) {
                var localIds = res.localIds; // 返回選定照片的本地ID列表涯塔,localId可以作為img標(biāo)簽的src屬性顯示圖片
                document.querySelector("#log").innerHTML = `<img src='${localIds[0]}' style='width:90%;height:auto;'/>`
            }
        });
    }
</script>
  • 手機(jī)端訪問
36.jpg
37.jpg

其他圖片接口肌稻,請(qǐng)參照文檔自己練習(xí)

6.音頻接口

  • 修改/static/index.html
<button id="vioce" onclick="toogleRecord()">Record</button>
...
<script>
...
    let isInRecord = false
    function toogleRecord() {
        let logger = document.querySelector("#log")
        if (!isInRecord) {
            isInRecord = true
            wx.startRecord()
            logger.innerHTML = '開始錄音。匕荸。爹谭。'
        } else {
            isInRecord = false
            wx.stopRecord({
                success: function (res) {
                    let localId = res.localId;
                    logger.innerHTML = '錄音結(jié)束,開始播放榛搔。诺凡。。'
                    wx.playVoice({
                        localId: localId,
                        success: function () {
                            logger.innerHTML = '播放成功践惑!'
                        },
                        fail: function () {
                            logger.innerHTML = '播放失敻姑凇!'
                        }
                    });
                },
                fail: function () {
                    logger.innerHTML = '錄音失敗!!!'
                },
                cancel: function () {
                    logger.innerHTML = '取消錄音'
                }
            })
        }
    }
</script>
  • 手機(jī)訪問地址尔觉,點(diǎn)擊Record按鈕
38.jpg

其他音頻接口真屯,請(qǐng)參照文檔自己練習(xí)

7.獲取地理位置接口

  • 修改/static/index.html
<button onclick="getLocation()">getLocation</button>
...
<script>
    function getLocation() {
        wx.getLocation({
            type: 'wgs84', // 默認(rèn)為wgs84的gps坐標(biāo),如果要返回直接給openLocation用的火星坐標(biāo)穷娱,可傳入'gcj02'
            success: function (res) {
                var latitude = res.latitude; // 緯度绑蔫,浮點(diǎn)數(shù),范圍為90 ~ -90
                var longitude = res.longitude; // 經(jīng)度泵额,浮點(diǎn)數(shù)配深,范圍為180 ~ -180。
                var speed = res.speed; // 速度嫁盲,以米/每秒計(jì)
                var accuracy = res.accuracy; // 位置精度
                document.querySelector("#log").innerHTML = `${longitude},${latitude}`
            }
        });
    }
</script>
  • 手機(jī)訪問篓叶,點(diǎn)擊getLocation按鈕
39.jpg

8.關(guān)閉當(dāng)前網(wǎng)頁窗口接口

  • 修改/static/index.html
<button onclick="closeWindow()">closeWindow</button>
...
<script>
    function closeWindow() {
        wx.closeWindow()
    }
</script>
  • 點(diǎn)擊按鈕closeWindow 關(guān)閉當(dāng)前網(wǎng)頁

9.界面操作-隱藏顯示按鈕

  • 修改/static/index.html
<button onclick="hideMenu()">hideMenu</button>
<button onclick="showMenu()">showMenu</button>
...
<script>
    let menuList = ["menuItem:exposeArticle", "menuItem:setFont", "menuItem:dayMode", "menuItem:nightMode", "menuItem:refresh", "menuItem:profile", "menuItem:addContact", "menuItem:share:appMessage", "menuItem:share:timeline", "menuItem:share:qq", "menuItem:share:weiboApp", "menuItem:favorite", "menuItem:share:facebook", "menuItem:share:QZone", "menuItem:editTag", "menuItem:delete", "menuItem:copyUrl", "menuItem:originPage", "menuItem:readMode", "menuItem:openWithQQBrowser", "menuItem:openWithSafari", "menuItem:share:email", "menuItem:share:brand"]
    function hideMenu() {
        wx.hideAllNonBaseMenuItem()
        wx.hideMenuItems({
            menuList: menuList // 要隱藏的菜單項(xiàng),只能隱藏“傳播類”和“保護(hù)類”按鈕羞秤,所有menu項(xiàng)見附錄3
        })
    }
    function showMenu() {
        wx.showAllNonBaseMenuItem()
        wx.showMenuItems({
            menuList: menuList // 要顯示的菜單項(xiàng)缸托,所有menu項(xiàng)見附錄3
        })
    }
</script>
  • 切換showMenu和hideMenu按鈕
40.jpg
41.jpg

10.微信掃一掃

  • 修改/static/index.html
<button onclick="scanQRCode()">scanQRCode</button>
...
<script>
    function scanQRCode() {
        wx.scanQRCode({
            needResult: 1, // 默認(rèn)為0,掃描結(jié)果由微信處理瘾蛋,1則直接返回掃描結(jié)果俐镐,
            scanType: ["qrCode", "barCode"], // 可以指定掃二維碼還是一維碼,默認(rèn)二者都有
            success: function (res) {
                var result = res.resultStr; // 當(dāng)needResult 為 1 時(shí)哺哼,掃碼返回的結(jié)果
                document.querySelector("#log").innerHTML = result
            }
        })
    }
</script>

到這里佩抹,測試公眾號(hào)的基本功能已經(jīng)測試完畢叼风,其他相關(guān)開發(fā),以后在慢慢整理棍苹。

未完待續(xù)无宿。。枢里。

git倉庫地址:https://github.com/shb190802/wechat

都看到這里孽鸡,給作者點(diǎn)個(gè)贊吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末栏豺,一起剝皮案震驚了整個(gè)濱河市彬碱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冰悠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件配乱,死亡現(xiàn)場離奇詭異溉卓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)搬泥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門桑寨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忿檩,你說我怎么就攤上這事尉尾。” “怎么了燥透?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵沙咏,是天一觀的道長碉钠。 經(jīng)常有香客問我局扶,道長丐枉,這世上最難降的妖魔是什么余爆? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任陆蟆,我火速辦了婚禮瞬逊,結(jié)果婚禮上哎媚,老公的妹妹穿的比我還像新娘占遥。我一直安慰自己理盆,他們只是感情好痘煤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著猿规,像睡著了一般衷快。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姨俩,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天烦磁,我揣著相機(jī)與錄音养匈,去河邊找鬼。 笑死都伪,一個(gè)胖子當(dāng)著我的面吹牛呕乎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陨晶,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼猬仁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了先誉?” 一聲冷哼從身側(cè)響起湿刽,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褐耳,沒想到半個(gè)月后诈闺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铃芦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年雅镊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刃滓。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仁烹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咧虎,到底是詐尸還是另有隱情卓缰,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布砰诵,位于F島的核電站征唬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏茁彭。R本人自食惡果不足惜鳍鸵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尉间。 院中可真熱鬧偿乖,春花似錦、人聲如沸哲嘲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眠副。三九已至画切,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間囱怕,已是汗流浹背霍弹。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工毫别, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人典格。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓岛宦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耍缴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砾肺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354