前言
数据是无价的,我们通常会把重要的业务数据存放在数据库中,并需要对数据库做定时的自动备份工作,防止数据异常丢失,造成无法挽回的损失。
小程序云开发提供了方便的云数据库供我们直接使用,云开发使用了腾讯云提供的云数据库,拥有完善的数据保障机制,无需担心数据丢失。但是,我们还是不可避免的会担心数据库中数据的安全,比如不小心删除了数据集合,写入了脏数据等。
还好,云开发控制台提供了数据集合的导出,导入功能,我们可以手动备份数据库。不过,总是手动备份数据库也太麻烦了点,所有重复的事情都应该让代码去解决,下面我们就说说怎么搞定云开发数据库自动备份。
通过查阅微信的文档,可以发现云开发提供了数据导出接口databaseMigrateExport-[-/a>
POST https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN |
通过这个接口,结合云函数的定时触发功能,我们就可以做数据库定时自动备份了。梳理一下大致的流程:
1. 获取 access_token
调用微信的接口需要 access_token,所以我们首先要获取 access_token。通过文档了解到使用 auth.getAccessToken 接口可以用小程序的 appid 和 secret 获取 access_token。
// 获取 access_token
request.get(
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`,
(err, res, body) => {
if (err) {
// 处理错误
return;
}
const data = JSON.parse(body);
// data.access_token
}
);
|
request.post(
`https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=${accessToken}`,
{
body: JSON.stringify({
env,
file_path: `${date}.json`,
file_type: '1',
query: 'db.collection("data").get()'
})
},
(err, res, body) => {
if (err) {
// 处理错误
return;
}
const data = JSON.parse(body);
// data.job_id
}
);
|
request.post( `https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=${accessToken}`,
{
body: JSON.stringify({
env,
job_id: jobId
})
},
(err, res, body) => {
if (err) {
reject(err);
}
const data = JSON.parse(body);
// data.file_url
}
);
|
await db.collection('db_back_info').add({
data: {
date: new Date(),
jobId: job_id
}
});
|
{
"triggers": [
{
"name": "dbTrigger",
"type": "timer",
"config": "0 0 2 * * * *"
}
]
}
|
注意,云函数的默认超时时间是 3 秒,创建备份函数时,建议将超时时间设定到最大值 20S,留有足够的时间查询任务结果。
/* eslint-disable */
const request = require('request');
const cloud = require('wx-server-sdk');
// 环境变量
const env = 'xxxx';
cloud.init({
env
});
// 换取 access_token
async function getAccessToken(appid, secret) {
return new Promise((resolve, reject) => {
request.get(
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`,
(err, res, body) => {
if (err) {
reject(err);
return;
}
resolve(JSON.parse(body));
}
);
});
}
// 创建导出任务
async function createExportJob(accessToken, collection) {
const date = new Date().toISOString();
return new Promise((resolve, reject) => {
request.post(
`https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=${accessToken}`,
{
body: JSON.stringify({
env,
file_path: `${date}.json`,
file_type: '1',
query: `db.collection("${collection}").get()`
})
},
(err, res, body) => {
if (err) {
reject(err);
}
resolve(JSON.parse(body));
}
);
});
}
// 查询导出任务状态
async function waitJobFinished(accessToken, jobId) {
return new Promise((resolve, reject) => {
// 轮训任务状态
const timer = setInterval(() => {
request.post(
`https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=${accessToken}`,
{
body: JSON.stringify({
env,
job_id: jobId
})
},
(err, res, body) => {
if (err) {
reject(err);
}
const { status, file_url } = JSON.parse(body);
console.log('查询');
if (status === 'success') {
clearInterval(timer);
resolve(file_url);
}
}
);
}, 500);
});
}
exports.main = async (event, context) => {
// 从云函数环境变量中读取 appid 和 secret 以及数据集合
const { appid, secret, backupColl, backupInfoColl } = process.env;
const db = cloud.database();
try {
// 获取 access_token
const { errmsg, access_token } = await getAccessToken(appid, secret);
if (errmsg && errcode !== 0) {
throw new Error(`获取 access_token 失败:${errmsg}` || '获取 access_token 为空');
}
// 导出数据库
const { errmsg: jobErrMsg, errcode: jobErrCode, job_id } = await createExportJob(access_token, backupColl);
// 打印到日志中
console.log(job_id);
if (jobErrCode !== 0) {
throw new Error(`创建数据库备份任务失败:${jobErrMsg}`);
}
// 将任务数据存入数据库
const res = await db.collection('db_back_info').add({
data: {
date: new Date(),
jobId: job_id
}
});
// 等待任务完成
const fileUrl = await waitJobFinished(access_token, job_id);
console.log('导出成功', fileUrl);
// 存储到数据库
await db
.collection(backupInfoColl)
.doc(res._id)
.update({
data: {
fileUrl
}
});
} catch (e) {
throw new Error(`导出数据库异常:${e.message}`);
}
};
|