358 lines
9.3 KiB
JavaScript

const fs = require("fs").promises;
const path = require("path");
/**
* 文件读取操作
* @param {Object} config 配置对象
* @param {string} config.filePath 文件路径
* @param {string} config.encoding 编码方式
* @param {string} config.readMode 读取模式
* @param {string} config.flag 读取标志
* @param {number} [config.start] 起始位置
* @param {number} [config.length] 读取长度
* @returns {Promise<string|Buffer>} 文件内容
*/
async function read(config) {
const { filePath, encoding, readMode, flag, start, length } = config;
if (readMode === "all") {
return await fs.readFile(filePath, { encoding, flag });
} else if (
readMode === "start" &&
typeof start === "number" &&
typeof length === "number"
) {
// 指定位置读取
const fileHandle = await fs.open(filePath, flag || "r");
try {
const buffer = Buffer.alloc(length);
const { bytesRead } = await fileHandle.read(buffer, 0, length, start);
await fileHandle.close();
return encoding
? buffer.slice(0, bytesRead).toString(encoding)
: buffer.slice(0, bytesRead);
} catch (error) {
await fileHandle.close();
throw error;
}
} else if (readMode === "line") {
// 按行读取,暂时使用全部读取然后分行的方式
const content = await fs.readFile(filePath, {
encoding: encoding || "utf8",
flag,
});
return content.split(/\r?\n/);
} else {
// 默认使用全部读取
return await fs.readFile(filePath, { encoding, flag });
}
}
/**
* 文件写入操作
* @param {Object} config 配置对象
* @param {string} config.filePath 文件路径
* @param {string} config.content 写入内容
* @param {string} config.encoding 编码方式
* @param {string} config.flag 写入标志 ('w'=覆盖写入, 'a'=追加写入)
* @param {string|number} config.mode 文件权限 (例如: '666', '644', '755')
* @returns {Promise<void>}
*/
async function write(config) {
const { filePath, content, encoding, flag = "w", mode } = config;
try {
// 确保目录存在
await fs.mkdir(path.dirname(filePath), { recursive: true });
// 写入文件
const options = {
encoding,
flag,
mode: mode ? parseInt(mode, 8) : undefined,
};
await fs.writeFile(filePath, content, options);
} catch (error) {
if (error.code === "EPERM") {
throw new Error("没有写入权限");
}
throw error;
}
}
/**
* 文件删除操作
*/
async function delete_(config) {
const { filePath, recursive, force, targetType } = config;
// 检查文件是否存在
try {
const stats = await fs.lstat(filePath);
// 检查目标类型
if (targetType === "file" && !stats.isFile()) {
throw new Error("目标不是文件");
}
if (targetType === "directory" && !stats.isDirectory()) {
throw new Error("目标不是目录");
}
// 执行删除操作
if (stats.isDirectory()) {
await fs.rm(filePath, { recursive, force });
} else {
await fs.unlink(filePath);
}
} catch (error) {
if (error.code === "ENOENT") {
if (!force) throw new Error("文件或目录不存在");
} else {
throw error;
}
}
}
/**
* 文件管理操作
*/
async function manage(config) {
const {
filePath,
manageOperation,
newPath,
mode,
uid,
gid,
recursive,
targetType,
} = config;
// 检查文件是否存在
const stats = await fs.lstat(filePath);
// 检查目标类型
if (targetType === "file" && !stats.isFile()) {
throw new Error("目标不是文件");
}
if (targetType === "directory" && !stats.isDirectory()) {
throw new Error("目标不是目录");
}
switch (manageOperation) {
case "rename":
// 确保目标目录存在
await fs.mkdir(path.dirname(newPath), { recursive: true });
await fs.rename(filePath, newPath);
break;
case "chmod":
if (recursive && stats.isDirectory()) {
const walk = async (dir) => {
const files = await fs.readdir(dir);
for (const file of files) {
const curPath = path.join(dir, file);
const stat = await fs.lstat(curPath);
await fs.chmod(curPath, parseInt(mode, 8));
if (stat.isDirectory()) {
await walk(curPath);
}
}
};
await fs.chmod(filePath, parseInt(mode, 8));
await walk(filePath);
} else {
await fs.chmod(filePath, parseInt(mode, 8));
}
break;
case "chown":
if (recursive && stats.isDirectory()) {
await fs.chown(filePath, uid, gid);
const walk = async (dir) => {
const files = await fs.readdir(dir);
for (const file of files) {
const curPath = path.join(dir, file);
const stat = await fs.lstat(curPath);
await fs.chown(curPath, uid, gid);
if (stat.isDirectory()) {
await walk(curPath);
}
}
};
await fs.chown(filePath, uid, gid);
await walk(filePath);
} else {
await fs.chown(filePath, uid, gid);
}
break;
default:
throw new Error(`不支持的操作类型: ${manageOperation}`);
}
}
/**
* 列出目录内容
* @param {Object} config 配置对象
* @param {string} config.filePath 目录路径
* @param {boolean} config.recursive 是否递归列出子目录
* @param {boolean} config.showHidden 是否显示隐藏文件
* @returns {Promise<Array>} 文件列表
*/
async function list(config) {
const { filePath, recursive, showHidden } = config;
if (recursive) {
const result = [];
const walk = async (dir) => {
const files = await fs.readdir(dir);
for (const file of files) {
if (!showHidden && file.startsWith(".")) continue;
const curPath = path.join(dir, file);
const stat = await fs.lstat(curPath);
result.push({
path: curPath,
isDirectory: stat.isDirectory(),
isFile: stat.isFile(),
isSymbolicLink: stat.isSymbolicLink(),
});
if (stat.isDirectory()) {
await walk(curPath);
}
}
};
await walk(filePath);
return result;
} else {
const files = await fs.readdir(filePath);
return Promise.all(
files
.filter((file) => showHidden || !file.startsWith("."))
.map(async (file) => {
const curPath = path.join(filePath, file);
const stat = await fs.lstat(curPath);
return {
path: curPath,
isDirectory: stat.isDirectory(),
isFile: stat.isFile(),
isSymbolicLink: stat.isSymbolicLink(),
};
})
);
}
}
/**
* 获取文件或目录状态
* @param {Object} config 配置对象
* @param {string} config.filePath 路径
* @param {string} config.targetType 目标类型
* @param {string} config.statMode 检查类型
* @param {boolean} [config.followSymlinks] 是否跟随符号链接
* @returns {Promise<Object>} 状态信息
*/
async function stat(config) {
const { filePath, targetType, statMode, followSymlinks } = config;
try {
const statFn = followSymlinks ? fs.stat : fs.lstat;
const stats = await statFn(filePath);
// 检查目标类型是否匹配
if (targetType === "file" && !stats.isFile()) {
throw new Error("目标不是文件");
}
if (targetType === "directory" && !stats.isDirectory()) {
throw new Error("目标不是目录");
}
// 根据检查类型返回不同的信息
if (statMode === "exists") {
return {
exists: true,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
};
} else if (statMode === "status") {
return {
exists: true,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
isSymbolicLink: stats.isSymbolicLink(),
size: stats.size,
mode: stats.mode,
uid: stats.uid,
gid: stats.gid,
accessTime: stats.atime,
modifyTime: stats.mtime,
changeTime: stats.ctime,
birthTime: stats.birthtime,
};
} else {
// 默认返回基本信息
return {
exists: true,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
isSymbolicLink: stats.isSymbolicLink(),
};
}
} catch (error) {
if (error.code === "ENOENT") {
return {
exists: false,
...(statMode === "exists" && {
isFile: false,
isDirectory: false,
}),
};
}
throw error;
}
}
/**
* 统一的文件操作入口
*/
async function operation(config) {
if (!config || typeof config !== "object") {
throw new Error("配置参数必须是一个对象");
}
const { operation } = config;
if (!operation) {
throw new Error("缺少必要的 operation 参数");
}
switch (operation) {
case "read":
return await read(config);
case "write":
return await write(config);
case "list":
return await list(config);
case "stat":
return await stat(config);
case "delete":
return await delete_(config);
case "manage":
return await manage(config);
default:
throw new Error(`不支持的操作类型: ${operation}`);
}
}
module.exports = {
read,
write,
list,
stat,
delete: delete_,
manage,
operation,
};