微信小程序 - 文件工具类 fileUtil.js

const fs = wx.getFileSystemManager();

/**
 * 选择媒体附魔的ZIP
 * @param {object} options 
 */
function chooseMediaZip(options = {}){
  const defaultOptions = { count: 9, mediaType: ['image']};
  let params = {...defaultOptions, ...options};
  return wx.chooseMedia(params) // 选择 *.zip.png 文件
  .then(res=>{ // png 处理为 zip
    return res.tempFiles.map(file => {
      /* 这是附魔的bat脚本,在zip前添加了25个字节,是一个PNG文件的头:89504E470D0A1A0A0000000D49484452 转成的 base64
      echo iVBORw0KGgoAAAANSUhEUg==>_________
      copy /b _________ + /b %1 %~nx1.png
      del /q _________
      */
      // 读取zip的二进制部分
      const arrayBuffer  = readFileSync(file.tempFilePath, '', 26);
      console.log(arrayBuffer);
      // 写 zip 到目录 globalData.PATH.zippng
      const zipFile = {};
      zipFile.name = `${Date.now()}.zip`;
      zipFile.path =`${ params.zippng }/${ zipFile.name }`;
      mkdir(params.zippng); // 先创建目录
      writeFileSync( zipFile.path, arrayBuffer, 'binary' ); // 写 zip 文件
      return zipFile; // {name: 'fileName', path: 'http://xxx.png'}
    });
  }).catch(console.error);
}

/**
 * 选择媒体文件
 * @param {object} options 
 */
function chooseMedia(options = {}){
  const defaultOptions = { count: 9, mediaType: ['image']};
  return wx.chooseMedia({...defaultOptions, ...options});
}

/**
 * 从聊天中选择文件
 * @param {number} _count 
 */
function chooseFile(_count){
  return wx.chooseMessageFile({ count: _count })
  .then( res => {
    //  res 内容: {
    //     "errMsg":"chooseMessageFile:ok",
    //     "tempFiles":[
    //       {
    //         "name":"spineboy.zip",
    //         "size":273679,
    //         "time":1692946544,
    //         "path":"http://tmp/eZhBTvHhtCEN1328314d91c79395dce640b2f2aba2e7.zip",
    //         "type":"file"
    //       }
    //     ]
    //   }
    if(res.errMsg === "chooseMessageFile:ok"){
      return res.tempFiles;
    }else{
      return [];
    }
  });
}

/**
 * 获取目录结构
 * @param {string} rootPath 
 */
function ls(rootPath) {  
  // 检查目录是否存在  
  if(!isExist(rootPath)){
    return [];
  }
  let list = fs.statSync(`${rootPath}`, true);

  if(Array.isArray(list) == false && list.isDirectory()) {
    return [];
  }

  // 返回一个对象包含 dirPath 目录下的文件和子目录:{'file':[], 'dir': []}
  return list.map(df => {
    df.type = df.stats.isFile() ? 'file' : 'dir';
    return df;
  }).reduce((result, item) => {
    item.path = `${rootPath}/${item.path}`;
    result[item.type].push(item);
    return result;
  }, {'file':[], 'dir': []});
}

/**
 * 删除文件(同步版)
 * @param {string} filePath 文件路径
 */
function unlinkSync(filePath){
  if(isExist(filePath) == false){
    console.log(`文件不存在:${filePath}`)
    return;
  }
  try {
    const res = fs.unlinkSync(filePath);
    console.log(res)
  } catch(e) {
    console.error(e)
  }
}

/**
 * 清空目录:包含所有子目录和文件
 * @param {string} rootPath 要清空的目录
 * @param {*} saveSelf      是否删除自己。为 false 则只清空其实的子目录和文件,但保留自己
 */
function clearDir(rootPath, saveSelf=true) {  
  // 检查目录是否存在  
  if(!isExist(rootPath)){
    return;
  }

  // 获取:文件与子目录
  let group = ls(rootPath);
  if(Array.isArray(group.file) == false){ // 如果file属性不是一个数组,说明没查到东西。是个空文件夹。
    return;
  }

  // 删除所有文件
  let p = group.file.map(f => {
    return new Promise((resolve, reject) => {
      fs.unlink({
        filePath: f.path,
        success: res => resolve(res),
        fail: err => reject(err)
      })
    });
  });

  // 删除全部文件后,再执行删除目录。
  Promise.all(p).then(res => {
    fs.rmdir({
      dirPath: `${rootPath}`,
      recursive: true,
      success: res => console.log(res),
      fail: err => console.error(err),
    });
  });
  // 上面是直接递归删除整个目录,包括自己。这里判断一下,如果需要保留根目录,就再创建一下。
  if(saveSelf){
    mkdir(rootPath);
  }
}

/**
 * 清空目录(同步版):包含所有子目录和文件
 * @param {string} rootPath 要清空的目录
 * @param {*} saveSelf      是否删除自己。为 false 则只清空其实的子目录和文件,但保留自己
 */
