前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >独立开发微信公众号服务的一次复盘

独立开发微信公众号服务的一次复盘

作者头像
terrence386
发布2022-07-14 21:12:24
3950
发布2022-07-14 21:12:24
举报

争与不争其实无关紧要,因为有句话说的好,功到自然成。

前情回顾

上篇文章主要分享了异步编程的一些经验。主要包括回调函数发布订阅Promise,async await以及yield关键字。

今天忍不住要分享一下,公众号的开发经历。主要是实现了自动回复以及获取素材列表两个简单的功能。实现这两个功能以后,其实对于微信公众平台上的其他功能基本上就可以自由接入了。因为基本都是同样的流程。

而之所以要整理这么一个东西,是因为以往的开发过程中,往往在类似的场景中,前端对后端的依赖太重,所有的接口都要等后端去完成,如果前端人员基于Node有一套自己的服务,那么类似的场景岂不是可以自由发挥了?

所以,大致牺牲了一个周的晚上时间,又仔细读了一遍微信公众平台的开发文档,找了些教程,将它的开发流程用Node走了一遍。

开发前准备

在正式开发前需要将文档大致浏览一遍。需要了解accecc_token,消息回复的流程及微信定义的各种消息格式。同时需要申请一个测试账号,以及ngrock用来进行内网穿透,方便我们在本地调试。

  • access_token相关
    • 两个小时刷新
    • 更新后 原有的token失效
    • 有效期 7200s
  • 自动回复消息 五个步骤
    • 处理POST类型控制逻辑,接受xml数据包
    • 解析数据包 消息类型 或 事件类型
    • 拼装定义好的消息
    • 包装成 XML格式
    • 5s 内返回回去
  • 消息格式
代码语言:javascript
复制
  <xml><ToUserName><![CDATA[gh_2b36e771f7c0]]></ToUserName>
  <FromUserName><![CDATA[oyT495_dXqAYqtimOcxfNmtoENpA]]></FromUserName>
  <CreateTime>1616689961</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[好]]></Content>
  <MsgId>23145433557740763</MsgId>
  </xml>
  • ngrock 命令
代码语言:javascript
复制
# 3001 为本地端口号
lt --port  3001

开发流程

借用公众号开发文档的图片

开发流程

当我们将服务启动,并将服务地址配置到公众号后台的服务器配置中时,服务端会接受到一个来自微信后台的get请求,这个请求会带上这几个参数signature,echostr,nonce,token,timestamp,然后我们将用nonce,token,timestamp这三个参数,按照先排序,后加密的流程得到的字符串跟signature进行比较,如果相同则表名是从微信后台进来的,然后将echostr返给微信后台,我们就可以进行其他业务开发了。

具体的验证逻辑:

代码语言:javascript
复制
var sha1 = require('sha1')
var getRawBody  = require('raw-body')
var Wechat = require('./wechat')
var util = require('./util')

module.exports = function (opts,handler) {
  console.log('handler--',handler)
  let wechat  = new Wechat(opts)
  return function *(next){
    // 验证逻辑
    var token = opts.token
    var signature = this.query.signature
    var nonce = this.query.nonce
    var echostr = this.query.echostr
    var timestamp = this.query.timestamp
    var str = [token,timestamp,nonce].sort().join('')
    var sha = sha1(str)
    console.log('opts',opts)
    if(this.method == 'GET'){
      if(sha === signature){
        this.body = echostr + ''
      }
      else {
        this.body = 'wrong'
      }
    }
    else if(this.method == 'POST'){
      if(sha !== signature){
        this.body = 'wrong' 
        return false
      }
      var data = yield getRawBody(this.req,{
        length:this.length,
        limit:'1mb',
        encoding:this.charset
      })
      console.log('data',data.toString())

      // util 包解析xml对象
      let content = yield util.parseXMLAsync(data)
      console.log('content',content)
      let message = yield util.formatMessage(content.xml)
      console.log('message',message)
      this.weixin = message
      // 改变执行上下文
      yield handler.call(this,next)
      // 执行回复逻辑
      wechat.reply.call(this)
    }
  }
}

access_token 更新策略

因为我们在调用对应的接口时,都需要用到access_token,所以需要对获取access_token的方法进行封装,并且要能检测它是否有效,并且能在无效时进行更新。

代码语言:javascript
复制
// 更新票据
Wechat.prototype.updateAccessToken = function(){
  
  var appID = this.appID
  var appSecret = this.appSecret
  var url = api.accessToken + `&appid=`+appID +'&secret='+appSecret

  return new Promise((resolve,reject)=>{

    request({method:'GET',url:url,json:true,}).then((res)=>{
      var data = res.body || {}
      var now = (new Date().getTime())
      var expires_in = now + (data.expires_in-20)*1000
  
      data.expires_in = expires_in
      resolve(data)
    })
  })
}
// 获取token
Wechat.prototype.fetchAccessToken = function(){
  let self = this
  console.log('fetchAccessToken--->>this',this)
  if(this.access_token && this.expires_in){
    if(this.isValidAccessToken(this)){
      return Promise.resolve(this)
    }
  }
  this.getAccessToken()
  .then(function(data){
    console('getAccessToken--》》data',data)

    try{
      data = JSON.parse(data)
      console('getAccessToken--》》data',data)
      // return
    }
    catch(e){
      return self.updateAccessToken(data)
    }

    if(self.isValidAccessToken(data)){
      return Promise.resolve(data)
    }
    else{
      return self.updateAccessToken()
    }
  })
  .then(function(data){
    self.access_token = data.access_token
    self.expires_in =  data.expires_in

    self.saveAccessToken(data)

    return Promise.resolve(data)
  })
} 

