小程序·云开发之数据库自动备份丨云开发101

小程序云开发之数据库自动备份

数据是无价的,咱们一般会把重要的业务数据存放在数据库中,并须要对数据库作定时的自动备份工做,防止数据异常丢失,形成没法挽回的损失。html

小程序云开发提供了方便的云数据库供咱们直接使用,云开发使用了腾讯云提供的云数据库,拥有完善的数据保障机制,无需担忧数据丢失。可是,咱们仍是不可避免的会担忧数据库中数据的安全,好比不当心删除了数据集合,写入了脏数据等。shell

还好,云开发控制台提供了数据集合的导出,导入功能,咱们能够手动备份数据库。不过,老是手动备份数据库也太麻烦了点,全部重复的事情都应该让代码去解决,下面咱们就说说怎么搞定云开发数据库自动备份。数据库

经过查阅微信的文档,能够发现云开发提供了数据导出接口databaseMigrateExportjson

POST https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN

经过这个接口,结合云函数的定时触发功能,咱们就能够作数据库定时自动备份了。梳理一下大体的流程:小程序

  1. 建立一个定时触发的云函数
  2. 云函数调用接口,导出数据库备份文件
  3. 将备份文件上传到云存储中以供使用

1. 获取 access_token

调用微信的接口须要 access_token,因此咱们首先要获取 access_token。经过文档了解到使用 auth.getAccessToken 接口能够用小程序的 appid 和 secret 获取 access_token。api

// 获取 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
    }
);

2. 建立数据库导出任务

获取 access_token 后,就可使用 databaseMigrateExport 接口导出数据进行备份。安全

databaseMigrateExport 接口会建立一个数据库导出任务,并返回一个 job_id,这个 job_id 怎么用咱们下面再说。显然数据库的数据导出并非同步的,而是须要必定时间的,数据量越大导出所要花费的时间就越多,我的实测,2W 条记录,2M 大小,导出大概须要 3~5 S。微信

调用 databaseMigrateExport 接口须要传入环境 Id,存储文件路径,导出文件类型(1 为 JSON,2 为 CSV),以及一个 query 查询语句。app

由于咱们是作数据库备份,因此这里就导出 JSON 类型的数据,兼容性更好。须要备份的数据能够用 query 来约束,这里仍是很灵活的,既能够是整个集合的数据,也能够是指定的部分数据,这里咱们就使用 db.collection('data').get() 备份 data 集合的所有数据。同时咱们使用当前时间做为文件名,方便之后使用时查找。async

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
    }
);

3. 查询任务状态,获取文件地址

在建立号数据库导出任务后,咱们会获得一个 job_id,若是导出集合比较大,就会花费较长时间,这时咱们可使用 databaseMigrateQueryInfo 接口查询数据库导出的进度。

当导出完成后,会返回一个 file_url,便可如下载数据库导出文件的临时连接。

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
    }
);

获取到文件下载连接以后,咱们能够将文件下载下来,存入到本身的云存储中,作备份使用。若是不须要长时间的保留备份,就能够不用下载文件,只须要将 job_id 存储起来,当须要恢复备份的时候,经过 job_id 查询到新的连接,下载数据恢复便可。

至于 job_id 存在哪,就看我的想法了,这里就选择存放在数据库里。

await db.collection('db_back_info').add({
    data: {
        date: new Date(),
        jobId: job_id
    }
});

4. 函数定时触发器

云函数支持定时触发器,能够按照设定的时间自动执行。云开发的定时触发器采用的 Cron 表达式语法,最大精度能够作的秒级,详细的使用方法能够参考官方文档:定时触发器 | 微信开放文档

这里咱们配置函数天天凌晨 2 点触发,这样就能够天天都对数据库进行备份。在云函数目录下新建 config.json文件,写入以下内容:

{
  "triggers": [
    {
      "name": "dbTrigger",
      "type": "timer",
      "config": "0 0 2 * * * *"
    }
  ]
}

完整代码

最后,贴出能够在云函数中使用的完整代码,只须要建立一个定时触发的云函数,并设置好相关的环境变量便可使用

  • appid
  • secret
  • backupColl:须要备份的集合名称,如 ‘data’
  • backupInfoColl:存储备份信息的集合名称,如 ‘db_back_info’

注意,云函数的默认超时时间是 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}`);
    }
};

若是你想要了解更多关于云开发CloudBase相关的技术故事/技术实战经验,请扫码关注【腾讯云云开发】公众号~

相关文章
相关标签/搜索