Parlons en profondeur du module File dans Node
En parlant de Stream/Buffer, nous avons déjà commencé à utiliser require("fs")
pour introduire le module file pour effectuer certaines opérationsrequire("fs")
引入文件模块做一些操作了
文件模块是对底层文件操作的封装,例如文件读写/打开关闭/删除添加等等
文件模块最大的特点就是所有的方法都提供的同步和异步两个版本,具有 sync 后缀的方法都是同步方法,没有的都是异步方法
文件常识
文件权限
因为需要对文件进行操作,所以需要设置对应的权限。【相关教程推荐:nodejs视频教程、编程教学】
主要分为三种角色,文件所有者、文件所属组、其他用户
文件权限分为读、写、执行,分别于数字表示为4/2/1,没有权限的时候表示为0
如果取消了执行权限指,文件夹内任何文件都无法访问,也无法 cd 到文件夹
使用 Linux 命令ll
La plus grande caractéristique du module de fichiers est que toutes les méthodes sont fournies en deux versions :
et
asynchroneMéthodes avec. le suffixe de synchronisation sont toutes des méthodes synchrones, sans toutes les méthodes asynchronesFichier bon sens
Autorisations de fichiers
, Enseignement de la programmation】

Si l'autorisation d'exécution est annulée, aucun fichier du dossier n'est accessible, et il est impossible de cd dans le dossier
Utilisez la commande Linuxll pour afficher les autorisations des fichiers/dossiers dans le répertoireLe premier d représente le dossier, - représente le fichier, suivi des permissions du fichier // TODO : Que représente @
Identification du fichier
Dans Node, le bit d'identification représente la méthode de fonctionnement du fichier, lisible/inscriptible/lisible et inscriptible, etc., qui peut être organisé et combiné
Descripteur de fichier
in Comme mentionné dans le contenu précédent, le système d'exploitation attribue un identifiant numérique appelé descripteur de fichier à chaque fichier ouvert et utilise ces valeurs pour suivre des fichiers spécifiques.
Les descripteurs de fichiers commencent généralement à partir de 3, 0/1/2 représentent respectivement l'entrée standard/la sortie standard/la sortie d'erreur
API commune
Quelques pratiques
Filtrage approprié fichiers dans le projetconst 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;
});
Copier après la connexion
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; });
Copie de fichier
Problème : vous devez copier le contenu du fichier 1 dans le fichier 2API de fichier
Vous pouvez utiliser fs.readFile pour lire le contenu du fichier, puis Utilisez fs.writeFile pour écrire un nouveau fichier
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); }); });
? Y a-t-il un problème avec cela ? Comme nous l'avons dit dans Stream, vous devez le faire petit à petit, sinon la mémoire sera trop importante pour les gros fichiers.
Utilisation du tampon 🎜🎜🎜 Utilisez la méthode fs.open pour ouvrir le fichier, obtenez le descripteur de fichier, puis appelez la méthode fs.read/fs.write pour lire et écrire une certaine quantité de données dans un emplacement spécifique 🎜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);
// 上传后资源的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); };
Copier après la connexionfile 是一种特殊的 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); };
Copier après la connexion根据切片构建每个切片的 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" }]) );
Copier après la connexion切片合并
当我们所有的切片上传成功之后,我们依旧希望是按着原始文件作为保存的,所以需要对切片进行合并操作
// 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); } };
Copier après la connexion上传文件校验
当我们上传一个文件的时候,先去判断在服务器上是否存在该文件,如果存在则直接不做上传操作,否则按上述逻辑进行上传
// 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: "检测失败", }; } });
Copier après la connexion上述直接使用文件名来做判断,过于绝对,对文件做了相关修改并不更改名字,就会出现问题。更应该采用的方案是根据文件相关的元数据计算出它的 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(); });
Copier après la connexion本文所有的代码可以github上查看
总结
本文从文件常识/常用的文件 API 入手,重点讲解了 Node 中 File 的相关实践,最后使用相关内容实现了大文件上传。
更多node相关知识,请访问:nodejs 教程!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

PHP et Vue : une combinaison parfaite d'outils de développement front-end À l'ère actuelle de développement rapide d'Internet, le développement front-end est devenu de plus en plus important. Alors que les utilisateurs ont des exigences de plus en plus élevées en matière d’expérience des sites Web et des applications, les développeurs front-end doivent utiliser des outils plus efficaces et plus flexibles pour créer des interfaces réactives et interactives. En tant que deux technologies importantes dans le domaine du développement front-end, PHP et Vue.js peuvent être considérés comme une arme parfaite lorsqu'ils sont associés. Cet article explorera la combinaison de PHP et Vue, ainsi que des exemples de code détaillés pour aider les lecteurs à mieux comprendre et appliquer ces deux éléments.

