07 文件权限与常见错误
1. Linux 权限基础
Linux 权限分为所有者、组和其他用户,每类包含:
r 读
w 写
x 执行;对目录表示进入/遍历
读取一个文件不仅需要文件可读,还需要对父目录具有遍历权限。创建或删除文件通常取决于父目录写权限,而不只是目标文件自身权限。
PM2 以哪个 Linux 用户启动,Node.js 就通常以哪个用户访问文件。Nginx 用户不应直接拥有全部业务存储写权限;公开静态目录和私有上传目录要分开。
2. Windows 权限
Windows 主要使用 ACL。Node.js 的 mode 参数在 Windows 上不能机械套用 Linux 权限语义;文件被杀毒软件、编辑器或其他进程占用时,还可能得到 EPERM、EBUSY。
3. 识别 NodeJS.ErrnoException
export function isErrnoException(
error: unknown,
): error is NodeJS.ErrnoException {
return error instanceof Error && "code" in error;
}
使用:
try {
await readFile(filePath);
} catch (error: unknown) {
if (isErrnoException(error)) {
switch (error.code) {
case "ENOENT":
throw new AppFileError("FILE_NOT_FOUND", error);
case "EACCES":
case "EPERM":
throw new AppFileError("FILE_ACCESS_DENIED", error);
}
}
throw error;
}
不要通过 error.message.includes("no such file") 判断;不同操作系统和 Node.js 版本的文本可能变化。
4. 高频错误码
| code | 常见原因 | 处理方向 |
|---|---|---|
ENOENT |
文件或某级父目录不存在 | 检查路径和生命周期 |
EACCES |
权限不足 | 检查运行用户和目录权限 |
EPERM |
操作不允许/Windows 占用 | 检查 ACL、占用和操作类型 |
EEXIST |
排他创建目标已存在 | 生成新名称或返回冲突 |
EISDIR |
把目录当文件 | 检查 Entry 类型 |
ENOTDIR |
路径中某段不是目录 | 检查路径冲突 |
ENOTEMPTY |
删除非空目录 | 确认是否允许递归删除 |
EMFILE |
进程打开句柄过多 | 限制并发、关闭资源 |
ENOSPC |
磁盘空间或 inode 耗尽 | 停止任务、告警、清理 |
EROFS |
只读文件系统 | 修复挂载/写入位置 |
EXDEV |
跨设备 rename | 复制成功后再删除 |
EBUSY |
资源占用 | 有界重试或等待占用释放 |
5. 重试不是万能解法
EBUSY、某些 Windows EPERM 可能短暂出现,可以少量、有退避地重试。EACCES、ENOENT、路径穿越不会因为盲目重试而恢复。重试前先判断错误是否暂时性,并设置最大次数。
6. 公开错误与日志
响应:
{
"code": "FILE_PROCESSING_FAILED",
"message": "文件处理失败",
"data": null
}
日志可以包含:
taskId、stage、relativePath、errno、syscall
不要把患者文件名、完整绝对路径和文件内容无条件返回前端。
7. umask
创建文件时最终权限会受到进程 umask 影响。不要只看到源码中的 mode: 0o666 就断言磁盘权限一定如此。生产部署应检查实际生成文件,并确保私有上传内容不被其他系统用户随意读取。
练习题
- 分别制造
ENOENT、EEXIST和EISDIR。 - 编写错误映射器,保留原始错误作为
cause。 - 在 Ubuntu 上解释目录
rwx对创建、列出和进入的影响。 - 为什么
ENOSPC后继续重试可能让系统更糟?