亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

目錄
檔案識(shí)別
%%PRE_BLOCK_3%%
問題:需要將檔案1中的內(nèi)容拷貝到檔案2中
可以用fs.readFile 把檔案內(nèi)容讀取完成,再採(cǎi)用fs.writeFile 寫入新的檔案
使用fs.open 方法開啟文件,取得檔案描述符,再呼叫fs.read/fs.write 方法往特定的位置讀取和寫入一定量的資料
%%PRE_BLOCK_4%%" >%%PRE_BLOCK_4%%
總結(jié)
首頁 web前端 js教程 深入聊聊Node中的File模組

深入聊聊Node中的File模組

Apr 24, 2023 pm 05:49 PM
前端 node.js

深入聊聊Node中的File模組

在聊天Stream/Buffer 的時(shí)候,我們已經(jīng)開始使用require("fs")引入文件模組做一些操作了

文件模組是對(duì)底層文件操作的封裝,例如文件讀寫/打開關(guān)閉/刪除添加等等

文件模組最大的特點(diǎn)就是所有的方法都提供的同步非同步兩個(gè)版本,具有sync 後綴的方法都是同步方法,沒有的都是非同步方法

##檔案常識(shí)

檔案權(quán)限

因?yàn)樾枰獙?duì)檔案進(jìn)行操作,所以需要設(shè)定對(duì)應(yīng)的權(quán)限。 【相關(guān)教學(xué)推薦:

nodejs影片教學(xué)程式設(shè)計(jì)教學(xué)

深入聊聊Node中的File模組

#主要分為三種角色,檔案擁有者、文件所屬群組、其他使用者

檔案權(quán)限分為讀取、寫入、執(zhí)行,分別於數(shù)字表示為4/2/1,沒有權(quán)限的時(shí)候表示為0

如果取消了執(zhí)行權(quán)限指,資料夾內(nèi)任何檔案都無法訪問,也無法cd 到資料夾

使用Linux 指令

ll能夠查看目錄中檔案/資料夾的權(quán)限

Untitled 1.png

第一位d 代表資料夾,- 表示文件,後面就是文件的權(quán)限?// TODO: @表示什麼

檔案識(shí)別

在Node 中,標(biāo)識(shí)位代表著對(duì)檔案的操作方式,可讀/可寫/即可讀又可寫等等,可以進(jìn)行排列組合

Untitled 2.png

#檔案描述子

在先前的內(nèi)容中講過,作業(yè)系統(tǒng)會(huì)為每個(gè)開啟的檔案指派一個(gè)叫做檔案描述子的數(shù)值標(biāo)識(shí),使用這些數(shù)值來追蹤特定的文件。

檔案描述子一般從3開始,0/1/2分別代表標(biāo)準(zhǔn)輸入/標(biāo)準(zhǔn)輸出/錯(cuò)誤輸出

常用API

Untitled 3.png

Untitled 4.png

Untitled 5.png

一些實(shí)作

##過濾專案中適當(dāng)?shù)臋n案

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中的內(nèi)容拷貝到檔案2中

檔案API

可以用fs.readFile 把檔案內(nèi)容讀取完成,再採(cǎi)用fs.writeFile 寫入新的檔案

const fs = require("fs");
const path = require("path");

const sourceFile = path.resolve(__dirname, "../doc/Mobx原理及丐版實(shí)現(xiàn).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);
    });
});

? 這樣是否有問題,我們?cè)赟tream 講過,需要一點(diǎn)一點(diǎn)來,否則在大檔案時(shí)內(nèi)存吃不消。

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("關(guān)閉源文件"); });
                    fs.close(targetFd, () => { console.log("關(guān)閉目標(biāo)文件"); });
                });
            }
            next();
        });
    });
};

Stream 使用

const fs = require("fs");
const path = require("path");
const readStream = fs.createReadStream(
    path.resolve(__dirname, "../doc/Mobx原理及丐版實(shí)現(xiàn).md")
);
const writeStream = fs.createWriteStream(path.resolve("target.txt"));
readStream.pipe(writeStream);