消息自动回复

消息自动回复的痛点在于需要对不同的消息格式进行解析,然后按照对应的格式生成对应的回复消息。

代码语言:javascript
复制

function compile (ToUserName,FromUserName,MsgType,Content){
  let timeStamp  = new Date().getTime()
  console.log('MsgType====>',MsgType)
  console.log('Content==>',Content)
  if(MsgType === 'text'){
    return `<xml>
      <ToUserName><![CDATA[${FromUserName}]]></ToUserName>
      <FromUserName><![CDATA[${ToUserName}]]></FromUserName>
      <CreateTime>${timeStamp}</CreateTime>
      <MsgType><![CDATA[${MsgType}]]></MsgType>
      <Content><![CDATA[${Content}]]></Content>
    </xml>`
  }else if(MsgType === 'image'){
    return `<xml>
      <ToUserName><![CDATA[${FromUserName}]]></ToUserName>
      <FromUserName><![CDATA[${ToUserName}]]></FromUserName>
      <CreateTime>${timeStamp}</CreateTime>
      <MsgType><![CDATA[${MsgType}]]></MsgType>
      <PicUrl><![CDATA[${picUrl}]]></PicUrl>
    </xml>`
  }else if(MsgType === 'news'){
    return `<xml>
      <ToUserName><![CDATA[${FromUserName}]]></ToUserName>
      <FromUserName><![CDATA[${ToUserName}]]></FromUserName>
      <CreateTime>${timeStamp}</CreateTime>
      <MsgType><![CDATA[${MsgType}]]></MsgType>
      <ArticleCount>${Content.length}</ArticleCount>
      <Articles>
      <item>
          <Title><![CDATA[${Content[0].title}]]></Title>
          <Description><![CDATA[${Content[0].description}]]></Description>
          <PicUrl><![CDATA[${Content[0].picUrl}]]></PicUrl>
          <Url><![CDATA[${Content[0].url}]]></Url>
        </item>
      </Articles>
    </xml>`
  }else if(MsgType === 'voice'){
    return `<xml>
      <ToUserName><![CDATA[${FromUserName}]]></ToUserName>
      <FromUserName><![CDATA[${ToUserName}]]></FromUserName>
      <CreateTime>${timeStamp}</CreateTime>
      <MsgType><![CDATA[${MsgType}]]></MsgType>
      <Voice>
        <MediaId><![CDATA[${Content.MediaId}]]></MediaId>
      </Voice>
    </xml>`
  }
}
exports.compile = compile 

接入素材管理功能

接入素材管理等一系列的功能,如果需要接入的功能比较多,为了方便还是进行了简单的配置化。

代码语言:javascript
复制
var api = {
  accessToken:prefix+'token?grant_type=client_credential',
  // 临时素材
  temporary:{
    upload:prefix+'media/upload?'
  },
  // 永久素材
  permanent:{
    upload:prefix+'material/add_material?',
    uploadNews:prefix+'material/add_news?',
    uploadNewsPic:prefix+'media/uploadimg',
    getMeterialList:prefix+'material/batchget_material?'
  }
}

然后在实例上添加了对应的方法:

代码语言:javascript
复制
Wechat.prototype.getMaterial = function(type,offset,count){
  let self = this
  let form = {
    "type":type,
    "offset":offset,
    "count":count
  }
  let materialUrl = api.permanent.getMeterialList
  return new Promise((resolve,reject)=>{
    self.fetchAccessToken()
    .then((data)=>{
      console.log('access_token==>',data)
          let url = `${materialUrl}access_token=${data.access_token}`
          request({method:'POST',url:url,body:form,json:true,}).then((res)=>{
            console.log('materialList---》',res.body)
            let _data = res.body || {}
            if(_data){
              resolve(_data)
            }
            else{
              throw new Error('获取素材失败~')
            }
          })
        }).catch(err=>{
          reject(err)
        })
  })
}

总体上遇到的问题

  • 变量拼写错误。
  • this的指向问题。有个地方用call,切换了上下文。
  • 配置JSapi域名回调时,域名已备案但仍然提示未备案。这个需要在微信的论坛里直接找运营让他们确认域名备案结果即可。

其他问题

开发完成,测试无误后,将代码上传至服务器,用pm2,或者forever运行起来。然后nginx将端口代理至80即可。

个人公众号不知道从什么时候开始不支持个人认证了,所以有些权限个人无法使用,比如微信网页开发,因为根本没有网页授权域名,即使配置了js安全接口域名,个人仍然无法进行微信网页开发。

包括一些重要的功能比如支付等,都无法接入。

总结

总体上开发的困难程度并不高,但是需要有合理的逻辑,适时地将一些个功能拆分出来,否则将会有很多if else的判断。

最后,代码在https://gitee.com/mynoe/public_code.git这个仓库中,有兴趣的可以了解一下

javascript基础知识总结

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

本文分享自 JavaScript高级程序设计 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前情回顾
  • 开发前准备
  • 开发流程
  • access_token 更新策略
  • 消息自动回复
  • 接入素材管理功能
  • 总体上遇到的问题
  • 其他问题
  • 总结
相关产品与服务
ICP备案
在中华人民共和国境内从事互联网信息服务的网站或APP主办者,应当依法履行备案手续。腾讯云为您提供高效便捷的 ICP 备案服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档