写在开头
随着1.0的国内版本正式上线,相信不少小伙伴需要从海外版迁移回来~
云函数以及云存储的备份迁移可以使用laf-cli,但是数据库的迁移和备份还没有很好的办法~于是经过一天的奋斗和测试~
亲测百万数据库的迁移和备份都是妥妥的稳稳的~
BackupLaf来了,妈妈再也不怕我数据丢失了,结合定时器,其实还能玩出更多花样!
以海外版迁移数据库到国内版举例
海外版地址:https://laf.dev 后面简称dev中
国内版地址:https://laf.run 备案域名,可以愉快的接入小程序了,后面简称run中
1、在dev中新建备份云函数
函数名:BackupDB
,备份数据库云函数
可以发布,可以不发布,无所谓。
import cloud from "@lafjs/cloud";
import { S3 } from "@aws-sdk/client-s3"
const bucket = `<appid>-存储桶名称`;
const credentialsURL = "https://appid.laf.run/get-oss-sts"
export async function main(ctx: FunctionContext) {
const { credentials, endpoint, region } = (await cloud.fetch(credentialsURL)).data;
const BackupDBPath = "BackupDB"
const s3Client = new S3({
endpoint: endpoint,
region: region,
credentials: {
accessKeyId: credentials.AccessKeyId,
secretAccessKey: credentials.SecretAccessKey,
sessionToken: credentials.SessionToken,
expiration: credentials.Expiration,
},
forcePathStyle: true,
})
const collections = await cloud.mongo.db.listCollections().toArray();
const filteredData = collections.filter(
(obj) => obj.name !== "__functions__" && obj.name !== "__function_logs__"
);
const DbListName = filteredData.map((obj) => obj.name);
let dbInfo = {}
if (DbListName.length > 0) {
for (const DbName of DbListName) {
const db = cloud.database();
const collection = db.collection(DbName);
const countResult = await collection.count();
const total = countResult.total;
dbInfo[DbName] = total
const batchTimes = Math.ceil(total / 1000);
let start = 0
const batchRes = await db.collection("BackupDB").where({DbName:DbName}).getOne()
if(batchRes.data){
start = batchRes.data.Batch
}
for (let i = start; i < batchTimes; i++) {
try {
const res = await collection.skip(i * 1000).limit(1000).get();
const filename = `${BackupDBPath}/${DbName}/${i}.json`
const upload_res = await s3Client.putObject({
Bucket: bucket,
Key: filename,
Body: JSON.stringify(res.data),
ContentType: 'application/json',
})
console.log(`插入${DbName}表第${i}批数据成功`);
await db.collection("BackupDB").add({
DbName: DbName,
Batch: i,
})
} catch (error) {
console.log(error);
return { data: "备份出错:"+error };
}
}
}
const filename = `${BackupDBPath}/info.json`
const upload_res = await s3Client.putObject({
Bucket: bucket,
Key: filename,
Body: JSON.stringify(dbInfo),
ContentType: 'application/json',
})
if (upload_res.$metadata.httpStatusCode == 200) {
console.log("全部数据库备份成功");
return { data: "全部数据库备份成功" };
}else{
return { data: "备份失败" };
}
}
}
2、在run中新建恢复云函数
函数名:ReductionDB
,恢复数据库云函数
可以发布,可以不发布,无所谓。
import cloud from "@lafjs/cloud";
const db = cloud.database();
const bucket = `https://<appid>-<bucketName>`;
const bucketURL = "oss.laf.run";
export async function main(ctx: FunctionContext) {
const info: {
[key: string]: number;
} = (await cloud.fetch(`${bucket}.${bucketURL}/BackupDB/info.json`))
.data;
for (const [key, value] of Object.entries(info)) {
const batchTimes = Math.ceil(value / 1000);
let start = 0;
const batchRes = await db
.collection("ReductionDB")
.where({ DbName: key })
.getOne();
if (batchRes.data) {
start = batchRes.data.Batch;
}
for (let i = start; i < batchTimes; i++) {
try {
const data = (
await cloud.fetch(
`${bucket}.${bucketURL}/BackupDB/${key}/${i}.json`
)
).data;
const collection = cloud.mongo.db.collection(key);
await collection.insertMany(data);
console.log(`插入${key}表第${i}批数据成功`);
await db.collection("ReductionDB").add({
DbName: key,
Batch: i,
});
} catch (error) {
console.log("插入失败:", error);
return { data: error };
}
}
}
console.log("全部数据库恢复完成");
return { data: "全部数据库恢复完成" };
}
3、run中新建云存储上传临时密钥云函数
函数名:get-oss-sts
必须发布
import cloud from "@lafjs/cloud";
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
exports.main = async function (ctx: FunctionContext) {
const sts: any = new STSClient({
region: cloud.env.OSS_REGION,
endpoint: cloud.env.OSS_INTERNAL_ENDPOINT,
credentials: {
accessKeyId: cloud.env.OSS_ACCESS_KEY,
secretAccessKey: cloud.env.OSS_ACCESS_SECRET,
},
});
const cmd = new AssumeRoleCommand({
DurationSeconds: 3600,
Policy:
'{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}',
RoleArn: "arn:xxx:xxx:xxx:xxxx",
RoleSessionName: cloud.appid,
});
const res = await sts.send(cmd);
return {
credentials: res.Credentials,
endpoint: cloud.env.OSS_EXTERNAL_ENDPOINT,
region: cloud.env.OSS_REGION,
};
};
4、run中新建存储桶
获得存储桶的名称和应用的appid
5、修改配置
BackupDB
云函数中修改内容如下:
ReductionDB
云函数只能中修改内容如下:
6、开始备份
BackupDB
中点击运行,会自动备份全部数据,如果数据较大,时间会比较长,期间不用一直等着,可以去做别的事情。
备份完成,可以在日志中找全部数据库备份
完成的日志,全部备份的数据,以json保存在了存储桶的BackupDB
目录下面
7、开始恢复
备份完成后,去ReductionDB
中点击运行,会自动将全部json文件导入对应的集合,如果数据较大,时间会比较长,期间不用一直等着,可以去做别的事情。
备份完成,可以在日志中找全部数据库恢复完成
完成的日志,每条数据的_id也是原来的。
8、注意事项
BackupDB
和 ReductionDB
云函数在备份/恢复过程中,如果数据较多,所需时间会较长。会出现网关超时的情况,不用理会,Laf后台会继续执行,Laf云函数的运行稳得一逼!!判断执行成功的方法,可以去日志查看全部数据库恢复完成
或全部数据库备份完成
的字样,判断是否成功
如果出现失败,请不要慌,直接再次运行备份/恢复云函数即可,本项目会将备份/恢复记录保存到集合名:BackupDB
和 ReductionDB
中,从而实现断点备份和断点恢复
9、开源地址
https://github.com/nightwhite/BackupLaf
后续会继续更新0.8备份数据库并迁移到1.0