3

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。

使用 Node.js 很大一部分的使用场景是,写个小脚本批量处理一下重复无聊的任务。比如说:

  1. 获取所有 json 内容,然后过滤出想要的数据。
  2. 把所有图片压缩一下,然后放入压缩包中。
  3. 把字体文件提取压缩一下。
  4. 把所有文件重命名一下。

这些任务中我们都需要访问文件系统,之前在浏览器环境中其实是没有文件系统概念的,今天我们来学一哈。

Node.js 文件模块

我们可以使用 fs 来操作文件系统。

const fs = require('fs');

回调方式

所有的文件系统操作都具有同步的(10.x 有,历史也有)、回调的(10.x 有,历史也有)、以及基于 promise(12.x 开始就有了,历史没有) 的形式。

同步

同步的形式会阻止 Node.js 事件循环和进一步的 JavaScript 执行,直到操作完成
异常会被立即地抛出,可以使用 try…catch 处理,也可以冒泡。

const fs = require('fs');

try {
  fs.unlinkSync('文件');
  console.log('已成功地删除文件');
} catch (err) {
  // 处理错误
}

异步

异步的形式总是把完成回调作为其最后一个参数
传给完成回调的参数取决于具体方法,但第一个参数总是预留给异常。 如果操作被成功地完成,则第一个参数会为 nullundefined

const fs = require('fs');

fs.unlink('文件', (err) => {
  if (err) throw err;
  console.log('已成功地删除文件');
});

promise

基于 Promise 的操作会返回 Promise(当异步操作完成时会被解决)。

const fs = require('fs/promises');

(async function(path) {
  try {
    await fs.unlink(path);
    console.log(`已成功地删除文件 ${path}`);
  } catch (error) {
    console.error('出错:', error.message);
  }
})('文件');

文件路径

