首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nodejs微信公众号开发

nodejs微信公众号开发

作者头像
一粒小麦
发布2019-08-06 16:58:50
6K4
发布2019-08-06 16:58:50
举报
文章被收录于专栏:一Li小麦一Li小麦

网上关于node开发公众号的资料相当缺乏,本文旨在以node的视角对公众号开发做一个阐述。

公众号简介

公众号类型

目前公众号主要分为三种:服务号、订阅号、小程序;还有企业微信只针对企业用户使⽤用,暂且不算在内。

微信公众平台:https://mp.weixin.qq.com/

名称

服务对象

业务类型

关注后的位置

服务号

企业

任意

联系⼈人列列表

订阅号

个⼈人或媒体

信息传播

归纳在订阅号

小程序

企业

任意

归纳在最近使⽤用

服务号:给企业提供用户管理与业务服务的能⼒,实现业务扩张。 订阅号:给个⼈或媒体提供信息传播的能⼒,与读者建⽴更好的沟通。 小程序:作⽤基本同服务号,比服务号H5应用体验更更好,但无法替代非H5的沟通系统,可以实现互补。

对于个⼈而⾔,无论是学习还是维护——个⼈公众号,只要不不涉及⽀支付环节,注册一个订阅号足以。如果需要⽀付功能,那么需要注册服务号,服务号注册时需要企业相关证书。
成为开发者

参考资料 微信开发者工具说明 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140 npm 库 wechat和wechat-api,以及微信开发者文档。

如果需要结合⾃身业务进⾏定制,那么就需要申请成为开发者,然后调用微信提供的api结合⾃身业务进⾏扩展。

  1. 首先在左侧菜单中找到:开发 => 基本配置
  2. 然后接受同意,成为开发者
  3. 获取开发者ID(AppID)与开发者密码(AppSecret),并妥善保存(很重要)
  4. 配置URL⽩白名单,把⾃自⼰己的服务器器IP填上去,保证只有指定的服务器器能获取到access_token(很重 要)
名词解释
  • AppID:公众应用唯⼀一身份认证
  • AppSecret:公众应⽤密码,需妥善保存
  • access_token:调⽤微信接⼝所需的凭证,每个接⼝调用都需要,可通过AppID和AppSecret获 取
  • URL白名单:增加获取accesstoken的安全性,当密码泄露时,通过白名单过滤⾮法请求(官方说明:https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&key=1495617578&version=1&lang=zhCN&platform=2)
开通测试账号

测试账号使用文档:https://blog.csdn.net/hzw2312/article/details/69664485

在进⾏公众号开发时,通常会先在测试账号中进⾏开发调试,经测试确认无误后,再把新功能切换到正式账号。
开通测试账号,将具有所有的权限!
  1. 后台管理左侧菜单 => 开发 => 开发者⼯工具
  1. 开发者⼯具⻚面 => 公众平台测试账号

2. 记录测试账号的appID与appsecret

3 . 测试账号的服务器配置

4. 测试账号的JS接口安全域名配置

5. 扫⼀扫关注⾃⼰的测试账号,然后会在用户列表⾥展现

6. 创建⼏个模板消息,供将来测试使⽤。

sunny-ngrok实现内网穿透

公众号开发时,总是面临各种上传文件服务器的操作,极其不便。而sunny-ngrok提供了内网穿透功能。它可以把你本机的ip发布到外网。

安装sunny-ngrok实现外网的映射:https://www.ngrok.cc/

注册,登录。

点击隧道管理理,打开"开通隧道"

编辑隧道信息-- 填入隧道名(随便填),前置域名(如www.yyy.baidu.com中的yyy,其实就是在该域名下开了一个前缀给你,因此只要写前缀就行了,选一个别⼈人没有⽤过的),本地映射的端⼝,则 是要和web项⽬目的http访问端⼝对应。

确认开通后回到隧道管理。就拿到了隧道id。

Mac

下载客户端。放到usr/local下。并在此打开命令行:

./sunny clientid d5324b15e9e99905 你的隧道id

看到这个就配置成功了。下面起一个node服务器来验证一下。

npm init
npm i koa koa-router koa-static koa-bodyparser -S
// 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);

然后写一个vue的页面:

<html>

<head>
    <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>

输入http://djtao.free.idcfengye.com/。访问成功:

客服消息接口

co-wechat实现消息收发业务

有一个不错的半官方的库 co-wechat:https://github.com/node-webot/co-wechat 把它安装了。

然后新建一个配置。

// config.js
module.exports={
    appid:测试号的appid,
    appsecret:测试号的appsecret,
    token:你自己定的token
}

然后根据co-wechat文档写一个接口:

const config=require('./config')

const wechat=require('co-wechat')

router.all('/wechat',wechat(config).middleware(
    async message=>{
        console.log('wecaht',message);
        return `hello world ${message.Content}`
    }
))

启动服务器。这时候可以配置测试号了。(服务不启动时,无法通过验证)

