社区/学习指南/小程序·云开发高级教程

文件系统的操作

11.2.1 读取云函数服务端的文件

通过 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,
  });
};

11.2.2 文件操作模块介绍

上面的案例使用到了 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 进行文件的增删改查等的操作,这些模块知识依然派的上用场。

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

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

11.2.4 云函数与 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。

本文出自 李东bbsky