小程序云开发提供了方便的云数据库供我们直接使用,但是在程序升级和后台数据管理过程中,可能会出现因为误操作等意外,导致一些不可避免数据库中数据的安全,比如不小心删除了数据集合,写入了脏数据等。
云数据库中的数据我们可以使用小程序开发者工具中的控制台进行操作,但必须要安装有开发者工具且操作用户微信被授权登录才可以进行管理维护。因此我使用Qt制作PC端应用程序,通过HTTP API调用的方式管理维护后台数据,这里介绍对云数据库中集合进行备份到本地的功能实现。
通过查阅微信的文档,可以发现云开发提供了数据导出接口databaseMigrateExport
请求地址
POST https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN
# 请求参数
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
access_token | string | 是 | 接口调用凭证 | |
env | string | 是 | 云环境ID | |
file_path | string | 是 | 导出文件路径(文件会导出到同环境的云存储中,可使用获取下载链接 API 获取下载链接) | |
file_type | number | 是 | 导出文件类型,文件格式参考数据库导入指引中的文件格式部分 | |
query | string | 是 | 导出条件 |
需要说明的是,此处导出的环境文档中写着同环境云存储中,但是并不是小程序云开发中,因此在导出的备份文件是不能在云开发控制台中的存储里找到的,因此参数中的file_path可以随意写,不需要根据云存储中文件夹路径来。
第一步:获取access_token
获取access_token的方法我之前博客有介绍,此处不过多阐述。传送门:Qt 连接管理微信小程序云数据库一(获取微信后台接口调用凭据ACCESS_TOKEN)
第二步:获取导出任务ID:job_id
获取 access_token 后,就可以使用 databaseMigrateExport
接口导出数据进行备份,该API接口返回导出任务ID,使用该任务ID,使用databaseMigrateQueryInfo接口,就可以查看到服务端中数据备份导出的状态。
void JsonDataModel::initial(){
manager = new QNetworkAccessManager(this);
request = new QNetworkRequest();
request->setHeader(QNetworkRequest::ContentTypeHeader,"application/json; encoding=utf-8");
}
request->setUrl(QUrl(tr("https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=%1").arg(Access_Token)));
QString path = ".json";
QString query = tr("db.collection('record').get()");
QJsonObject obj{
{"file_path","record.json"},
{"env",env},
{"file_type",1},//1:json格式;2:csv格式
{"query",query}
};
QByteArray oby = QJsonDocument(obj).toJson();
QNetworkReply* r = manager->post(*request,oby);
QEventLoop loop;
connect(r, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();//开启事件循环,等待接收到响应数据
QByteArray ba = r->readAll();
QJsonDocument callbackJson = QJsonDocument::fromJson(ba);
job_id = callbackJson["job_id"];
qDebug()<<callbackJson;//打印返回的数据内容
r->deleteLater();
返回结果为:
QJsonDocument({"errcode":0,"errmsg":"ok","job_id":100692781})
第三步:查询任务状态,获取文件下载地址
使用databaseMigrateQueryInfo接口查询当前任务ID的状态,接口文档:
请求地址
POST https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=ACCESS_TOKEN
# 请求参数
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
access_token | string | 是 | 接口调用凭证 | |
env | string | 是 | 云环境ID | |
job_id | number | 是 | 迁移任务ID |
通过测试发现数据库的数据导出并不是同步的,而是需要一定时间的,数据量越大导出所要花费的时间就越多。接口返回的数据中status代表了备份数据导出的状态,因此我使用QTimer设计一个计时器,每隔200ms发送一次状态请求,当发现导出成功时,停止计时器,下载备份文件。
QTimer *timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(checkBackup()));
timer->start(200);
void JsonDataModel::checkBackup(){
QTimer* timer = qobject_cast<QTimer*>(sender());
request->setUrl(QUrl(tr("https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=%1").arg(Access_Token)));
QJsonObject obj{
{"env",env},
{"job_id",job_id}
};
QByteArray objBy= QJsonDocument(obj).toJson();
QNetworkReply* r = manager->post(*request,objBy);
QEventLoop loop;
connect(r, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
QByteArray ba = r->readAll();
QJsonDocument js_r = QJsonDocument::fromJson(ba);
qDebug()<<js_r;//打印返回数据内容
QString file_url = js_r["file_url"].toString();
if(file_url!=""){
timer->stop();//当返回下载地址不为空时,停止计时器
}
}
打印输出:
QJsonDocument({"errcode":0,"errmsg":"ok","error_msg":"","file_url":"","record_fail":0,"record_success":0,"status":"waiting"})
QJsonDocument({"errcode":0,"errmsg":"ok","error_msg":"","file_url":"","record_fail":0,"record_success":0,"status":"migrating"}))
QJsonDocument({"errcode":0,"errmsg":"ok","error_msg":"导出完成.","file_url":"https://tcb-mongodb-data-1254135806.cos.ap-shanghai.myqcloud.com/100013993337/record.json?q-sign-algorithm=sha1&q-ak=AKIDsp8NUoE8C8yd9TvEemXUh9Wy&q-sign-time=1592803039;1592806639&q-key-time=1592803039;1592806639&r-list=&q-url-param-list=&q-signature=06763f74cee1b3577d17f9ef82dae1a23add","record_fail":0,"record_success":10,"status":"success"})
此处的file_url就是备份文件的下载地址
第四步:下载备份文件至本地磁盘
if(file_url!=""){
timer->stop();
QString path = QDir::currentPath()+"/record.json";
QFile file(path);
request->setUrl(QUrl(file_url));
QNetworkReply* rr = manager->get(*request);
connect(rr, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
QByteArray ba_file = rr->readAll();
file.open(QIODevice::WriteOnly);
file.write(ba_file);
file.close();
}
备份数据如下:
当需要还原数据时使用云开发控制台导入数据即可
完整代码:
void JsonDataModel::initial(){
manager = new QNetworkAccessManager(this);
request = new QNetworkRequest();
request->setHeader(QNetworkRequest::ContentTypeHeader,"application/json; encoding=utf-8");
}
void JsonDataModel::backupDataBase(){
request->setUrl(QUrl(tr("https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=%1").arg(Access_Token)));
QString path = ".json";
QString query = tr("db.collection('record').get()");
QJsonObject obj{
{"file_path","record.json"},
{"env",env},
{"file_type",1},
{"query",query}
};
QByteArray oby = QJsonDocument(obj).toJson();
QNetworkReply* r = manager->post(*request,oby);
QEventLoop loop;
connect(r, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
QByteArray ba = r->readAll();
QJsonDocument callbackJson = QJsonDocument::fromJson(ba);
job_id = callbackJson["job_id"];
qDebug()<<callbackJson;
QTimer *timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(checkBackup()));
timer->start(200);
r->deleteLater();
}
void JsonDataModel::checkBackup(){
QTimer* timer = qobject_cast<QTimer*>(sender());
request->setUrl(QUrl(tr("https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=%1").arg(Access_Token)));
QJsonObject obj{
{"env",env},
{"job_id",job_id}
};
QByteArray objBy= QJsonDocument(obj).toJson();
QNetworkReply* r = manager->post(*request,objBy);
QEventLoop loop;
connect(r, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
QByteArray ba = r->readAll();
QJsonDocument js_r = QJsonDocument::fromJson(ba);
qDebug()<<js_r;
QString file_url = js_r["file_url"].toString();
if(file_url!=""){
timer->stop();
QString path = QDir::currentPath()+"/record.json";
QFile file(path);
request->setUrl(QUrl(file_url));
QNetworkReply* rr = manager->get(*request);
connect(rr, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
QByteArray ba_file = rr->readAll();
file.open(QIODevice::WriteOnly);
file.write(ba_file);
file.close();
rr->deleteLater();
timer->deleteLater();
r->deleteLater();
}
}
如果搭配上databaseCollectionGet的获取云数据库所有集合信息,加上循环,就可以自动将数据库中所有集合备份到本地: