阅读(2222) (0)

云开发 文件系统的操作

2020-07-20 13:34:55 更新

一、读取云函数服务端的文件

通过Nodejs的模块,我们可以实现云函数与服务端的文件系统进行一定的交互,比如在前面我们就使用云函数将服务端的图片先使用fs.createReadStream读取,然后上传到云存储。Nodejs的文件处理能力让云函数也能操作服务端的文件,比如文件查找、读取、写入乃至代码编译。

还是以nodefile云函数为例,使用微信开发者工具在nodefile云函数下新建一个文件夹,比如assets,然后在assets里放入demo.jpg图片文件以及index.html网页文件等,目录结构如下所示:

nodefile // 云函数目录
├── config //config文件夹
│   └── config.js //config.js文件
├── assets //assets文件夹
│   └── demo.jpg
│   └── index.html
└── index.js
└── config.json 
└── package.json 

然后再在nodefile云函数的index.js里输入以下代码,使用fs.createReadStream读取云函数目录下的文件:

const cloud = require('wx-server-sdk')
const fs = require('fs')
const path = require('path')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})
exports.main = async (event, context) => {
  const fileStream = fs.createReadStream(path.join(__dirname, './assets/demo.jpg'))
  return await cloud.uploadFile({
    cloudPath: 'demo.jpg',
    fileContent: fileStream,
  })
}

二、文件操作模块介绍

上面的案例使用到了Nodejs文件处理必不可少的fs模块,fs 模块: 可以实现文件目录的创建、删除、查询以及文件的读取和写入:

  • 读取文件,fs.readFile()

  • 创建文件,fs.appendFile()、fs.open()、fs.writeFile()

  • 更新文件,fs.appendFile()、fs.writeFile()

  • 删除文件,fs.unlink()

  • 重命名文件,fs.rename()

上面只大致列举了fs模块的一些方法,关于如何使用大家可以去参考Nodejs官方技术文档,当然还有fs.Stats类,封装了文件信息相关的操作;fs.Dir类,封装了和文件目录相关的操作;fs.Dirent类,封装了目录项的相关操作等等。

Nodejs fs模块中的方法都有异步和同步版本,比如读取文件内容的方法有异步的 fs.readFile() 和同步的 fs.readFileSync()。异步的方法函数最后一个参数为回调函数callback,回调函数的参数里都包含了错误信息(error),通常建议大家使用异步方法,性能更高,速度更快,而且没有阻塞。

操作文件之时,不可避免的都会使用到path模块,path 模块: 提供了一些用于处理文件路径的API,它的常用方法有:

  • path.basename(),获取路径中文件名;

  • path.delimiter(),返回操作系统中目录分隔符;

  • path.dirname(),获取路径中目录名;

  • path.extname(),获取路径中的扩展名;

  • path.join(),路径结合、合并;

  • path.normalize(),路径解析,得到规范化的路径格式

Node读取文件有两种方式,一是利用fs.readFile来读取,还有一个是使用流fs.createReadStream来读取。如果要读取的文件比较小,我们可以使用fs.readFile,fs.readFile读取文件是将文件一次性读取到本地内存。而如果读取一个大文件,比如当文件超过16M左右的时候(文件越大性能也就会越大),一次性读取就会占用大量的内存,效率比较低,这个时候需要用流来读取。流是将文件数据分割成一段段的读取,可以控制速率,效率比较高,不会占用太大的内存。无论文件是大是小,我们都可以使用fs.createReadStream来读取文件。

为了让大家看的更加明白一些,我们再看下面这个案例,使用云函数来读取云函数在云端的目录下有哪些文件(也就是列出云函数目录下的文件清单):

const cloud = require('wx-server-sdk')
const fs = require('fs')
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV
  })
exports.main = async (event, context) => {
  const funFolder = '.';//.表示当前目录
  fs.readdir(funFolder, (err, files) => {
    files.forEach(file => {
        console.log(file);
    });
  });
}

上面就用到了fs.readdir()方法,以异步的方式读取云函数在服务端的目录下面所有的文件。

我们需要注意的是,云函数的目录文件只有读权限,没有写权限,我们不能把文件写入到云函数目录文件里,也不能修改或删除里面的文件。但是每个云函数实例都在 /tmp 目录下提供了一块 512MB 的临时磁盘空间用于处理单次云函数执行过程中的临时文件读写需求,我们可以用云函数来对 /tmp 进行文件的增删改查等的操作,这些模块知识依然派的上用场。

三、使用云函数操作临时磁盘空间

我们还可以结合结合Nodejs文件操作的知识,使用云函数在 /tmp 临时磁盘空间创建一个txt文件,然后将创建的文件上传到云存储。

const cloud = require('wx-server-sdk')
const fs = require('fs')
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV
  })
exports.main = async (event, context) => {
  //创建一个文件
  const text = "云开发技术训练营CloudBase Camp. ";
  await fs.writeFile("/tmp/tcb.txt", text, 'utf8', (err) => { //将文件写入到临时磁盘空间
    if (err) console.log(err);
    console.log("成功写入文件.");
  });
  //将创建的txt文件上传到云存储
  const fileStream = await fs.createReadStream('/tmp/tcb.txt')
  return await cloud.uploadFile({
    cloudPath: 'tcb.txt',
    fileContent: fileStream,
  })
}

上面创建文件使用的是fs.writeFile()方法,我们也可以使用fs.createWriteStream()的方法来处理:

  const writeStream = fs.createWriteStream("tcb.txt");
  writeStream.write("云开发技术训练营. ");
  writeStream.write("Tencent CloudBase.");
  writeStream.end();

注意,我们创建文件的目录也就是临时磁盘空间是一个绝对路径/tmp,而不是云函数的当前目录.,也就是说临时磁盘空间独立于云函数,不在云函数目录之下。

临时磁盘空间有512M,可读可写,因此我们可以在云函数的执行阶段做一些文件处理的周转,但是这块临时磁盘空间在函数执行完毕后可能被销毁,不应依赖和假设在磁盘空间存储的临时文件会一直存在。

四、云函数与Buffer

Nodejs Buffer类的引入,让云函数也拥有操作文件流或网络二进制流的能力,云函数通过downloadFile接口从云存储里下载的数据类型就是Buffer,以及uploadFile接口可以将Buffer数据上传到云存储。Buffer 类在全局作用域中,因此我们无需使用 require('buffer')引入。

使用Buffer还可以进行编码转换,比如下面的案例是将云存储的图片下载(这个数据类型是Buffer)通过buffer类的toString()方法转换成base64编码,并返回到小程序端。使用开发者工具新建一个downloading的云函数,然后在index.js里输入以下代码:

const cloud = require('wx-server-sdk')
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV,
})
exports.main = async (event, context) => {
    const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/cloudbase/1576500614167-520.png' 
  //换成你云存储内的一张图片的fileID,图片不能过大
    const res = await cloud.downloadFile({
        fileID: fileID,
    })
    const buffer = res.fileContent
    return buffer.toString('base64')
}

在小程序端创建一个事件处理函数getServerImg()来调用云函数,将云函数返回的数据(base64编码的图片)赋值给data对象里的img,比如在一个页面的js文件里输入以下代码:

data: {
  img:""
},
getServerImg(){
    wx.cloud.callFunction({
        name: 'downloadimg',
        success: res => {
            console.log("云函数返回的数据",res.result)
                 this.setData({
                    img:res.result
                 })
        },
        fail: err => {
            console.error('云函数调用失败:', err)
      }
    })
}

在页面的wxml文件里添加一个image组件(注意src的地址),当点击button时,就会触发事件处理函数来调用云函数将获取到的base64图片渲染到页面。

<button bindtap="getServerImg">点击渲染base64图片</button>
<image width="400px" height="200px" src="data:image/jpeg;base64,{{img}}"></image>

云函数在处理图片时,将图片转成base64是有很多限制的,比如图片不能过大,返回到小程序的数据大小不能超过1M,而且这些图片最好是临时性的文件,通常建议大家把处理好的图片以云存储为桥梁,将图片处理好后上传到云存储获取fileID,然后在小程序端直接渲染这个fileID即可。

Buffer还可以和字符串String、JSON等转化,还可以处理ascii、utf8、utf16le、ucs2、binary、hex等编码,可以进行copy拷贝、concat拼接、indexOf查找、slice切片等等操作,这些都可以应用到云函数里,就不一一介绍了,具体内容可以阅读Nodejs官方技术文档。

通过云存储来进行大文件的传输从成本的角度上讲也是有必要的,云函数将文件传输给云存储使用的是内网流量,速度快零费用,小程序端获取云存储的文件走的是CDN,传输效果好,成本也比较低,大约0.18元/GB;云函数将文件发送给小程序端消耗的是云函数外网出流量,成本相对比较高,大约0.8元/GB。