评论

微信小程序使用自定义目录(文件路径)进行下载/保存 案例(fail permission denied 解决方案)

小程序下载文件到自定义目录中

场景描述

最近项目中有一个需要把网络文件下载下来保存到本地,然后对下载的文件进行读取,待文件不再使用后把文件进行删除的需求。当然也类似的需求还有很多,比如把小程序中的临时图片/文件永久保存下来等等,都是对文件操作的典型场景。

常见问题

在以上场景的实现过程中可能会遇到各式各样的问题,以下是比较常见的几个:

  1. 不清楚文件应该保存到哪个目录下。
  2. fail permission denied 文件权限问题。
  3. 使用同步函数不清楚怎么获取执行结果。

API提炼

提到文件操作我们会自然而然地想到了API中的FileSystemManager相关的API,我这里用到的函数有以下几个:

下载函数

wx.downloadFile(Object object)

异步函数:

FileSystemManager.access(Object object)

FileSystemManager.mkdir(Object object)

同步函数:

FileSystemManager.accessSync(string path)

FileSystemManager.mkdirSync(string dirPath, boolean recursive)

我对同样的业务逻辑分别分别尝试了异步和同步的两种不同的方案,下面我们用一个最简单的下载保存到本地的案例来切入正题。

案例实践

一:获取正确的文件目录路径

当然在保存文件之前我们先要解决一个小问题,那就是我们要保存到哪里?也就是我们自定义的目录。这里我们简单命名其为

//自定义缓存文件根路径
var rootPath = "......";

(当然你也可以命名成其他名字)
变量名字可以随便写,不过自定义路径可不能随便写,也不可以在下载的时候直接给一个path路径,否者就会抛出没有权限或找不到文件的异常。所以开发者并不是可以随意的决定自定义文件的路径,这里就不卖关子了,小程序API中有一个很容易被忽略的API,
wx.env.USER_DATA_PATH是专门获取文件系统中的用户目录路径的常量值。

这就是我们在小程序中合法的可操作文件的根目录路径:

rootPath = wx.env.USER_DATA_PATH;

好了到目前为止我们已经知道了我们该往里存储文件了。

定义一下我们下载文件的缓存目录

var cachePath = rootPath+"/cache";

也就是说之后我们下载的文件都会保存到 /cache 目录下,我们在之后的代码中用到的目录路径均为此路径。

二:异步函数实现方案

我们先来用异步函数来实现一下下载保存的流程。

1 下载文件之前我们首先要判断当前目录是否存在,如果目录不存在我们就直接下载文件到该目录下就会抛出 fail permission denied

这是判断目录存在的代码

  access() {
    return new Promise(function(resolve, reject) {
      let fm = wx.getFileSystemManager();
      fm.access({
        path: cachePath,
        success: function(res) {
          resolve();
        },
        fail: function(err) {
          resolve(err);
        }
      });
    });
  },

2 如果目录真实存在那我们当然可以直接使用,如果目录不存在则需要开发者自己创建目录。

  mkdir(){
    return new Promise(function(resolve, reject) {
      let fm = wx.getFileSystemManager();
      fm.mkdir({
        dirPath: cachePath,
        recursive: true,
        success: function(res) {
          resolve();
        },
        fail: function(err) {
          resolve(err);
        }
      });
    });
  },

代码执行完之后我可以验证一下目录是否如我们所愿被创建出来。

开发工具右上角的详情–>基本信息–>文件系统–>当前小程序文件系统根目录

点击usr文件夹进入根目录

执行完上面代码之后我们可以看一下 cache 文件夹确实已经存在了

3 目录也存在了,万事具备只欠下载了

  downloadFile() {
    let fileUrl = 'https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg';
    wx.downloadFile({
      url: fileUrl,
      filePath: cachePath + '/temp.png',
      success: function(res) {
        console.log('downloadFile  success', res);
      },
      fail: function(err) {
        console.log('downloadFile  fail', err);
      }
    });
  },

那执行完下载的代码之后我们再来看看cache目录

这就是我们刚刚下载的图片。
ok,异步的整个下载和文件创建流程就走完了。接下来我们来瞅瞅同步流程中有哪些需要我们注意的。

三: 同步方案

同步方案和异步方案的流程大体一致,都是先判断文件目录是否存在,若不存在则创建目录,存在则执行下载逻辑。

使用同步函数需要特别注意的是怎么去判断函数的执行结果,由于

FileSystemManager.accessSync(string path)

FileSystemManager.mkdirSync(string dirPath, boolean recursive)

这两个同步函数没有直接给出任何的返回结果。那我们怎么知道目录是否存在、目录是否被创建成功了呢?

这里我们需要剑走偏锋一下,即利用同步函数抛出的异常来判断结果。
我们直接来看代码

  accessSync() {
    return new Promise(function(resolve, reject) {
      let fm = wx.getFileSystemManager();
      try {
        fm.accessSync(cachePath);
        resolve();
      } catch (err) {
        resolve(err);
      }
    });
  },

  mkdirSync() {
    return new Promise(function(resolve, reject) {
      let fm = wx.getFileSystemManager();
      try {
        fm.mkdirSync(cachePath, true);
        resolve();
      } catch (err) {
        resolve(err);
      }
    });
  },

可以看到我们的代码中多了 try catch 的代码结构,因为同步方法中没有直接返回给我们可用的信息,那我们可以认为同步函数正常执行完的结果为true或success,而进入 catch 后则结果为false或fail亦或根据具体异常具体处理。我们利用同步函数来走一遍下载保存的流程。

    // 同步函数流程
    this.accessSync().then(function (err) {

      if (err) {
        return that.mkdirSync();
      }
    }).then(function (err) {

      if (!err) {
        that.downloadFile()
      }
    });

可以看到我们利用同步函数下载的图片。

总结时刻

我们分别使用异步和同步函数完成了目录的创建和文件的下载等流程。在这个过程中我们特别需要注意几点:

  1. 操作文件的根目录是以 wx.env.USER_DATA_PATH 开头的。
  2. 使用自定义目录时一定主要不可直接使用,需要增加 判断目录存在、创建目录 两个步骤。
  3. 使用同步函数时的执行结果是通过抓取同步函数抛出的异常来进行判断的。在没有给出直接结果的时候要学会利用异常信息来达到目的。
  4. 在创建目录时如果该目录存在同样会抛出异常(fail file already exists),这时按照success逻辑继续往下执行即可。
  5. 异步方案中介绍了目录查看的步骤和方法,可自行验证。其中usr目录是开发者自定义目录根节点,tmp目录是小程序默认的缓存根节点。

结尾

叙述若有不对或不严谨之处还请不吝指正。

最后一次编辑于  2019-10-31  
点赞 7
收藏
评论

