Node の File モジュールについて詳しく説明しましょう
Stream/Buffer について話すとき、すでに require("fs")
を使用して導入を開始しています。ファイル モジュールはいくつかの操作を実行します。
ファイル モジュールは、ファイルの読み取りと書き込み/オープンとクローズ/削除の追加など、基礎となるファイル操作をカプセル化したものです。
ファイルモジュールはすべてのメソッドが提供されるということです Synchronous と Asynchronous の 2 つのバージョンがあります。sync サフィックスが付いているメソッドはすべて同期メソッドであり、付いていないメソッドはすべて非同期メソッド
ファイルの常識
ファイルのアクセス許可
ファイルを操作する必要があるため、対応するアクセス許可を設定する必要があります。 [関連チュートリアルの推奨事項: nodejs ビデオ チュートリアル 、プログラミング教育 ]
は主に 3 つの役割に分かれています。ファイル所有者、ファイルが所属するグループと他のユーザー
ファイルの権限は読み取り、書き込み、実行に分かれており、それぞれ4/2/1で表され、権限がない場合は0で表されます。
実行がキャンセルされた場合、権限により、フォルダー内のファイルにアクセスできなくなり、フォルダーに cd できなくなります。
Linux コマンドを使用してください。ll
権限を確認できます。ディレクトリ内のファイル/フォルダーの数
最初の d はフォルダーを表し、 - はファイルを表し、その後にファイルのアクセス許可が続きます。 // TODO: @ は何を表しますか
ファイル識別
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); }); });
バッファの使用
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") );
##主な手順
- 前端接收大文件,并进行切片处理
- 将每份切片进行上传处理
- 后端接收到所有的切片,存储所有切片到一个文件夹中
- 将文件夹中的切片做合并,并对切片做删除
- 再次上传统一文件时,能够快速上传
具体实现
前端切片
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); };
ログイン後にコピーfile 是一种特殊的 Blob 对象,可以使用 slice 进行大文件分割
上传切片
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); };
ログイン後にコピー根据切片构建每个切片的 formData,将二进制数据放在 slice 参数中,分别发送请求。
onUploadProgress 来处理每个切片的上传进度
// 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: "检测失败", }; } });
ログイン後にコピー上述直接使用文件名来做判断,过于绝对,对文件做了相关修改并不更改名字,就会出现问题。更应该采用的方案是根据文件相关的元数据计算出它的 hash 值来做判断。
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(); });
ログイン後にコピー本文所有的代码可以github上查看
总结
本文从文件常识/常用的文件 API 入手,重点讲解了 Node 中 File 的相关实践,最后使用相关内容实现了大文件上传。
更多node相关知识,请访问:nodejs 教程!
以上がNode の File モジュールについて詳しく説明しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ 今日のインターネットの急速な発展の時代において、フロントエンド開発はますます重要になっています。 Web サイトやアプリケーションのエクスペリエンスに対するユーザーの要求がますます高まっているため、フロントエンド開発者は、より効率的で柔軟なツールを使用して、応答性の高いインタラクティブなインターフェイスを作成する必要があります。フロントエンド開発の分野における 2 つの重要なテクノロジーである PHP と Vue.js は、組み合わせることで完璧なツールと見なされます。この記事では、PHP と Vue の組み合わせと、読者がこれら 2 つをよりよく理解し、適用できるようにするための詳細なコード例について説明します。

インターネット技術の発展に伴い、フロントエンド開発の重要性がますます高まっています。特にモバイル デバイスの人気により、効率的で安定しており、安全で保守が容易なフロントエンド開発テクノロジーが必要です。 Go 言語は、急速に発展しているプログラミング言語として、ますます多くの開発者によって使用されています。では、フロントエンド開発に Go 言語を使用することは可能でしょうか?次に、この記事ではフロントエンド開発にGo言語を使用する方法を詳しく説明します。まずはフロントエンド開発にGo言語が使われる理由を見てみましょう。多くの人は Go 言語は

フロントエンド開発のインタビューでは、HTML/CSS の基本、JavaScript の基本、フレームワークとライブラリ、プロジェクトの経験、アルゴリズムとデータ構造、パフォーマンスの最適化、クロスドメイン リクエスト、フロントエンド エンジニアリング、デザインパターン、新しいテクノロジーとトレンド。面接官の質問は、候補者の技術スキル、プロジェクトの経験、業界のトレンドの理解を評価するように設計されています。したがって、候補者はこれらの分野で自分の能力と専門知識を証明するために十分な準備をしておく必要があります。

Django は、迅速な開発とクリーンなメソッドを重視した Python で書かれた Web アプリケーション フレームワークです。 Django は Web フレームワークですが、Django がフロントエンドなのかバックエンドなのかという質問に答えるには、フロントエンドとバックエンドの概念を深く理解する必要があります。フロントエンドはユーザーが直接対話するインターフェイスを指し、バックエンドはサーバー側プログラムを指し、HTTP プロトコルを通じてデータと対話します。フロントエンドとバックエンドが分離されている場合、フロントエンドとバックエンドのプログラムをそれぞれ独立して開発して、ビジネス ロジックとインタラクティブ効果、およびデータ交換を実装できます。

C# 開発者としての私たちの開発作業には、通常、フロントエンドとバックエンドの開発が含まれますが、テクノロジーが発展し、プロジェクトが複雑になるにつれて、フロントエンドとバックエンドの共同開発はますます重要かつ複雑になってきています。この記事では、C# 開発者が開発作業をより効率的に完了できるようにする、フロントエンドとバックエンドの共同開発テクニックをいくつか紹介します。インターフェイスの仕様を決定した後、フロントエンドとバックエンドの共同開発は API インターフェイスの相互作用から切り離せません。フロントエンドとバックエンドの共同開発をスムーズに進めるためには、適切なインターフェース仕様を定義することが最も重要です。インターフェイスの仕様にはインターフェイスの名前が含まれます

Golang はフロントエンドとして使用できます。Golang は、フロントエンド アプリケーションなど、さまざまなタイプのアプリケーションの開発に使用できる非常に多用途なプログラミング言語です。Golang を使用してフロントエンドを作成することで、 JavaScript などの言語によって引き起こされる一連の問題、たとえば、型安全性の低さ、パフォーマンスの低下、コードの保守の困難などの問題です。

Go 言語は、高速で効率的なプログラミング言語として、バックエンド開発の分野で広く普及しています。ただし、Go 言語をフロントエンド開発と結びつける人はほとんどいません。実際、フロントエンド開発に Go 言語を使用すると、効率が向上するだけでなく、開発者に新たな視野をもたらすことができます。この記事では、フロントエンド開発に Go 言語を使用する可能性を探り、読者がこの分野をよりよく理解できるように具体的なコード例を示します。従来のフロントエンド開発では、ユーザー インターフェイスの構築に JavaScript、HTML、CSS がよく使用されます。

インスタント メッセージングを実装する方法には、WebSocket、ロング ポーリング、サーバー送信イベント、WebRTC などが含まれます。詳細な紹介: 1. クライアントとサーバーの間に永続的な接続を確立してリアルタイムの双方向通信を実現できる WebSocket フロントエンドは WebSocket API を使用して WebSocket 接続を作成し、送受信によるインスタント メッセージングを実現できます。 2. Long Polling(リアルタイム通信を模擬する技術)など