#檔案上傳

小檔案上傳大檔案上傳

Untitled 6.png

#主要步驟

#

  1. 前端接收大文件,并進(jìn)行切片處理
  2. 將每份切片進(jìn)行上傳處理
  3. 后端接收到所有的切片,存儲(chǔ)所有切片到一個(gè)文件夾中
  4. 將文件夾中的切片做合并,并對(duì)切片做刪除
  5. 再次上傳統(tǒng)一文件時(shí),能夠快速上傳

具體實(shí)現(xiàn)

  1. 前端切片

    const BIG_FILE_SIZE = 25 * 1024 * 1024;
    const SLICE_FILE_SIZE = 5 * 1024 * 1024;
    
    const uploadFile = async () => {
        if (!fileList?.length) return alert("請(qǐng)選擇文件");
        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 對(duì)象,可以使用 slice 進(jìn)行大文件分割

    Untitled 7.png

  2. 上傳切片

    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);
    };

    根據(jù)切片構(gòu)建每個(gè)切片的 formData,將二進(jìn)制數(shù)據(jù)放在 slice 參數(shù)中,分別發(fā)送請(qǐng)求。

    onUploadProgress 來處理每個(gè)切片的上傳進(jìn)度

    // 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);
          }
          // 設(shè)置文件的存儲(chǔ)目錄
          cb(null, SLICE_DIR);
      },
      filename: async function (req, file, cb) {
          // 設(shè)置文件名
          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" }])
    );
  3. 切片合并

    當(dāng)我們所有的切片上傳成功之后,我們依舊希望是按著原始文件作為保存的,所以需要對(duì)切片進(jìn)行合并操作

    // 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 來讀寫數(shù)據(jù),將 slice 中數(shù)據(jù)讀取到文件中
    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("當(dāng)前文件不存在");
        }
        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);
        }
    };
  4. 上傳文件校驗(yàn)

    當(dāng)我們上傳一個(gè)文件的時(shí)候,先去判斷在服務(wù)器上是否存在該文件,如果存在則直接不做上傳操作,否則按上述邏輯進(jìn)行上傳

    // 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("請(qǐng)選擇文件");
        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: "檢測(cè)失敗",
            };
        }
    });

    上述直接使用文件名來做判斷,過于絕對(duì),對(duì)文件做了相關(guān)修改并不更改名字,就會(huì)出現(xiàn)問題。更應(yīng)該采用的方案是根據(jù)文件相關(guān)的元數(shù)據(jù)計(jì)算出它的 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(&#39;Error reading file&#39;));
        };
        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上查看

    總結(jié)

    本文從文件常識(shí)/常用的文件 API 入手,重點(diǎn)講解了 Node 中 File 的相關(guān)實(shí)踐,最后使用相關(guān)內(nèi)容實(shí)現(xiàn)了大文件上傳。

    更多node相關(guān)知識(shí),請(qǐng)?jiān)L問:nodejs 教程!

    以上是深入聊聊Node中的File模組的詳細(xì)內(nèi)容。更多資訊請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願(yuàn)投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請(qǐng)聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

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

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級(jí)程式碼編輯軟體(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72
PHP與Vue:完美搭檔的前端開發(fā)利器 PHP與Vue:完美搭檔的前端開發(fā)利器 Mar 16, 2024 pm 12:09 PM

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

Go語言前端技術(shù)探秘:前端開發(fā)新視野 Go語言前端技術(shù)探秘:前端開發(fā)新視野 Mar 28, 2024 pm 01:06 PM

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

Django是前端還是後端?一探究竟! Django是前端還是後端?一探究竟! Jan 19, 2024 am 08:37 AM

Django是一個(gè)由Python編寫的web應(yīng)用框架,它強(qiáng)調(diào)快速開發(fā)和乾淨(jìng)方法。儘管Django是web框架,但要回答Django是前端還是後端這個(gè)問題,需要深入理解前後端的概念。前端是指使用者直接和互動(dòng)的介面,後端是指伺服器端的程序,他們透過HTTP協(xié)定進(jìn)行資料的互動(dòng)。在前端和後端分離的情況下,前後端程式可以獨(dú)立開發(fā),分別實(shí)現(xiàn)業(yè)務(wù)邏輯和互動(dòng)效果,資料的交

C#開發(fā)經(jīng)驗(yàn)分享:前端與後端協(xié)同開發(fā)技巧 C#開發(fā)經(jīng)驗(yàn)分享:前端與後端協(xié)同開發(fā)技巧 Nov 23, 2023 am 10:13 AM

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

前端面試官常問的問題 前端面試官常問的問題 Mar 19, 2024 pm 02:24 PM

在前端開發(fā)面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎(chǔ)、JavaScript基礎(chǔ)、框架和函式庫(kù)、專案經(jīng)驗(yàn)、演算法和資料結(jié)構(gòu)、效能最佳化、跨域請(qǐng)求、前端工程化、設(shè)計(jì)模式以及新技術(shù)和趨勢(shì)。面試官的問題旨在評(píng)估候選人的技術(shù)技能、專案經(jīng)驗(yàn)以及對(duì)行業(yè)趨勢(shì)的理解。因此,應(yīng)試者應(yīng)充分準(zhǔn)備這些方面,以展現(xiàn)自己的能力和專業(yè)知識(shí)。

Django:前端和後端開發(fā)都能搞定的神奇框架! Django:前端和後端開發(fā)都能搞定的神奇框架! Jan 19, 2024 am 08:52 AM

Django:前端和後端開發(fā)都能搞定的神奇框架! Django是一個(gè)高效、可擴(kuò)展的網(wǎng)路應(yīng)用程式框架。它能夠支援多種Web開發(fā)模式,包括MVC和MTV,可以輕鬆地開發(fā)出高品質(zhì)的Web應(yīng)用程式。 Django不僅支援後端開發(fā),還能夠快速建構(gòu)出前端的介面,透過模板語言,實(shí)現(xiàn)靈活的視圖展示。 Django把前端開發(fā)和後端開發(fā)融合成了一種無縫的整合,讓開發(fā)人員不必專門學(xué)習(xí)

Golang與前端技術(shù)結(jié)合:探討Golang如何在前端領(lǐng)域發(fā)揮作用 Golang與前端技術(shù)結(jié)合:探討Golang如何在前端領(lǐng)域發(fā)揮作用 Mar 19, 2024 pm 06:15 PM

Golang與前端技術(shù)結(jié)合:探討Golang如何在前端領(lǐng)域發(fā)揮作用,需要具體程式碼範(fàn)例隨著互聯(lián)網(wǎng)和行動(dòng)應(yīng)用的快速發(fā)展,前端技術(shù)也愈發(fā)重要。而在這個(gè)領(lǐng)域中,Golang作為一門強(qiáng)大的後端程式語言,也可以發(fā)揮重要作用。本文將探討Golang如何與前端技術(shù)結(jié)合,以及透過具體的程式碼範(fàn)例來展示其在前端領(lǐng)域的潛力。 Golang在前端領(lǐng)域的角色作為一門高效、簡(jiǎn)潔且易於學(xué)習(xí)的

了解React的主要功能:前端視角 了解React的主要功能:前端視角 Apr 18, 2025 am 12:15 AM

React的主要功能包括組件化思想、狀態(tài)管理和虛擬DOM。 1)組件化思想允許將UI拆分成可複用的部分,提高代碼可讀性和可維護(hù)性。 2)狀態(tài)管理通過state和props管理動(dòng)態(tài)數(shù)據(jù),變化觸發(fā)UI更新。 3)虛擬DOM優(yōu)化性能,通過內(nèi)存中的DOM副本計(jì)算最小操作更新UI。

See all articles