此时试一试发消息:

后台console的信息是:

原理

以上这个过程是怎么实现的呢?原理必然是重点。

这是服务器验证微信的过程

首先简单描述一下微信收发信息流程:

假设我们不需要co-WeChat这个库,自己写这个收发流程。可以是这样:

// index.js
const Koa = require('koa')
const Router = require('koa-router')
const static = require('koa-static') 
const xml2js = require('xml2js') // xml转化为json
const app = new Koa()
const url = require('url')
const conf = require('./conf')
const crypto = require('crypto') // 加密模块
const xmlParser = require('koa-xml-body') //解析xml数据

app.use(xmlParser())
const router = new Router()
app.use(static(__dirname + '/'))

// 验证
router.get('/wechat', ctx => {
        console.log('校验url', ctx.url) 
      const {query} = url.parse(ctx.url, true)
    const {
            signature, // 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
            timestamp, // 时间戳
      nonce, // 随机数
      echostr // 随机字符串
    } = query
    console.log('wechat', query)

        // 将 token timestamp nonce 三个参数进行字典序排序并用sha1加密
    let str = [conf.token, timestamp, nonce].sort().join('');
    console.log('str',str)
    let strSha1 = crypto.createHash('sha1').update(str).digest('hex');
        console.log(`自己加密后的字符串为:${strSha1}`); 
      console.log(`微信传入的加密字符为:${signature}`); 
      console.log(`两者比较结果为:${signature == strSha1}`);

        // 签名对比,相同则按照微信要求返回echostr参数值
      if (signature == strSha1) {
        ctx.body = echostr
    } else {
            ctx.body = "你不是微信" }
        }
)

// 接受信息
router.post('/wechat', ctx => {
    const {xml: msg} = ctx.request.body
    console.log('Receive:', msg)
    const builder = new xml2js.Builder()
    const result = builder.buildObject({
        xml: {
            ToUserName: msg.FromUserName,
            FromUserName: msg.ToUserName,
            CreateTime: Date.now(),
            MsgType: msg.MsgType,
            Content: 'Hello ' + msg.Content
                } 
    })
    ctx.body = result
})

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
接受消息

首先是微信向服务器发送get请求。

微信发出GET请求通常包括4个常见字段。

参数

描述

signature

加密签名,包括token、timestamp和nonce加密混成

timestamp

时间戳

nonce

随机数n+once

echostr

随机字符串

而微信发送消息,请求除了带上token、timestamp和nonce,还会带上一个xml数据包。

  • 对token/timestamp/nonce进行字典排序
  • 对排序完之后的字段拼接,sha1加密
  • 以加密结果对比signature,二者相等则通过校验
发送消息
  • 消息解析为字符串,获取XML数据(接收方,发送方,内容等)。
  • 构造发回的xml数据包(注意原来的发送方变为接收方)
  • 通过片echo把构造好的数据发出去!
SHA1算法

