24 fs、path 与 Stream Cheatsheet
node:path
| API |
返回/作用 |
易错点 |
join(...parts) |
拼接并规范化 |
不保证绝对路径 |
resolve(...parts) |
生成绝对路径 |
后面的绝对片段会覆盖前面,不代表安全 |
relative(from,to) |
相对路径 |
常用于根目录边界检查 |
normalize(value) |
规范化点段和分隔符 |
不会阻止目录穿越 |
dirname(value) |
父目录 |
创建输出父目录 |
basename(value) |
最后一段 |
会抹去目录层级 |
extname(value) |
最后扩展名 |
a.tar.gz 只得到 .gz |
parse(value) |
root/dir/base/name/ext |
适合替换扩展名 |
format(parts) |
从部件构造路径 |
注意 ext 点号规则和 Node 版本 |
isAbsolute(value) |
是否绝对路径 |
ZIP Entry 还要使用 POSIX 语义 |
path.posix |
强制 POSIX 解析 |
ZIP 内部路径 |
path.win32 |
强制 Windows 解析 |
跨平台测试 |
path.sep |
当前平台路径分隔符 |
Windows \、POSIX / |
path.delimiter |
多路径列表分隔符 |
Windows ;、POSIX : |
node:fs/promises
| API |
用途 |
大文件 |
常见错误 |
readFile() |
读取完整文件 |
不推荐 |
ENOENT,EACCES,EISDIR |
writeFile() |
写完整内容 |
谨慎 |
EACCES,ENOSPC,EEXIST |
mkdir() |
创建目录 |
不适用 |
EACCES,ENOTDIR |
readdir() |
列出目录 |
目录巨大时谨慎 |
ENOENT,ENOTDIR |
stat() |
跟随链接查看信息 |
适用 |
ENOENT |
lstat() |
查看链接自身 |
适用 |
ENOENT |
copyFile() |
复制文件 |
OS 实现,仍需错误处理 |
ENOSPC,EACCES |
rename() |
重命名/移动 |
高效 |
EXDEV,EPERM |
unlink() |
删除文件 |
适用 |
ENOENT,EPERM |
rm() |
删除文件/目录 |
适用 |
EPERM,EBUSY |
open() |
打开 FileHandle |
适用 |
依 flag 而定 |
mkdtemp() |
唯一临时目录 |
适用 |
ENOENT,EACCES |
realpath() |
解析真实路径 |
适用 |
ENOENT,ELOOP |
打开标志
r 只读,必须存在
r+ 读写,必须存在
w 写入,创建或截断
wx 排他写,存在则失败
a 追加
ax 排他追加
Stream 类型
| 类型 |
输入/输出 |
示例 |
| Readable |
只提供数据 |
File ReadStream、HTTP req |
| Writable |
只接收数据 |
File WriteStream、HTTP res |
| Duplex |
两侧都存在,可相对独立 |
Socket |
| Transform |
输入转换为输出 |
gzip、计数器 |
| PassThrough |
原样通过 |
进度统计 |
Readable 事件
| 事件 |
含义 |
失败后保证? |
data |
消费一个 Chunk |
否 |
readable |
可能可读取 |
否 |
end |
数据已全部消费 |
出错后通常不出现 |
error |
读取失败 |
需要处理 |
close |
资源关闭 |
不代表成功 |
Writable 事件
| 事件 |
含义 |
失败后保证? |
drain |
缓冲恢复,可继续写 |
目标先失败则不出现 |
finish |
end() 后数据处理完 |
出错后不保证出现 |
error |
写入失败 |
需要处理 |
close |
资源关闭 |
不代表成功 |
记忆:
Readable 成功结束看 end
Writable 成功结束看 finish
资源关闭看 close
整体传输用 pipeline 的 Promise
推荐 Pipeline
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
await pipeline(
createReadStream(sourcePath),
createWriteStream(targetPath),
);
支持取消:
await pipeline(input, output, {
signal: controller.signal,
});
Pipeline 失败后仍需:
删除半成品
更新任务状态
记录阶段日志
清理业务临时目录
路径安全最小检查
拒绝绝对路径
拒绝 NUL
拒绝 . 和 .. 路径段
按 ZIP POSIX 语义解析 Entry
构造本地绝对目标
使用 path.relative 检查根边界
拒绝链接和重复 Entry
限制长度、数量和实际字节
错误处理模板
try {
await operation();
} catch (error: unknown) {
if (
error instanceof Error &&
"code" in error
) {
const code = (error as NodeJS.ErrnoException).code;
}
throw error;
} finally {
}
上线前检查
[ ] 所有路径根由配置提供并解析为绝对路径
[ ] 上传目录与源码目录分离
[ ] 单文件、总大小、数量、路径长度均有限制
[ ] 大文件复制使用 pipeline
[ ] 错误后不会等待永不出现的 end/finish
[ ] 句柄和 Stream 有明确所有者
[ ] 半成品使用临时名
[ ] 客户端中断能停止可取消工作
[ ] 清理器有固定根、深度限制和 Dry Run
[ ] Windows 与 Ubuntu 均测试
[ ] 日志不包含 Token 和敏感文件内容