Avec le développement de la technologie Internet, le développement front-end est devenu de plus en plus important. La popularité des appareils mobiles, en particulier, nécessite une technologie de développement frontal efficace, stable, sûre et facile à entretenir. En tant que langage de programmation en développement rapide, le langage Go est utilisé par de plus en plus de développeurs. Alors, est-il possible d’utiliser le langage Go pour le développement front-end ? Ensuite, cet article expliquera en détail comment utiliser le langage Go pour le développement front-end. Voyons d’abord pourquoi le langage Go est utilisé pour le développement front-end. Beaucoup de gens pensent que le langage Go est un

Lors des entretiens de développement front-end, les questions courantes couvrent un large éventail de sujets, notamment les bases HTML/CSS, les bases JavaScript, les frameworks et les bibliothèques, l'expérience du projet, les algorithmes et les structures de données, l'optimisation des performances, les requêtes inter-domaines, l'ingénierie front-end, les modèles de conception et les nouvelles technologies et tendances. Les questions de l'intervieweur sont conçues pour évaluer les compétences techniques du candidat, son expérience en matière de projet et sa compréhension des tendances du secteur. Par conséquent, les candidats doivent être parfaitement préparés dans ces domaines pour démontrer leurs capacités et leur expertise.

Django est un framework d'application Web écrit en Python qui met l'accent sur un développement rapide et des méthodes propres. Bien que Django soit un framework Web, pour répondre à la question de savoir si Django est un front-end ou un back-end, vous devez avoir une compréhension approfondie des concepts de front-end et de back-end. Le front-end fait référence à l'interface avec laquelle les utilisateurs interagissent directement, et le back-end fait référence aux programmes côté serveur. Ils interagissent avec les données via le protocole HTTP. Lorsque le front-end et le back-end sont séparés, les programmes front-end et back-end peuvent être développés indépendamment pour mettre en œuvre respectivement la logique métier et les effets interactifs, ainsi que l'échange de données.

En tant que développeur C#, notre travail de développement comprend généralement le développement front-end et back-end. À mesure que la technologie se développe et que la complexité des projets augmente, le développement collaboratif du front-end et du back-end est devenu de plus en plus important et complexe. Cet article partagera quelques techniques de développement collaboratif front-end et back-end pour aider les développeurs C# à effectuer leur travail de développement plus efficacement. Après avoir déterminé les spécifications de l’interface, le développement collaboratif du front-end et du back-end est indissociable de l’interaction des interfaces API. Pour assurer le bon déroulement du développement collaboratif front-end et back-end, le plus important est de définir de bonnes spécifications d’interface. La spécification de l'interface implique le nom de l'interface

Golang peut être utilisé comme frontal. Golang est un langage de programmation très polyvalent qui peut être utilisé pour développer différents types d'applications, y compris des applications frontales. En utilisant Golang pour écrire le front-end, vous pouvez vous débarrasser d'un front-end. série de problèmes causés par des langages tels que JavaScript. Par exemple, des problèmes tels qu'une mauvaise sécurité des types, de faibles performances et un code difficile à maintenir.

En tant que langage de programmation rapide et efficace, le langage Go est très populaire dans le domaine du développement back-end. Cependant, peu de gens associent le langage Go au développement front-end. En fait, l’utilisation du langage Go pour le développement front-end peut non seulement améliorer l’efficacité, mais également ouvrir de nouveaux horizons aux développeurs. Cet article explorera la possibilité d'utiliser le langage Go pour le développement front-end et fournira des exemples de code spécifiques pour aider les lecteurs à mieux comprendre ce domaine. Dans le développement front-end traditionnel, JavaScript, HTML et CSS sont souvent utilisés pour créer des interfaces utilisateur.

Les méthodes de mise en œuvre de la messagerie instantanée incluent WebSocket, Long Polling, Server-Sent Events, WebRTC, etc. Introduction détaillée : 1. WebSocket, qui peut établir une connexion persistante entre le client et le serveur pour obtenir une communication bidirectionnelle en temps réel. Le frontal peut utiliser l'API WebSocket pour créer une connexion WebSocket et obtenir une messagerie instantanée en envoyant et en recevant. messages 2. Long Polling, une technologie qui simule la communication en temps réel, etc.