8 个评论

  • Héctor2
    Héctor2
    2019-12-31

    同步的接口貌似没有必要再包一层promise, 直接try catch就ok了吧

    2019-12-31
    赞同 1
    回复 3
    • down
      down
      2019-12-31
      可以直接try catch,我这里为了强调先判断目录存在再创建目录,所以是把判断目录是否存在和创建目录分开来处理了,其实直接放在一个try catch 里更简单直接点
      2019-12-31
      回复
    • Héctor2
      Héctor2
      2019-12-31回复down
      soga
      2019-12-31
      回复
    • K
      K
      03-03回复down
      ios 下载过后再哪儿找到文件?
      03-03
      回复
  • Jueang
    Jueang
    10-14

    请问这种写法,有没有遇到在电脑上可以下载成功,但是在手机上提示“downloadFile:fail move to savedFilePath fail”

    10-14
    赞同
    回复
  • 栞
    08-10

    大佬,想问个问题,

    有一组数据,我从别的地方拿到的,这一组里每一块都是一张图片和几个固定字段,然后拿到之后,我又要丢到数据库,这怎么和别的固定字段一起丢给后台,我看了看微信小程序图片上传是单独的

    08-10
    赞同
    回复
  • fanerge
    fanerge
    07-13
    // 感觉这样判断,cachePath 是否存在,会比较方便
    export const getDownLoadFilePath = (function getDownLoadFilePath() {
      let cachePath = `${wx.env.USER_DATA_PATH}/downloads`
      let fm = getFileSystemManager()
      try {
        // 访问成功则存在
        fm.accessSync(cachePath)
      } catch (error) {
        // 不存在则新建
        fm.mkdirSync(cachePath, true)
      }
      return cachePath
    })()
    
    07-13
    赞同
    回复 1
    • fanerge
      fanerge
      07-13
      本地用户文件:小程序通过接口把本地临时文件缓存后产生的文件,允许自定义目录和文件名。跟本地缓存文件共计,普通小程序最多可存储 10MB,游戏类目的小程序最多可存储 50MB。
      07-13
      回复
  • Lemon
    Lemon
    03-24

    有没有遇到小程序小程序iOS端重命名rename:fail permission denied没有权限的问题?安卓手机重命名成功。

    03-24
    赞同
    回复
  • oldjang
    oldjang
    02-28

    看了你这个教程做了一个下载的功能,不过有点问题,能不能请教下?

    想给你私信来着,不能发,不知道什么原因。

    02-28
    赞同
    回复 9
    • down
      down
      02-28
      请教不敢,可以共同探讨
      02-28
      回复
    • oldjang
      oldjang
      02-28回复down
      按照你的方法我新建了下载目录,然后可以正常下载,官方给出的10M到底是单次下载,还是下载以后我新建的这个下载目录总大小不能超过10M,因为我这里的需求,用户可能会下载多个文件,如果总大小不能超过10M,那这个下载功能意义就不大了
      02-28
      回复
    • oldjang
      oldjang
      02-28
      另外第二个问题,我有点不理解,我直接用wx.downloadFile下载保存,当目录总大小超过10M的时候,再下载就会报错,下载会失败。
      但是我在wx.downloadFile的回调里用manager.saveFile保存到本地,我在目录里面放了100多M的文件,继续下载,会报:log appendFile err fs_appendFile:fail the maximum size of the file storage limit is exceeded这个错误,但是下载还是能成功
      02-28
      回复
    • down
      down
      02-28回复oldjang
      如果你这边需要大文件或多文件的话,不建议使用上面文章中提到的直接给出下载目录的方式,可以下载成功后直接保存 saveFile 保存到其他地方比较好。
      02-28
      回复
    • oldjang
      oldjang
      02-28回复down
      也就是说我上面说的:在wx.downloadFile的回调里面执行manager.saveFile。这样子我试过,可以随便次数下载,总内存没有10M的限制,但是单次下载还是不能超过10M。
      不过这个方法又延伸出了另外一个问题,总大小超过10M以后还可以继续下载,不过,小程序会一直提示:log appendFile err fs_appendFile:fail the maximum size of the file storage limit is exceeded。不知道会不会有什么影响
      02-28
      回复
    查看更多(4)
  • CallMeSoul
    CallMeSoul
    01-14

    本地临时文件怎么转 base64呢? 难道要先上传再下载到操作目录,再转?

    01-14
    赞同
    回复 2
    • down
      down
      01-14
      可以用FileSystemManager 相关的方法来处理,当然也可以自己写一个转换的工具方法,直接转。顺便问一下这边的临时文件是打包的静态文件还是程序运行过程中自己获取或产生的文件
      01-14
      回复
    • CallMeSoul
      CallMeSoul
      01-14回复down
      有程序运行的也有自己产生的,发现有filemange的readFile就可以了,一开始writeFile要权限readFile也要权限,发现readFile不用
      01-14
      1
      回复
  • 百熟优质果~运营合作
    百熟优质果~运营合作
    2019-11-27

    同步方法亲测有效,异步不知道怎么使用,其他人自测哈

    2019-11-27
    赞同
    回复
登录 后发表内容