24 fspath 与 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;
    // 按 code 映射业务错误
  }

  throw error;
} finally {
  // 只清理已经确认归属于本任务的资源
}

上线前检查

[ ] 所有路径根由配置提供并解析为绝对路径
[ ] 上传目录与源码目录分离
[ ] 单文件、总大小、数量、路径长度均有限制
[ ] 大文件复制使用 pipeline
[ ] 错误后不会等待永不出现的 end/finish
[ ] 句柄和 Stream 有明确所有者
[ ] 半成品使用临时名
[ ] 客户端中断能停止可取消工作
[ ] 清理器有固定根、深度限制和 Dry Run
[ ] Windows 与 Ubuntu 均测试
[ ] 日志不包含 Token 和敏感文件内容