之前的基于ghost的博客小程序,由于服务端快到期了,所以想将数据源切到mini-blog上来。
背景
经常看我文章的知道,我有两个博客小程序(程序员的博客
和我si程序员
)。前者基于开源博客框架ghost。
由于服务器想另做他用,所以打算将程序员的博客
的数据源也基于公众号的文章。当然,最简单的方式还是按照mini-blog
的部署方式再部署一套。
但再部署一套的缺点就是数据无法打通,文章也就罢了,浏览量,评论数据这些用户行为就相对独立了,这不是我想要的。
于是,利用云开发的HTTP API,来实现跨小程序访问同一个云资源的功能。
云开发 HTTP API
关于云开发 HTTP API的使用,这里就不再多说了,官方的文档写的比较详细了。
之前也有写过一篇利用python操作小程序云数据库实现简单的增删改查,可以参考。
具体改造内容
首先评估下需要改造的点,简单来说原来获取数据源的地方都需要修改,原本是通过本身的云开发API获取本身的数据,而现在相当于是要通过Http请求来通过外网的方式请求数据源。
值得庆幸的是,当初在写mini-blog
时,将获取数据源的地方统一收口在api.js
中了。这样理论上只需要修改这一个文件的实现,就可以轻松达到目的了。
解决AccessToken问题
一开始觉得挺容易,可刚准备动手就遇到了一个难题,想要使用云开发 HTTP API,首先得获取调用凭证(AccessToken),而要获取调用凭证就需要AppId
和SecretId
,显然这两个数据比较敏感,放在小程序端是比较危险的。
于是想到,获取AccessToken
的动作还是封装在云函数中。但随之而来的另外一个问题就是AccessToken
的值存储在哪。
显然没办法放在云资源端(死循环了),于是只能考虑第三方了,我这里使用了bmob后端云
。
利用小程序云函数,创建一个同步AccessToken的定时任务,每一小时同步一次token值到bmob后端云
中,用来供外部访问,核心代码如下:
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调用云函数,具体代码如下:
/**
* 查询云数据库
*/
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)
}
然后就可以将原本直接调用云资源的方法重新实现了,拿获取文章列表举例,获取文章列表是通过直接查云数据库实现的,改造后的代码如下:
/**
* 获取文章列表
* @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的返回体都不一样(可能不是一波人写的,但好歹一个大团队,不能规范下嘛)
于是在成功获取完数据之后,为了不动到页面的代码,将返回结果再构造成之前的样子:
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。
openId: event.openId == undefined ? event.userInfo.openId : event.openId
总结
绕了一圈把功能实现了,也算对小程序、云开发又有了新的认识吧。
同时,代码一些细节挺重要的,可能会直接影响到后续迭代的工作量。比如调用数据的方法收口,如果当初是散落在各个页面的,那这改造的工作量就大了很多。
最后,保持统一的输入输出规范也很重要,统一的标准不管是提供方还是接入方,都会事半功倍。
有了这个经验和实现,下一步就要把数据搬到QQ小程序上了,这个改造应该也不大,后面实现了再分享给大家。