巧用云开发,实现多个小程序访问同一个云数据库

2019-09-09 17:35:46 浏览数 (1)

之前的基于ghost的博客小程序,由于服务端快到期了,所以想将数据源切到mini-blog上来。

背景

经常看我文章的知道,我有两个博客小程序(程序员的博客我si程序员)。前者基于开源博客框架ghost。

由于服务器想另做他用,所以打算将程序员的博客的数据源也基于公众号的文章。当然,最简单的方式还是按照mini-blog的部署方式再部署一套。

但再部署一套的缺点就是数据无法打通,文章也就罢了,浏览量,评论数据这些用户行为就相对独立了,这不是我想要的。

于是,利用云开发的HTTP API,来实现跨小程序访问同一个云资源的功能。

云开发 HTTP API

关于云开发 HTTP API的使用,这里就不再多说了,官方的文档写的比较详细了。

之前也有写过一篇利用python操作小程序云数据库实现简单的增删改查,可以参考。

具体改造内容

首先评估下需要改造的点,简单来说原来获取数据源的地方都需要修改,原本是通过本身的云开发API获取本身的数据,而现在相当于是要通过Http请求来通过外网的方式请求数据源。

值得庆幸的是,当初在写mini-blog时,将获取数据源的地方统一收口在api.js中了。这样理论上只需要修改这一个文件的实现,就可以轻松达到目的了。

解决AccessToken问题

一开始觉得挺容易,可刚准备动手就遇到了一个难题,想要使用云开发 HTTP API,首先得获取调用凭证(AccessToken),而要获取调用凭证就需要AppIdSecretId,显然这两个数据比较敏感,放在小程序端是比较危险的。

于是想到,获取AccessToken的动作还是封装在云函数中。但随之而来的另外一个问题就是AccessToken的值存储在哪。

显然没办法放在云资源端(死循环了),于是只能考虑第三方了,我这里使用了bmob后端云

利用小程序云函数,创建一个同步AccessToken的定时任务,每一小时同步一次token值到bmob后端云中,用来供外部访问,核心代码如下:

代码语言:javascript复制
async function postTokenToBmob(token) {
  var options = {
    method: 'PUT',
    uri: `https://api2.bmob.cn/1/classes/token/X2RgBBBO`,
    body: {
      accessToken: token
    },
    headers: {
      'User-Agent': 'Request-Promise',
      'X-Bmob-Application-Id': BMOBKEY,
      'X-Bmob-REST-API-Key': BMOBPWD
    },
    json: true
  };
  let result = await rp(options)
  console.info(result);
}

重写api.js

解决了token问题,就可以根据官方文档来编写具体实现了,首先编写两个公共方法,一个通过HTTP API调用云数据库,一个通过HTTP API调用云函数,具体代码如下:

代码语言:javascript复制
/**
 *  查询云数据库
 */
const queryData = async function(query) {
  let token = await getAccessToken()
  var url = `${WECHAT_URL}/tcb/databasequery?access_token=${token}`

  var options = {
    method: 'POST',
    uri: url,
    body: {
      "env": ENV,
      "query": query
    },
    json: true
  };
  return await rp(options)
}

/**
 * 调用云函数
 */
const postAction = async function(data) {
  let token = await getAccessToken()
  let FUNCTION_NAME = "postsService"
  var url = `${WECHAT_URL}/tcb/invokecloudfunction?access_token=${token}&env=${ENV}&name=${FUNCTION_NAME}`
  var options = {
    method: 'POST',
    uri: url,
    body: data,
    json: true
  };
  return await rp(options)
}

然后就可以将原本直接调用云资源的方法重新实现了,拿获取文章列表举例,获取文章列表是通过直接查云数据库实现的,改造后的代码如下:

代码语言:javascript复制
/**
 * 获取文章列表
 * @param {} page 
 */
const getPostsList = async function(page, filter, isShow, orderBy, label) {
  let where = {}
  let strWhere = ""
  if (filter !== '') {
    strWhere = `title:db.RegExp({regexp:'${filter}',options: 'i',}),`
  }
  if (isShow !== -1) {
    strWhere = strWhere   "isShow:1,"
  }

  if (orderBy == undefined || orderBy == "") {
    orderBy = "createTime"
  }

  if (label != undefined && label != "") {
    strWhere = strWhere   `label:db.RegExp({regexp:'${label}',options: 'i',}),`
  }

  page = (page - 1) * 10

  let query = `db.collection("mini_posts")
  .where({${strWhere}})
  .orderBy("${orderBy}", "desc")
  .skip(${page})
  .limit(10)
  .get()`

  console.info(query)
  let res = await queryData(query)
  return res.data
}

这里有个比较坑的地方是where条件,原本通过对象转成字符串来构造的,但发现构造出来的字符串会有引号,类似{"isShow":1}这样,但实际调用接口会提示语法错误,后来发现在构造查询语句时要的是类似{isShow:1}这样,不带引号的。

但奇葩的是,排序的变量又是需要引号的,类似.orderBy("createTime", "desc")这样。真的要被腾讯玩坏了。

而调用云函数就比较简单了,传个云函数名称和对应的参数就行了,就不贴代码了。

重写api.js之后的坑

原本以为大功告成了,结果是我想多了。

不得不吐槽下云开发的返回体的定义,没有一个标准,云数据库、云函数、HTTP API的返回体都不一样(可能不是一波人写的,但好歹一个大团队,不能规范下嘛)

于是在成功获取完数据之后,为了不动到页面的代码,将返回结果再构造成之前的样子:

代码语言:javascript复制
function buildDataResult(res) {
    let result = {}
    let jsonData = []
    if (res != null) {
        for (let i = 0, len = res.result.length; i < len; i  ) {
            jsonData.push(JSON.parse(res.result[i]))
        }
    }
    result.data = jsonData
    return result
}

到这里,大多数页面都已经可以正常展示了,还差几个功能按钮了。

评论、收藏、点赞的按钮,这里有点小坑,openId的问题,原先是直接在云函数端获取用户的openId去保存的。

但通过Http访问云资源端就需要自己传了,需要重写下原来的云函数,优先取传入的openId。

代码语言:javascript复制
openId: event.openId == undefined ? event.userInfo.openId : event.openId

总结

绕了一圈把功能实现了,也算对小程序、云开发又有了新的认识吧。

同时,代码一些细节挺重要的,可能会直接影响到后续迭代的工作量。比如调用数据的方法收口,如果当初是散落在各个页面的,那这改造的工作量就大了很多。

最后,保持统一的输入输出规范也很重要,统一的标准不管是提供方还是接入方,都会事半功倍。

有了这个经验和实现,下一步就要把数据搬到QQ小程序上了,这个改造应该也不大,后面实现了再分享给大家。

0 人点赞