分享代码片段
不想听废话的,直接看代码。
https://developers.weixin.qq.com/s/UL9aojmn7iNU
场景分析
如果你的微信小程序需要选中 ZIP 包,然后解压并处理其中的文件。
对于移动端可以使用 wx.chooseMessageFile(Object object) 来实现,它支持从客户端会话选择文件。
但不幸的是PC端,并不支持此API。官方也没有给出别的API。(当然论坛里有提到一个曲线解决方案,申请个企业号,用webview
实现选择文件。网上有讨论的,这里就不细聊了。)
但是对于个人号来说,这就尴尬了。一是没有权限使用webview
,二是纯前端的小程序,为了选择文件还得加个服务器,有点不值当。
所以想到了一个曲线的解决方案。利用:chooseMedia
解决思路
- 虽然PC端不支持 wx.chooseMessageFile 但支持 wx.chooseMedia 这个API是用来选择媒体文件的,比如:视频、图片。
- 通过测试,直接将 zip 的扩展名改为 PNG 是无效的。因为选择后,它会检查文件头,过滤掉不符合的,最终只能拿到真实的PNG文件。
- 既然它是校验了文件头,那我们就给ZIP文件前加上PNG的文件名,然后通过
wx.chooseMedia
获取到文件后,再跳过我们自己的 PNG头,把剩余的ZIP部分读出来,保存为一个正常的 ZIP 文件。 - 然后就可以正常解压或读取 ZIP 压缩包了。
附魔脚本
将PNG
头加到ZIP
文件前面,可以使用下面这个脚本实现。
这是附魔的bat
脚本,通过在zip
前添加25
个字节,是PNG
文件的头89504E470D0A1A0A0000000D49484452
转成的base64
。
echo iVBORw0KGgoAAAANSUhEUg==>_________
copy /b _________ + /b %1 %~nx1.png
del /q _________
将zip
拖到脚本上之后,即可生成一个 xxx.zip.png
文件。
chooseMediaZip 选择附魔后的ZIP文件
const fs = wx.getFileSystemManager();
/**
* 选择媒体附魔的ZIP
* @param {object} options
*/
function chooseMediaZip(options = {}){
const defaultOptions = {
count: 9, // 单次选择数量上限
mediaType: ['image'], // 选择框支持的文件类型
encoding: 'binary', // 编码类型,默认 'binary'。想写文本用 'utf8'
folder: `${wx.env.USER_DATA_PATH}/jerry`, // 保存二进制文件的路径
suffix: 'zip' // 二进制文件的扩展名
};
let params = {...defaultOptions, ...options};
return wx.chooseMedia(params) // 选择 *.zip.png 文件
.then(res=>{ // 跳过 PNG 读取出实际二进制数据
return res.tempFiles.map(tempFile=> {
// 跳过PNG头25字节,从26开始读取 zip 的二进制部分
const arrayBuffer = readFileSync(tempFile.tempFilePath, '', 26);
console.log(arrayBuffer);
// 写 二进制文件 到目录 path
const file = {};
file.name = `${Date.now()}.${suffix}`;
file.path =`${ params.folder }/${ file.name }`;
mkdir(params.folder);
writeFileSync( file.path, arrayBuffer, encoding ); // 写 二进制 文件
return file; // {name: 'fileName', path: 'http://xxx.xxx'}
});
}).catch(console.error);
}
相关方法
chooseMedia 需要用到的几个方法
/**
* 判断文件或目录是否存在
* @param {string} path 文件或目录路径
*/
function isExist(path){
try {
fs.accessSync(path);
return true;
} catch (err) {
console.log(`文件或目录不存在:${err.message}`);
return false;
}
}
/**
* 创建目录路
* 如果目录不存在就创建。
* @param {string} dirPath 文件夹路径
* @param {boolean} recursive 如果上级目录不存在,是否自动创建。默认:是
*/
function mkdir(path, recursive = true){
if(isExist(path) == false){ // 判断目录是否存在
fs.mkdirSync(path, recursive); // 如果没有就创建
}
}
/**
* 写文件
* @param {*} filePath 文件路径。要写入的文件路径 (本地路径)
* @param {*} data 要写入的文本或二进制数据
* @param {*} encoding 指定写入文件的字符编码。默认 binary
*/
function writeFileSync(filePath, data, encoding='binary') {
fs.writeFileSync(filePath, data, encoding);
}
测试方法
test(e){
fileUtil.chooseMediaZip({
count: 9, // 单次选择数量上限
mediaType: ['image'], // 选择框支持的文件类型
encoding: 'binary', // 编码类型,默认 'binary'。想写文本用 'utf8'
folder: `${wx.env.USER_DATA_PATH}/jerry`, // 保存二进制文件的路径
suffix: 'zip' // 二进制文件的扩展名
}).then(res => {
res.forEach(zip => {
fs.readZipEntry({
'filePath': zip.path,
'entries': 'all',
success: res => console.log(res.entries), // zip中的文件列表
fail: err => console.log(err)
});
});
});
}
参考资料
判断文件/目录是否存在(同步版) FileSystemManager.accessSync(string path)
创建目录(同步版) FileSystemManager.mkdirSync(string dirPath, boolean recursive)
写文件(同步版) FileSystemManager.writeFileSync(string filePath, string|ArrayBuffer data, string encoding)