支持 字符串(常用Path库就对了)、Buffer(为了兼容)、URL(需要 new URL('file:///C:/' 为入参。可以用相对路径 (process.cwd() 可以查看当前路径),绝对路径。

字符串形式

在 Windows 上,Node.js 遵循独立驱动器工作目录的概念。 当使用没有反斜杠的驱动器路径时,可以观察到此行为。 例如, fs.readdirSync('C:\\') 可能会返回与 fs.readdirSync('C:') 不同的结果。 详见此 MSDN 页面

image.png

一般我们使用 Path 库:

  1. 可以直接看一下最终地址
  2. 可以正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是""。

image.png

URL 形式

image.png

// 在 Windows 上:

// - 带有主机名的 WHATWG 文件的 URL 会转换为 UNC 路径。
// file://主机名/文件 => 主机名文件
fs.readFileSync(new URL('file://主机名/文件'));

// - 带有驱动器号的 WHATWG 文件的 URL 会转换为绝对路径。
// file:///C:/文件 => C:文件
fs.readFileSync(new URL('file:///C:/文件'));

API

目录 dir 类

fs.readdirSync() 同步获取目录
fs.readdir() 异步获取目录

当调用 fs.readdir()fs.readdirSync()withFileTypes 选项设置为 true)时,则生成的数组会使用 fs.Dirent 对象(而不是字符串或 Buffer)填充。

fs.mkdir(path[, options], callback) 异步地创建目录。回调会传入可能的异常、以及创建的第一个目录的路径(如果 recursivetrue), (err, [path])。可选的 options 参数可以是整数(指定 mode(权限和粘滞位))、或对象(具有 mode 属性和 recursive 属性(指示是否要创建父目录))。 当 path 是已存在的目录时,调用 fs.mkdir() 仅在 recursive 为 false 时才会导致错误。
fs.mkdirSync(path[, options]) 同步地创建目录。 返回 undefined,或创建的第一个目录的路径(如果 recursivetrue)。 这是 fs.mkdir() 的同步版本。

监听 fs.FSWatcher 类

fs.watch() 每当指定监视的文件被修改时,会触发 'change' 事件。

// 使用 fs.watch()监听器的示例。
fs.watch('./tmp', { 
        encoding: 'buffer' 
    }, (eventType, filename) => {
  if (filename) {
    console.log(filename);
  }
});

读取流 fs.ReadStream 类

使用 fs.createReadStream() 函数创建并返回的 fs.ReadStream 实例。

fs.createReadStream(path[, options])

写入流 fs.WriteStream 类

使用 fs.createWriteStream() 函数创建并返回的 fs.WriteStream 实例。

fs.createWriteStream(path[, options])

,.

文件信息 fs.Stats 类

fs.Stats 对象提供了关于文件的信息。在遍历的时候我们会判断文件类型,如果是文件就打开,如果是目录就递归

fs.stat()fs.lstat()fs.fstat()、以及它们的同步方法返回的对象都是此类型。 如果传给这些方法的 options 中的 bigint 为 true,则数值会是 bigint 型而不是 number 型,并且该对象还会包含额外的纳秒级精度的属性(以 Ns 为后缀)。

  1. stats.isDirectory() 如果 fs.Stats 对象描述文件系统目录,则返回 true
  2. stats.isFile() 如果 fs.Stats 对象描述普通的文件,则返回 true
  3. stats.size 文件的大小(以字节为单位)。
  4. stats.mtime 表明上次修改此文件的时间戳。
    stats.mtimeMs 表明上次修改此文件的时间戳,以 POSIX 纪元以来的毫秒数表示。
  5. stats.birthtime 表示此文件的创建时间的时间戳。
    stats.birthtimeMs 表明此文件的创建时间的时间戳,以 POSIX 纪元以来的毫秒数表示。

文件属性的时间值#

中英对照

atimeMsmtimeMsctimeMsbirthtimeMs 属性是保存相应时间(以毫秒为单位)的数值。 它们的精度取决于平台。 当将 bigint: true 传给生成该对象的方法时,属性将会是 bigint 型,否则它们将会是数字型

atimeNsmtimeNsctimeNsbirthtimeNs 属性是保存相应时间(以纳秒为单位)的 bigint。 仅当将 bigint: true 传给生成该对象的方法时,它们才会出现。 它们的精度取决于平台。

atimemtimectimebirthtime 是对应时间的 Date 对象。 Date 值和数值没有关联性。 赋值新的数值、或者改变 Date 的值,都将不会影响到对应的属性。

stat 对象中的时间具有以下语义:

  • atime "访问时间" - 上次访问文件数据的时间。由 mknod(2)utimes(2)read(2) 系统调用更改。
  • mtime "修改时间" - 上次修改文件数据的时间。由 mknod(2)utimes(2)write(2) 系统调用更改。
  • ctime "更改时间" - 上次更改文件状态(修改索引节点数据)的时间。由 chmod(2)chown(2)link(2)mknod(2)rename(2)unlink(2)utimes(2)read(2)write(2) 系统调用更改。
  • birthtime "创建时间" - 创建文件的时间。当创建文件时设置一次。 在不支持创建时间的文件系统上,该字段可能改为保存 ctime1970-01-01T00:00Z(即 Unix 纪元时间戳 0)。 在这种情况下,该值可能大于 atimemtime。 在 Darwin 和其他的 FreeBSD 衍生系统上,也可能使用 utimes(2) 系统调用将 atime 显式地设置为比 birthtime 更早的值。

在 Node.js 0.12 之前,在 Windows 系统上 ctime 保存 birthtime。 从 0.12 开始, ctime 不再是“创建时间”,而在 Unix 系统上则从来都不是。

获取权限访问 fs.access、fs.accessSync

除了判断权限,还可以判断是否存在。不过我们一般可以直接 fs.open() 直接去处理 err 。通常,仅在不直接使用文件时(例如当其可访问性是来自其他进程的信号时),才检查文件的可访问性。

在 Windows 上,目录上的访问控制策略(ACL)可能会限制对文件或目录的访问。 但是, fs.access() 函数不检查 ACL,因此即使 ACL 限制用户读取或写入,也可能报告路径是可访问的。

fs.access(path[, mode], callback)

测试用户对 path 指定的文件或目录的权限。 mode 参数是一个可选的整数,指定要执行的可访问性检查。 查看文件可访问性的常量了解 mode 的可选值。 可以创建由两个或更多个值按位或组成的掩码(例如 fs.constants.W_OK | fs.constants.R_OK)。

文件

  1. fs.open(path[, flags[, mode]], callback) 异步地打开文件。
    fs.openSync(path[, flags, mode]) 同步打开文件
  2. fs.read(fd, buffer, offset, length, position, callback)fd 指定的文件中读取数据,写入到 buffer 中。
    fs.readSync(fd, buffer, offset, length, position) 同步版本,从指定 fs 中读取数据
    fs.readFile(path[, options], callback) 异步地读取文件的全部内容。
    fs.readFileSync(path[, options]) 同步版本,读取文件全部内容
  3. fs.appendFile(path, data[, options], callback) 异步地追加数据到文件,如果文件尚不存在则创建文件。 data 可以是字符串或 Buffer
    fs.appendFileSync(path, data[, options]) 同步地将数据追加到文件,如果文件尚不存在则创建该文件。 data 可以是字符串或 Buffer

    // 直接针对文件追加
    fs.appendFileSync('文件.txt', '追加的数据', 'utf8');
    
    // 先打开文件,然后针对文件描述符追加
    fd = fs.openSync('文件.txt', 'a');
    fs.appendFileSync(fd, '追加的数据', 'utf8');
  4. fs.copyFile(src, dest[, mode], callback) 异步地将 src 拷贝到 dest。 默认情况下,如果 dest 已经存在,则覆盖它。 除了可能的异常,回调函数没有其他参数。 Node.js 不保证拷贝操作的原子性。 如果在打开目标文件用于写入后发生错误,则 Node.js 将尝试删除目标文件。
    fs.copyFileSync(src, dest[, mode]) 同步
  5. fs.rename(oldPath, newPath, callback) 异步地把 oldPath 文件重命名为 newPath 提供的路径名。 如果 newPath 已存在,则覆盖它。 除了可能的异常,完成回调没有其他参数。
    fs.renameSync(oldPath, newPath) 同步版本,文件重命名
  6. fs.write(fd, buffer[, offset[, length[, position]]], callback) 写入 bufferfd 指定的文件。不等待回调就对同一个文件多次使用 fs.write()不安全的。 对于这种情况,建议使用 fs.createWriteStream()

示例

获取目录下所有图片,并上传到服务器

const FormData = require('form-data');
const fetch = require('node-fetch');
var fs = require('fs');
var path = require('path');
var dirPath = 'cdn-transform'
var filePath = path.resolve(`./node-upload-img/${dirPath}`);
//文件遍历方法

function fileDisplay(filePath){
    fs.readdir(filePath,function(err,files){
        if(err){
            console.warn(err)
        }else{
            files.forEach(function(filename){
                var filedir = path.join(filePath, filename);
                var buffer = fs.readFileSync(filedir)
                var formData = new FormData();
                var fileName = `${filename}`
                var url =  `www.lilnong.top/upload/fe-up/${dirPath}/${fileName}`;
                formData.append('file', buffer, `${fileName}`);
                fetch('http://www.lilnong.top/upload',{
                    headers: formData.getHeaders(),
                    method: 'post',
                    body: formData
                })
                .then(v=>v.text())
                .then(v=>{
                    console.log('http://www.lilnong.top/upload', url, v)
                })
            })
        }
    });
}
fileDisplay(filePath)

递归遍历所有json

const readDir = (entry, paths = []) => {
    const dirInfo = fs.readdirSync(entry);
    dirInfo.forEach(item=>{
        const location = path.join(entry,item);
        const info = fs.statSync(location);
        if(info.isDirectory()){
            console.log(`dir:${location}`);
            readDir(location, [item]);
        }else{
            if(/.json$/.test(location)){
                readFile(location, paths)
            }
        }
    })
}
console.log('__dirname', __dirname)
readDir(__dirname);

function readFile(path, pathKey){
    return console.log(path, pathKey);
})

微信公众号:前端linong

clipboard.png

参考文献

  1. 前端培训目录、前端培训规划、前端培训计划
  2. http://nodejs.cn/api/fs.html#fs_file_paths

linong
29.2k 声望9.5k 粉丝

Read-Search-Ask