有一些情况下,我们的小程序需要初始一次性导入大量历史数据,这是一个比较难的大任务,要一次性由一个云函数来完成。
笔者为完成这样一个任务,经过了痛苦的摸索,终于搞定。尽管回头看其实不是什么特别难的事情,但是摸索过程比较痛苦,所以留下经验贴,供各位微信小程序的程序员参考。
实现过程思路如下:
1. 用 EXCEL 文件组织好数据,批量上传,一条一条导入;
2. EXCEL 文件要控制好大小,尽量别太大,1M以下为好;
3. 云函数本身对资源的消耗(时间、空间)是有限制的,所以 EXCEL 文件上传后,要分批次提交。循环读取每一行的数据,做成一个任务,插入任务队列,当队列达到一个批次的数量(例如 200 条记录一个批次)提交一次,待一批次执行完成,清空任务队列,控制空间使用,再去组织下一个批次。这样,既能实现一定程度上的任务并发完成,又能控制整体空间消耗,使得大任务能够化整为零,最终完成。
云函数定义如下:
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 使用当前云环境
const xlsx = require('node-xlsx');
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const db = cloud.database();
let fileID = event.fileID;
let LabelDBName = event.LabelDBName;
/* 1. 通过fileID下载云存储里的 excel 文件 */
const res = await cloud.downloadFile({
fileID: fileID,
})
const buffer = res.fileContent;
const tasks = []; /* 用来存储所有的添加数据操作 */
let RecNum = 0;
let TotalRecNum = 0;
/* 2. 解析excel文件里的数据 */
var sheets = xlsx.parse(buffer); /* 获取到所有sheets */
sheets.forEach( async function(sheet) { /* 这一行的 async 特别重要,因为要批次提交并等候完成。如果没有这个 async 的定义,在后面提交代码执行时会报错 await 无效,我试了很久才明白报错的真正原因 */
console.log(sheet['name']);
for (var rowId in sheet['data']) {
var row = sheet['data'][rowId]; /* 第几行数据 */
if (rowId > 0 && row) { /* 第一行是表格标题,所有我们要从第2行开始读 */
if (row[1].trim() == '') continue;
/* 3. 把解析到的数据存到excelList数据表里 */
const promise = db.collection(LabelDBName)
.add({
data: { /* 这里是数据字段值列表 */
ID: row[0], /* ID */
LabelNO: row[1],
BookName: row[2],
Author: row[3],
_openid: wxContext.OPENID, /* 建议加入这列,否则数据没有主人后续对数据的操作比较麻烦 */
step: 0
}
})
tasks.push(promise)
RecNum++;
}
if (RecNum >= 200) { /* 每 200 条数据提交一次 */
/* 等待所有数据添加完成 */
let result = await Promise.all(tasks).then(res => {
console.log("total: ", tasks.length);
/* 清空数组,下一轮 */
tasks.splice(0, RecNum);
TotalRecNum = TotalRecNum + RecNum;
RecNum = 0;
}).catch(function(err) {
console.log(err)
})
}
}
});
/* 等待所有数据添加完成 */
let result = await Promise.all(tasks).then(res => {
console.log("total: ", tasks.length);
}).catch(function(err) {
console.log(err)
})
return {
event,
total: TotalRecNum,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
}
}
页面 JS 调用代码如下:
/* 导入数据函数 */
myImport() {
let self = this;
let LabelDBName = self.data.userInfo.LabelDB;
wx.chooseMessageFile({ /* 选择文件 */
count: 1,
type: "excel",
success(res) {
let tmpFilepath = res.tempFiles[0].path;
let cloudFilePath = new Date().getTime() + ".xls";
wx.cloud.uploadFile({ /* 上传文件 */
cloudPath: cloudFilePath,
filePath: tmpFilepath,
success(res) {
console.log("EXCEL 文件上传成功。", tmpFilepath);
wx.showToast({
title: '文件已成功上传,开始导入处理。。。',
duration: 4000,
icon: 'none',
})
console.log("调用云函数 label_importLabels。");
wx.cloud.callFunction({
name: "label_importLabels",
data: {
fileID: res.fileID,
LabelDBName: LabelDBName,
},
success(res) {
console.log(res)
wx.showModal({
title: '导入完成',
content: '共导入记录条数:' + res.result.total,
})
},
fail(res) {
console.log("云函数 label_importLabels 调用失败,", res)
}
})
},
fail(res) {
console.log("上传失败。");
wx.showToast({
title: '文件上传失败',
duration: 4000,
icon: 'none',
})
}
})
}
})
},
总结:
1. 对于执行长任务的云函数,需要设置其时间空间开销:
2. 鉴于云函数对时间空间的限制,试下来的结论是:每个 EXCEL 文件最好控制在 1 万条数据。
3. 如果实在是历史数据很多,那么就还是用云开发中的数据库入库功能,将数据手动导入。导入时记得在 EXCEL 文件中加一列 _openid,指定一下数据的主人,这样才好在应用中控制修改权限。