深入聊聊Node中的File模組
在聊天Stream/Buffer 的時候,我們已經開始使用require("fs")
引入文件模組做一些操作了
文件模組是對底層文件操作的封裝,例如文件讀寫/打開關閉/刪除添加等等
文件模組最大的特點就是所有的方法都提供的同步和非同步兩個版本,具有sync 後綴的方法都是同步方法,沒有的都是非同步方法
##檔案常識
檔案權限
因為需要對檔案進行操作,所以需要設定對應的權限。 【相關教學推薦:ll能夠查看目錄中檔案/資料夾的權限
檔案識別
在Node 中,標識位代表著對檔案的操作方式,可讀/可寫/即可讀又可寫等等,可以進行排列組合#檔案描述子
在先前的內容中講過,作業系統會為每個開啟的檔案指派一個叫做檔案描述子的數值標識,使用這些數值來追蹤特定的文件。 檔案描述子一般從3開始,0/1/2分別代表標準輸入/標準輸出/錯誤輸出常用API
一些實作
##過濾專案中適當的檔案const fs = require("fs");
const path = require("path");
const { promisify } = require("util");
const reg = new RegExp("(.ts[x]*|.js[x]*|.json)$");
const targetPath = path.resolve(__dirname, "../mini-proxy-mobx");
const readDir = (targetPath, callback) => {
fs.readdir(targetPath, (err, files) => {
if (err) callback(err);
files.forEach(async (file) => {
const filePath = path.resolve(__dirname, `${targetPath}/${file}`);
const stats = await promisify(fs.stat)(filePath);
if (stats.isDirectory()) {
await readDir(filePath);
} else {
checkFile(filePath);
}
});
});
};
const checkFile = (file) => {
if (reg.test(file)) {
console.log(file);
}
};
readDir(targetPath, (err) => {
throw err;
});
登入後複製
檔案拷貝const fs = require("fs"); const path = require("path"); const { promisify } = require("util"); const reg = new RegExp("(.ts[x]*|.js[x]*|.json)$"); const targetPath = path.resolve(__dirname, "../mini-proxy-mobx"); const readDir = (targetPath, callback) => { fs.readdir(targetPath, (err, files) => { if (err) callback(err); files.forEach(async (file) => { const filePath = path.resolve(__dirname, `${targetPath}/${file}`); const stats = await promisify(fs.stat)(filePath); if (stats.isDirectory()) { await readDir(filePath); } else { checkFile(filePath); } }); }); }; const checkFile = (file) => { if (reg.test(file)) { console.log(file); } }; readDir(targetPath, (err) => { throw err; });
問題:需要將檔案1中的內容拷貝到檔案2中
檔案API可以用fs.readFile 把檔案內容讀取完成,再採用fs.writeFile 寫入新的檔案
const fs = require("fs"); const path = require("path"); const sourceFile = path.resolve(__dirname, "../doc/Mobx原理及丐版实现.md"); const targetFile = path.resolve(__dirname, "target.txt"); fs.readFile(sourceFile, (err, data) => { if (err) throw err; const dataStr = data.toString(); fs.writeFile(targetFile, dataStr, (err) => { if (err) throw err; console.log("copy success~"); process.exit(1); }); });
? 這樣是否有問題,我們在Stream 講過,需要一點一點來,否則在大檔案時內存吃不消。
Buffer 使用使用fs.open 方法開啟文件,取得檔案描述符,再呼叫fs.read/fs.write 方法往特定的位置讀取和寫入一定量的資料
const copyFile = (source, target, size, callback) => { const sourceFile = path.resolve(__dirname, source); const targetFile = path.resolve(__dirname, target); const buf = Buffer.alloc(size); let hasRead = 0; // 下次读取文件的位置 let hasWrite = 0; // 下次写入文件的位置 fs.open(sourceFile, "r", (err, sourceFd) => { if (err) callback(err); fs.open(targetFile, "w", (err, targetFd) => { if (err) throw callback(err); function next() { fs.read(sourceFd, buf, 0, size, hasRead, (err, bytesRead) => { if (err) callback(err); hasRead += bytesRead; if (bytesRead) { fs.write(targetFd, buf, 0, size, hasWrite, (err, bytesWrite) => { if (err) callback(err); hasWrite += bytesWrite; next(); }); return; } fs.close(sourceFd, () => { console.log("关闭源文件"); }); fs.close(targetFd, () => { console.log("关闭目标文件"); }); }); } next(); }); }); };
const fs = require("fs");
const path = require("path");
const readStream = fs.createReadStream(
path.resolve(__dirname, "../doc/Mobx原理及丐版实现.md")
);
const writeStream = fs.createWriteStream(path.resolve("target.txt"));
readStream.pipe(writeStream);
登入後複製
#檔案上傳const fs = require("fs"); const path = require("path"); const readStream = fs.createReadStream( path.resolve(__dirname, "../doc/Mobx原理及丐版实现.md") ); const writeStream = fs.createWriteStream(path.resolve("target.txt")); readStream.pipe(writeStream);
小檔案上傳
// 上传后资源的URL地址
const RESOURCE_URL = `http://localhost:${PORT}`;
// 存储上传文件的目录
const UPLOAD_DIR = path.join(__dirname, "../public");
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
// 设置文件的存储目录
cb(null, UPLOAD_DIR);
},
filename: function (req, file, cb) {
// 设置文件名
cb(null, `${file.originalname}`);
},
});
const multerUpload = multer({ storage });
router.post(
"/uploadSingle",
async (ctx, next) => {
try {
await next();
ctx.body = {
code: 1,
msg: "文件上传成功",
url: `${RESOURCE_URL}/${ctx.file.originalname}`,
};
} catch (error) {
console.log(error);
ctx.body = {
code: 0,
msg: "文件上传失败",
};
}
},
multerUpload.single("file")
);
登入後複製
大檔案上傳// 上传后资源的URL地址 const RESOURCE_URL = `http://localhost:${PORT}`; // 存储上传文件的目录 const UPLOAD_DIR = path.join(__dirname, "../public"); const storage = multer.diskStorage({ destination: async function (req, file, cb) { // 设置文件的存储目录 cb(null, UPLOAD_DIR); }, filename: function (req, file, cb) { // 设置文件名 cb(null, `${file.originalname}`); }, }); const multerUpload = multer({ storage }); router.post( "/uploadSingle", async (ctx, next) => { try { await next(); ctx.body = { code: 1, msg: "文件上传成功", url: `${RESOURCE_URL}/${ctx.file.originalname}`, }; } catch (error) { console.log(error); ctx.body = { code: 0, msg: "文件上传失败", }; } }, multerUpload.single("file") );
# 具体实现 前端切片 file 是一种特殊的 Blob 对象,可以使用 slice 进行大文件分割 上传切片 根据切片构建每个切片的 formData,将二进制数据放在 slice 参数中,分别发送请求。 onUploadProgress 来处理每个切片的上传进度 切片合并 当我们所有的切片上传成功之后,我们依旧希望是按着原始文件作为保存的,所以需要对切片进行合并操作 上传文件校验 当我们上传一个文件的时候,先去判断在服务器上是否存在该文件,如果存在则直接不做上传操作,否则按上述逻辑进行上传 上述直接使用文件名来做判断,过于绝对,对文件做了相关修改并不更改名字,就会出现问题。更应该采用的方案是根据文件相关的元数据计算出它的 hash 值来做判断。 本文所有的代码可以github上查看 本文从文件常识/常用的文件 API 入手,重点讲解了 Node 中 File 的相关实践,最后使用相关内容实现了大文件上传。 更多node相关知识,请访问:nodejs 教程!const BIG_FILE_SIZE = 25 * 1024 * 1024;
const SLICE_FILE_SIZE = 5 * 1024 * 1024;
const uploadFile = async () => {
if (!fileList?.length) return alert("请选择文件");
const file = fileList[0];
const shouldUpload = await verifyUpload(file.name);
if (!shouldUpload) return message.success("文件已存在,上传成功");
if (file.size > BIG_FILE_SIZE) {
// big handle
getSliceList(file);
}
// // normal handle
// upload("/uploadSingle", file);
};
const getSliceList = (file: RcFile) => {
const sliceList: ISlice[] = [];
let curSize = 0;
let index = 0;
while (curSize < file.size) {
sliceList.push({
id: shortid.generate(),
slice: new File(
[file.slice(curSize, (curSize += SLICE_FILE_SIZE))],
`${file.name}-${index}`
),
name: file.name,
sliceName: `${file.name}-${index}`,
progress: 0,
});
index++;
}
uploadSlice(sliceList);
setSliceList(sliceList);
};
const uploadSlice = async (sliceList: ISlice[]) => {
const requestList = sliceList
.map(({ slice, sliceName, name }: ISlice, index: number) => {
const formData = new FormData();
formData.append("slice", slice);
formData.append("sliceName", sliceName);
formData.append("name", name);
return { formData, index, sliceName };
})
.map(({ formData }: { formData: FormData }, index: number) =>
request.post("/uploadBig", formData, {
onUploadProgress: (progressEvent: AxiosProgressEvent) =>
sliceUploadProgress(progressEvent, index),
})
);
await Promise.all(requestList);
};
// Client
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
const name = file?.originalname.split(".")?.[0];
const SLICE_DIR = path.join(UPLOAD_DIR, `${name}-slice`);
if (!fs.existsSync(SLICE_DIR)) {
await fs.mkdirSync(SLICE_DIR);
}
// 设置文件的存储目录
cb(null, SLICE_DIR);
},
filename: async function (req, file, cb) {
// 设置文件名
cb(null, `${file?.originalname}`);
},
});
// Server
router.post(
"/uploadBig",
async (ctx, next) => {
try {
await next();
const slice = ctx.files.slice[0]; // 切片文件
ctx.body = {
code: 1,
msg: "文件上传成功",
url: `${RESOURCE_URL}/${slice.originalname}`,
};
} catch (error) {
ctx.body = {
code: 0,
msg: "文件上传失败",
};
}
},
multerUpload.fields([{ name: "slice" }])
);
// Client
const uploadSlice = async (sliceList: ISlice[]) => {
// ...和上述 uploadSlice 一致
mergeSlice();
};
const mergeSlice = () => {
request.post("/mergeSlice", {
size: SLICE_FILE_SIZE,
name: fileList[0].name,
});
};
// Server
router.post("/mergeSlice", async (ctx, next) => {
try {
await next();
const { size, name } = ctx.request.body ?? {};
const sliceName = name.split(".")?.[0];
const filePath = path.join(UPLOAD_DIR, name);
const slice_dir = path.join(UPLOAD_DIR, `${sliceName}-slice`);
await mergeSlice(filePath, slice_dir, size);
ctx.body = {
code: 1,
msg: "文件合并成功",
};
} catch (error) {
ctx.body = {
code: 0,
msg: "文件合并失败",
};
}
});
// 通过 stream 来读写数据,将 slice 中数据读取到文件中
const pipeStream = (path, writeStream) => {
return new Promise((resolve) => {
const readStream = fs.createReadStream(path);
readStream.on("end", () => {
fs.unlinkSync(path); // 读取完成之后,删除切片文件
resolve();
});
readStream.pipe(writeStream);
});
};
const mergeSlice = async (filePath, sliceDir, size) => {
if (!fs.existsSync(sliceDir)) {
throw new Error("当前文件不存在");
}
const slices = await fs.readdirSync(sliceDir);
slices.sort((a, b) => a.split("-")[1] - b.split("-")[1]);
try {
const slicesPipe = slices.map((sliceName, index) => {
return pipeStream(
path.resolve(sliceDir, sliceName),
fs.createWriteStream(filePath, { start: index * size })
);
});
await Promise.all(slicesPipe);
await fs.rmdirSync(sliceDir); // 读取完成之后,删除切片文件夹
} catch (error) {
console.log(error);
}
};
// Client
const verifyUpload = async (name: string) => {
const res = await request.post("/verify", { name });
return res?.data?.data;
};
const uploadFile = async () => {
if (!fileList?.length) return alert("请选择文件");
const file = fileList[0];
const shouldUpload = await verifyUpload(file.name);
if (!shouldUpload) return message.success("文件已存在,上传成功");
if (file.size > BIG_FILE_SIZE) {
// big handle
getSliceList(file);
}
// // normal handle
// upload("/uploadSingle", file);
};
// Server
router.post("/verify", async (ctx, next) => {
try {
await next();
const { name } = ctx.request.body ?? {};
const filePath = path.resolve(UPLOAD_DIR, name);
if (fs.existsSync(filePath)) {
ctx.body = {
code: 1,
data: false,
};
} else {
ctx.body = {
code: 1,
data: true,
};
}
} catch (error) {
ctx.body = {
code: 0,
msg: "检测失败",
};
}
});
const calculateMD5 = (file: any) => new Promise((resolve, reject) => {
const chunkSize = SLICE_FILE_SIZE
const fileReader = new FileReader();
const spark = new SparkMD5.ArrayBuffer();
let cursor = 0;
fileReader.onerror = () => {
reject(new Error('Error reading file'));
};
fileReader.onload = (e: any) => {
spark.append(e.target.result);
cursor += e.target.result.byteLength;
if (cursor < file.size) loadNext();
else resolve(spark.end());
};
const loadNext = () => {
const fileSlice = file.slice(cursor, cursor + chunkSize);
fileReader.readAsArrayBuffer(fileSlice);
}
loadNext();
});
总结
以上是深入聊聊Node中的File模組的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

