分享代码片段

不想听废话的,直接看代码。
https://developers.weixin.qq.com/s/UL9aojmn7iNU

场景分析

如果你的微信小程序需要选中 ZIP 包,然后解压并处理其中的文件。
对于移动端可以使用 wx.chooseMessageFile(Object object) 来实现,它支持从客户端会话选择文件。
但不幸的是PC端,并不支持此API。官方也没有给出别的API。(当然论坛里有提到一个曲线解决方案,申请个企业号,用webview实现选择文件。网上有讨论的,这里就不细聊了。)
但是对于个人号来说,这就尴尬了。一是没有权限使用webview,二是纯前端的小程序,为了选择文件还得加个服务器,有点不值当。
所以想到了一个曲线的解决方案。利用:chooseMedia

解决思路

  1. 虽然PC端不支持 wx.chooseMessageFile 但支持 wx.chooseMedia 这个API是用来选择媒体文件的,比如:视频、图片。
  2. 通过测试,直接将 zip 的扩展名改为 PNG 是无效的。因为选择后,它会检查文件头,过滤掉不符合的,最终只能拿到真实的PNG文件。
  3. 既然它是校验了文件头,那我们就给ZIP文件前加上PNG的文件名,然后通过 wx.chooseMedia 获取到文件后,再跳过我们自己的 PNG头,把剩余的ZIP部分读出来,保存为一个正常的 ZIP 文件。
  4. 然后就可以正常解压或读取 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)
        });
      });
    });
  }

微信小程序 - PC端选择ZIP文件-LMLPHP

参考资料

判断文件/目录是否存在(同步版) FileSystemManager.accessSync(string path)
创建目录(同步版) FileSystemManager.mkdirSync(string dirPath, boolean recursive)
写文件(同步版) FileSystemManager.writeFileSync(string filePath, string|ArrayBuffer data, string encoding)

12-09 08:31