小程序·雲開發之數據庫自動備份丨雲開發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相關的技術故事/技術實戰經驗,請掃碼關注【騰訊云云開發】公衆號~

相關文章
相關標籤/搜索