function clearDirSync(rootPath, saveSelf=true) {  
  // 检查目录是否存在  
  if(!isExist(rootPath)){
    return;
  }

  // 获取:文件与子目录
  let group = ls(rootPath);
  if(Array.isArray(group.file) == false){ // 如果file属性不是一个数组,说明没查到东西。是个空文件夹。
    return;
  }

  // 删除所有文件
  group.file.map(f => fs.unlinkSync(f.path));
  // 递归删除全部目录。
  fs.rmdirSync(rootPath, true);

  // 上面是直接递归删除整个目录,包括自己。这里判断一下,如果需要保留根目录,就再创建一下。
  if(saveSelf){
    mkdir(rootPath);
  }
}

/**
 * 清空目录下的文件(同步版)(只删除文件)
 * @param {string} rootPath 要清空的目录
 * @returns 被删除的文件列表
 */
function clearFilesSync(rootPath) {  
  try {
    // 获取目录下的文件列表
    const files = fs.readdirSync(rootPath);
    // 删除所有文件
    files.forEach(path => fs.unlinkSync(`${rootPath}/${path}`));
    return files;
  } catch (error) {
    console.error(error);
  }
}

/**
 * 判断文件或目录是否存在
 * @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 {string} path 要删除的目录路径 (本地路径) 
 * @param {boolean} recursive 是否递归删除目录。如果为 true,则删除该目录和该目录下的所有子目录以及文件
 */
function rmdirSync(path, recursive=false){
  if(isExist(path)){                       // 判断目录是否存在
    fs.rmdirSync(path, recursive);            // 如果存在就删除
  }
}

/**
 * 为文件名添加编号后缀
 * @param {*} fileName      原文件名
 * @param {*} suffixNumber  编号
 * @param {*} suffixLen     编号位数
 */
function addSuffixNumber(fileName, suffixNumber, suffixLen) {
    // 获取文件名与扩展名
    let extension = '';  
    let index = fileName.lastIndexOf('.');  
    if (index !== -1) {  
        extension = fileName.substring(index);  
        fileName = fileName.substring(0, index);  
    }  
    // 计算后缀序号的位数  
    let suffixLength = Math.floor(Math.log10(suffixLen)) + 1;
    // 对后缀编号进行高位补零  
    let paddedSuffixNumber = String(suffixNumber).padStart(suffixLength, '0');  
    // 返回新的文件名  
    return `${fileName}_${paddedSuffixNumber}${extension}`;  
}

/**
 * 高位补零
 * @param {*} num       需要补零的序号
 * @param {*} count     序号所在序列中,的总数。用于计算要补几位
 * @param {*} suffixStr 自定补啥字符
 */
function padNumber(num, count=10, suffixStr='0') {
  // 计算后缀序号的位数  
  let suffixLength = Math.floor(Math.log10(count)) + 1;
  // 对后缀编号进行高位补零  
  return String(num).padStart(suffixLength, suffixStr);  
}

/**
 * 解析文件路径返回:{路径、全名、文件名、扩展名}
 * @param {*} filePath 文件路径
 */
function getFileInfo(filePath) {  
  // 获取文件夹路径  
  const folderPath = filePath.substring(0, filePath.lastIndexOf('/') + 1);
  // 获取文件名  
  const fileName = filePath.substring(folderPath.length, filePath.lastIndexOf('.'));
  // 获取扩展名  
  const fileExtension = filePath.substring(filePath.lastIndexOf('.') + 1);
  return {  
    path: folderPath,  
    fullName: `${fileName}.${fileExtension}`,
    name: fileName,  
    extension: fileExtension  
  };  
}

/**
 * 读取本地文件内容。单个文件大小上限为100M。
 * @param {*} filePath 文件路径。要读取的文件的路径 (本地路径)
 * @param {*} encoding 指定读取文件的字符编码,如果不传 encoding,则以 ArrayBuffer 格式读取文件的二进制内容
 * @param {*} position 默认从0开始读取。左闭右开区间 [position, position+length)。有效范围:[0, fileLength - 1]。单位:byte
 * @param {*} length   指定文件的长度,如果不指定,则读到文件末尾。有效范围:[1, fileLength]。单位:byte
 */
function readFileSync(filePath, encoding='', position=0, length) {  
  return fs.readFileSync(filePath, encoding, position, length);
}

/**
 * 写文件
 * @param {*} filePath 文件路径。要写入的文件路径 (本地路径)
 * @param {*} data 要写入的文本或二进制数据
 * @param {*} encoding 指定写入文件的字符编码。默认 binary
 */
function writeFileSync(filePath, data, encoding='binary') {  
  fs.writeFileSync(filePath, data, encoding);
}

module.exports = {
  fs,
  chooseMediaZip,
  chooseMedia,
  chooseFile,
  ls,
  unlinkSync,
  clearDir,
  clearDirSync,
  clearFilesSync,
  isExist,
  mkdir,
  rmdirSync,
  addSuffixNumber,
  padNumber,
  readFileSync,
  writeFileSync
};
12-08 10:20