背景: uniapp打包成app时,需要分片上传的场景,未提供完整api,只能自己摸索
在 UniApp 的 HTML5+ API(plus.io) 中,确实没有直接提供 readAsArrayBuffer 方法。这与标准浏览器环境中的 FileReader 不同,后者支持 readAsArrayBuffer 用于直接读取文件的二进制数据
UniApp 中的替代方案
步骤1:使用 readAsDataURL + Base64 转 ArrayBuffer
问:为什么要去除dataUrl头部?
答:在 DataURL 中,头部(Header) 描述了数据的类型和编码方式,而 主体(Body) 才是真正的 Base64 编码内容。去除头部的目的是为了获取 纯 Base64 字符串,以便后续处理(如转 ArrayBuffer)。以下是详细解释
const dataURL = 'data:image/png;base64,iVBORw0KGgo...';
const base64 = dataURL.split(',')[1]; // 提取逗号后的部分
console.log(base64); // 输出:iVBORw0KGgo...
代码如下:
// 读取文件切片
export function readFileSlice(arrayBuffer, start, end) {
return new Promise((resolve, reject) => {
//#ifdef H5
resolve(arrayBuffer.slice(start, end));
//#endif
//#ifdef APP-PLUS
const reader = new plus.io.FileReader();
const sliceFile = arrayBuffer.slice(start, end);
reader.readAsDataURL(sliceFile); // 读取为base64
reader.onloadend = (e) => {
const arrayBuffer = dataURLToArrayBuffer(e.target?.result);
console.log('分片大小:', end - start + 1, '转换后字节数:', arrayBuffer.byteLength);
resolve(arrayBuffer);
};
reader.onerror = (e) => {
console.error('读取错误:', e.target.error);
reject(false);
};
//#endif
});
}
// DataURL转ArrayBuffer
function dataURLToArrayBuffer(dataURL) {
const commaIndex = dataURL.indexOf(','); // 查找第一个逗号位置
if (commaIndex === -1) throw new Error('Invalid DataURL');
const base64 = dataURL.substring(commaIndex + 1); // 精确截取Base64部分
return uni.base64ToArrayBuffer(base64);
}
2:分片上传到服务端后文件为啥多了几个字节?
有的可以播放,有的不能播放,分析原因:
使用对比工具发现,多出的字节都与前一个字节相同
联想到是不是每个切片开头结尾有问题,去查看官网
HTML5+ API Reference (html5plus.org)
破案了,html5+api中slice,不是开区间,包含end
解决办法:每个切换end-1,最后一个切片不减
// 获取切片end
export function getSliceEnd(start, chunkSize, fileSize, index, totalChunks) {
//#ifdef H5
return Math.min(start + chunkSize, fileSize);
//#endif
//#ifdef APP-PLUS
return index < totalChunks - 1 ? start + chunkSize - 1 : fileSize;
//#endif
}
完整代码如下:
// 读取文件内容(跨平台处理H5,app)
export function readFile(fileInfo) {
return new Promise((resolve, reject) => {
//#ifdef H5
if (fileInfo) {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(fileInfo);
}
//#endif
//#ifdef APP-PLUS
const filepath = fileInfo.url || fileInfo.path;
if (filepath) {
plus.io.resolveLocalFileSystemURL(
filepath,
(fileEntry) => {
fileEntry.file(
(file) => {
console.log('获取文件对象成功:', file);
resolve(file);
},
(error) => {
console.error('获取文件对象失败:', error);
reject(error);
},
);
},
(error) => {
console.error('解析路径失败:', error);
},
);
}
//#endif
});
}
// 获取切片end
export function getSliceEnd(start, chunkSize, fileSize, index, totalChunks) {
//#ifdef H5
return Math.min(start + chunkSize, fileSize);
//#endif
//#ifdef APP-PLUS
return index < totalChunks - 1 ? start + chunkSize - 1 : fileSize;
//#endif
}
// 切片逻辑
const fileBuffer = await readFile(file);
// Upload each part.
const partsInfo = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * this.config.chunkSize;
const end = getSliceEnd(start, this.config.chunkSize, file.size, i, totalChunks);
// 获取到chunk 数据(arraybuffer)
const chunk = await readFileSlice(fileBuffer, start, end);
...
}
3:切片后也可以写入到文件,demo如下
// 保存ArrayBuffer为临时文件
function saveArrayBufferToTempFile(arrayBuffer) {
// 将 ArrayBuffer 转换为 H5+ 字节数组
const byteArray = new plus.io.ByteArray();
byteArray.setBuffer(arrayBuffer); // 直接传入 ArrayBuffer
return new Promise((resolve, reject) => {
const tempPath = '_doc/' + Date.now() + '_chunk.tmp';
plus.io.resolveLocalFileSystemURL('_doc', (dirEntry) => {
dirEntry.getFile(tempPath, { create: true }, (fileEntry) => {
fileEntry.createWriter((writer) => {
writer.onwrite = () => resolve(tempPath);
writer.onerror = reject;
//const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
writer.write(byteArray);
}, reject);
}, reject);
}, reject);
});
}