隨著網路技術的發展,前端開發變得日益重要。尤其是行動端設備的普及,更需要高效率、穩定、安全又易於維護的前端開發技術。而作為一門快速發展的程式語言,Go語言已經被越來越多的開發者所使用。那麼,使用Go語言進行前端開發行得通嗎?接下來,本文將為你詳細說明如何使用Go語言進行前端開發。先來看看為什麼要使用Go語言進行前端開發。很多人認為Go語言是一門

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

身為C#開發者,我們的開發工作通常包括前端和後端的開發,而隨著技術的發展和專案的複雜性提高,前端與後端協同開發也變得越來越重要和複雜。本文將分享一些前端與後端協同開發的技巧,以幫助C#開發者更有效率地完成開發工作。確定好介面規範前後端的協同開發離不開API介面的交互。要確保前後端協同開發順利進行,最重要的是定義好介面規格。接口規範涉及到接口的命

實作即時通訊的方法有WebSocket、Long Polling、Server-Sent Events、WebRTC等等。詳細介紹:1、WebSocket,它可以在客戶端和伺服器之間建立持久連接,實現即時的雙向通信,前端可以使用WebSocket API來創建WebSocket連接,並透過發送和接收訊息來實現即時通訊;2、Long Polling,是一種模擬即時通訊的技術等等

Django:前端和後端開發都能搞定的神奇框架! Django是一個高效、可擴展的網路應用程式框架。它能夠支援多種Web開發模式,包括MVC和MTV,可以輕鬆地開發出高品質的Web應用程式。 Django不僅支援後端開發,還能夠快速建構出前端的介面,透過模板語言,實現靈活的視圖展示。 Django把前端開發和後端開發融合成了一種無縫的整合,讓開發人員不必專門學習

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。