安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准 (Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息, SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。SHA1有如下特性: 不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要,(但会有1x10 ^ 48分之一的机 率出现相同的消息摘要,一般使用时忽略)。

哈希: 不可变长 -> 摘要固定长度

  • 摘要
  • 雪崩效应:密文变化后看不出来。
  • 类似MD5 SHA256

官方api调用

实际上微信不仅是收发消息那么简单。

官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183

为了简化操作,你可以调用一个库,co-wechat的好基友—— co-wechat-api

https://github.com/node-webot/co-wechat-api

!image-20190804010154636

acess_token

accesstoken是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用accesstoken。开发者需要进行妥善保存。accesstoken的存储至少要保留512个字符空间。accesstoken的有效期目前为2个小时(7200s),需定时刷新,重复获取将导致上次获取的access_token失效。

公众平台的API调用所需的access_token的使用及生成方式说明:

1、建议公众号开发者使用中控服务器统一获取和刷新accesstoken,其他业务逻辑服务器所使用的accesstoken均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务;

2、目前accesstoken的有效期通过返回的expirein来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新accesstoken。在刷新过程中,中控服务器可对外继续输出的老accesstoken,此时公众平台后台会保证在5分钟内,新老access_token都可用,这保证了第三方业务的平滑过渡;

3、accesstoken的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新accesstoken的接口,这样便于业务服务器在API调用获知accesstoken已超时的情况下,可以触发accesstoken的刷新流程。

公众号和小程序均可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。小程序无需配置IP白名单。

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

需要在后端引入axios,获取token则应该这么写:

router.get('/getTokens',async (ctx)=>{
    const APPID=config.appid;

    const APPSECRET=config.appsecret;

    const api=`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
    const res = await axios.get(api);
    console.log(res);
    Object.assign(tokenCache, res.data, {
        updateTime: Date.now()
    });
    ctx.body = res.data
})

前端调用这个接口,得到:

因为我经常要用,所以要写一个获取方法:

const getTokens=async function(){
    if(!tokenCache.access_token||Date.now()-7200*1000>tokenCache.updateTime){
        // console.log(222,Date.now()-7200*1000,tokenCache.updateTime)
        const APPID = config.appid;
        const APPSECRET = config.appsecret;
        const api = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
        const res = await axios.get(api);
        return Object.assign(tokenCache, res.data, {
            updateTime: Date.now()
        })
    }else{

        return tokenCache;
    } 
}

实现关注用户列表

// 获取关注者列表
router.get('/getFollowers', async ctx => {
    await getTokens();
    const api = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${tokenCache.access_token}`
    const res = await axios.get(api)
    ctx.body = res.data
})

api接口于api调用,是微信验证我们的服务器。

Co-wechat工具库下更多api的调用

我想在index.html实现以下功能:

-  实际工作中,通常是用库来实现的。比如用户:
const WechatAPI = require('co-wechat-api');
const api = new WechatAPI(conf.appid, conf.appsecret);

router.get('/getFollowers', async ctx => {
    let res = await api.getFollowers();
    // 批量获取用户信息
    let _res=await api.batchGetUsers(res.data.openid);
    ctx.body = _res
})

全局票据的管理

按照上文的api,token是放到服务器运行内存里的。

获取accesstoken的次数是2000次。每次都调用时不现实的。而且在负载均衡情况下,accesstoken是放node1还是node2呢?

答案是放数据库里。

以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
const {ServerToken}=require('./mongoose')
const api = new WechatAPI(config.appid,
    config.appsecret,
    async function () {
        return await ServerToken.findOne()
    },
    async function (token) {
        const res = await ServerToken.updateOne({}, token, { upsert: true }) //允许覆盖
    }
)

在new出 WechatAPI实例的时候,实际上提供了第三第四个参数。第一个用于存token,第四个用于存放。

案例:微信在线投票

后端

先装依赖:

npm i koa koa-router koa-static koa-socket co-wechat -s
// index.js

const Koa = require('koa')
const Router = require('koa-router')

const app = new Koa()
const router = new Router()

// 静态文件
const static = require('koa-static')
app.use(static(__dirname + '/'))
const conf = require('./conf')
const wechat = require('co-wechat')

// socket.io
const IO = require('koa-socket')
const io = new IO()
io.attach(app)
app._io.on('connection',socket => {
    console.log('socket connection..')
})

// 消息接口
router.all('/wechat', wechat(conf).middleware(
    async (message, ctx) => {
        console.log('wechart', message)
        app._io.emit('chat',message)
        return '收到!';
    }
))

app.use(router.routes()); 
/*启动路由*/
app.use(router.allowedMethods());
app.listen(3000);

通过socket.io监听收发消息。

比较简单没什么可说的。

前端

前端通过vue和echarts展现数据

<div id="app">
    <div id="chart" style="width:100%;height:50%;"></div>
    <cube-button @click="reset">重置</cube-button>
    <div class="view-wrapper">
      <div class="list" v-for="item in list.slice(0,5)">
        <div class="item">
          <div class="avatar"></div>
          <div class="bubble">
            <p>{{ item.Content }}</p>
          </div>
        </div>
      </div>
    </div>
  </div>
var app = new Vue({
      el: "#app",
      data: {
        list: []
      },
      watch: {
        list: {
          handler(newName) {
            this.renderChart();
          },
          immediate: true,
          deep: true
        }
      },
      methods: {
        initChart() {
          const option = {
            series: [{
              type: "pie",
              selectedMode: "single",
              radius: [0, "70%"],
              label: {
                normal: {
                  position: "inner"
                }
              },
              labelLine: {
                normal: {
                  show: false
                }
              }
            }]
          };
          this.chart = echarts.init(document.getElementById("chart"));
          this.chart.setOption(option);
        },
        // 刷新图表
        renderChart(newName) {
          const data = ["1", "2"]
            .map(key => ({
              name: key,
              value: this.list.filter(v => v.Content === key).length
            }))
            .filter(v => v.value !== 0);
          const option = {
            series: [{
              data
            }]
          };
          this.chart ? this.chart.setOption(option) : "";
        },
        // 重置
        reset() {
          this.list = [];
        }
      },
      mounted() {
        this.initChart();
        const socket = io()
        socket.on('chat', msg => {
          console.log('chart ...', msg)
          this.list.unshift(msg)
        })
      }
    });

效果如下


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 公众号简介
    • 公众号类型
      • 成为开发者
        • 名词解释
          • 开通测试账号
          • sunny-ngrok实现内网穿透
            • Mac
            • 客服消息接口
              • co-wechat实现消息收发业务
                • 原理
                  • 接受消息
                  • 发送消息
                  • SHA1算法
              • 官方api调用
                • acess_token
                  • Co-wechat工具库下更多api的调用
                  • 全局票据的管理
                  • 案例:微信在线投票
                    • 后端
                      • 前端
                      相关产品与服务
                      访问管理
                      访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档