欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang實(shí)現(xiàn)大文件上傳功能全過程

 更新時間:2023年07月03日 08:42:39   作者:白馬嘯西施  
Go語言可以用來實(shí)現(xiàn)大文件傳輸,下面這篇文章主要給大家介紹了關(guān)于golang實(shí)現(xiàn)大文件上傳功能的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

在我們的工作中,上傳功能是一個比較常見的功能,但是當(dāng)上傳文件過大就可能上傳不成功,或者花費(fèi)時間過長或失敗。

這個時候我就需要將大文件進(jìn)行分割成小文件上傳,然后在合并成一個大文件,提供上傳的容錯率。

現(xiàn)將大文件上傳功能記錄與此。

實(shí)現(xiàn)邏輯:

1.將文件分割成n個文件,并將他們?nèi)可蟼鞯椒?wù)器,可以給文件hash一個值,確保n個文件是同一個文件的一部分。

2.上傳完成后,服務(wù)器進(jìn)行合并,根據(jù)hash。

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

1.前段html index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload file</title>
</head>
<body id="app">
<h1 style="text-align: center">大文件切片上傳-實(shí)例</h1>
<form method="post" enctype="multipart/form-data" onsubmit="return false" style="left: 10vw;position: relative;display: flex;height: 30vh;flex-direction: column;width: 80vw;margin: 20px;text-align: center;">
    <input type="file"  id="file" name="ff" multiple="multiple" style="margin-left: 30px"/><br/>
    <input type="submit" value="提交" id="xx" onclick="upload()" style="margin-left: 30px;width:70px"/>
</form>
<div style="height: 30px; width:80vw;left: 10vw;position: relative;"><span>上傳過程:</span></div>
<div style="display: block;height: 40vh; width:80vw;overflow: scroll; background: darkgray;left: 10vw;position: relative;">
    <textarea id="ct" style="height: 100%;width:100%;"></textarea>
</div>
</body>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js"></script>
<script >
    const chunkSize = 2 * 1024 * 1024; // 每個chunk的大小,設(shè)置為2兆
    const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
    const hashFile = (file) => {
        return new Promise((resolve, reject) => {
            const chunks = Math.ceil(file.size / chunkSize);
            let currentChunk = 0;
            const spark = new SparkMD5.ArrayBuffer();
            const fileReader = new FileReader();
            const loadNext = () => {
                const start = currentChunk * chunkSize;
                const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
                fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
            }
            fileReader.onload = e => {
                spark.append(e.target.result); // Append array buffer
                currentChunk++;
                if (currentChunk < chunks) {
                    loadNext();
                    ct = document.getElementById("ct")
                    ct.textContent = ct.textContent + `第${currentChunk}分片解析完成,開始解析${currentChunk + 1}分片\n\r`
                    console.log(`第${currentChunk}分片解析完成,開始解析${currentChunk + 1}分片`);
                } else {
                    console.log('finished loading');
                    const result = spark.end();
                    // 如果單純的使用result 作為hash值的時候, 如果文件內(nèi)容相同,而名稱不同的時候
                    // 想保留兩個文件無法保留。所以把文件名稱加上。
                    const sparkMd5 = new SparkMD5();
                    sparkMd5.append(result);
                    sparkMd5.append(file.name);
                    const hexHash = sparkMd5.end();
                    resolve(hexHash);
                }
            };
            fileReader.onerror = () => {
                console.warn('文件讀取失?。?);
            };
            loadNext();
        }).catch(err => {
            console.log(err);
        });
    }
    const upload = async () => {
        const fileDom = $('#file')[0];
        // 獲取到的files為一個File對象數(shù)組,如果允許多選的時候,文件為多個
        const files = fileDom.files;
        const file = files[0];
        if (!file) {
            alert('沒有獲取文件');
            return;
        }
        // alert("文件大小:"+ file.size / 1024 / 1024)
        // console.log(file)
        const blockCount = Math.ceil(file.size / chunkSize); // 分片總數(shù)
        const axiosPromiseArray = []; // axiosPromise數(shù)組
        const hash = await hashFile(file); //文件 hash
        // 獲取文件hash之后,如果需要做斷點(diǎn)續(xù)傳,可以根據(jù)hash值去后臺進(jìn)行校驗(yàn)。
        // 看看是否已經(jīng)上傳過該文件,并且是否已經(jīng)傳送完成以及已經(jīng)上傳的切片。
        for (let i = 0; i < blockCount; i++) {
            const start = i * chunkSize;
            const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
            // 構(gòu)建表單
            const form = new FormData();
            form.append('file', blobSlice.call(file, start, end));
            form.append('name', file.name);
            form.append('total', blockCount);
            form.append('index', i);
            form.append('size', file.size);
            form.append('hash', hash);
            console.log(blockCount, blobSlice.call(file, start, end), i, start, end, file.size);
            // ajax提交 分片,此時 content-type 為 multipart/form-data
            const axiosOptions = {
                onUploadProgress: e => {
                    // 處理上傳的進(jìn)度
                    // console.log(blockCount, i, e, file);
                    ct = document.getElementById("ct")
                    ct.textContent = ct.textContent + `第${i}分片上傳完成\n\r`
                },
            };
            // 加入到 Promise 數(shù)組中
            axiosPromiseArray.push(axios.post('/uploadFile', form, axiosOptions));
        }
        await axios.all(axiosPromiseArray).then((result) => {
            // 合并chunks
            const data = {
                size: file.size,
                name: file.name,
                total: blockCount,
                hash
            };
            const form = new FormData();
            form.append('size', file.size);
            form.append('name', file.name);
            form.append('total', blockCount);
            form.append('hash', hash);
            console.log(result);
            axios.post("/file/chunks", form).then(res => {
                //console.log(res)
                ct = document.getElementById("ct")
                ct.textContent = ct.textContent + `上傳完成\n\r`
                console.log("全部上傳完畢");
            })
        }).catch((err) => {
        });
    }
</script>
</html>

2.后端代碼

package main
import (
	"bufio"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"syscall"
)
var dir, _ = os.Getwd()
var uploadPath = path.Join(dir, "uploads")
var uploadTempPath = path.Join(uploadPath, "temp")
// 加載html前段頁面
func home(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	t, err := template.ParseFiles("static/index.html")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	t.Execute(w, "")
	return
}
// PathExists 判斷文件夾是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
func uploadFile(w http.ResponseWriter, r *http.Request) {
	file, _, err := r.FormFile("file")
	index := r.PostFormValue("index")
	hash := r.PostFormValue("hash")
	// 獲取uploads下所有的文件夾
	nameList, err := ioutil.ReadDir(uploadPath)
	m := map[string]interface{}{
		"code": 46900,
		"msg":  "文件已上傳",
	}
	result, _ := json.MarshalIndent(m, "", "    ")
	// 循環(huán)判斷hash是否在文件里如果有就返回上傳已完成
	for _, name := range nameList {
		tmpName := strings.Split(name.Name(), "_")[0]
		if tmpName == hash {
			fmt.Fprintf(w, string(result))
			return
		}
	}
	chunksPath := path.Join(uploadTempPath, hash, "/")
	isPathExists, err := PathExists(chunksPath)
	if !isPathExists {
		err = os.MkdirAll(chunksPath, os.ModePerm)
	}
	destFile, err := os.OpenFile(path.Join(chunksPath+"/"+hash+"-"+index), syscall.O_CREAT|syscall.O_WRONLY, 0777)
	reader := bufio.NewReader(file)
	writer := bufio.NewWriter(destFile)
	buf := make([]byte, 1024*1024) // 1M buf
	for {
		n, err := reader.Read(buf)
		if err == io.EOF {
			writer.Flush()
			break
		} else if err != nil {
			return
		} else {
			writer.Write(buf[:n])
		}
	}
	defer file.Close()
	defer destFile.Close()
	if err != nil {
		log.Fatal("%v", err)
	}
	fmt.Printf("第%s:%s塊上傳完成\n", index, destFile.Name())
}
// 合并文件
func chunks(w http.ResponseWriter, r *http.Request) {
	size, _ := strconv.ParseInt(r.PostFormValue("size"), 10, 64)
	hash := r.PostFormValue("hash")
	name := r.PostFormValue("name")
	toSize, _ := getDirSize(path.Join(uploadTempPath, hash, "/"))
	if size != toSize {
		fmt.Fprintf(w, "文件上傳錯誤")
	}
	chunksPath := path.Join(uploadTempPath, hash, "/")
	files, _ := ioutil.ReadDir(chunksPath)
	// 排序
	filesSort := make(map[string]string)
	for _, f := range files {
		nameArr := strings.Split(f.Name(), "-")
		filesSort[nameArr[1]] = f.Name()
	}
	saveFile := path.Join(uploadPath, name)
	if exists, _ := PathExists(saveFile); exists {
		os.Remove(saveFile)
	}
	fs, _ := os.OpenFile(saveFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, os.ModeAppend|os.ModePerm)
	var wg sync.WaitGroup
	filesCount := len(files)
	if filesCount != len(filesSort) {
		fmt.Fprintf(w, "文件上傳錯誤2")
	}
	wg.Add(filesCount)
	for i := 0; i < filesCount; i++ {
		// 這里一定要注意按順序讀取不然文件就會損壞
		fileName := path.Join(chunksPath, "/"+filesSort[strconv.Itoa(i)])
		data, err := ioutil.ReadFile(fileName)
		fmt.Println(err)
		fs.Write(data)
		wg.Done()
	}
	wg.Wait()
	os.RemoveAll(path.Join(chunksPath, "/"))
	m := map[string]interface{}{
		"code": 20000,
		"msg":  "上傳成功",
	}
	result, _ := json.MarshalIndent(m, "", "    ")
	fmt.Fprintf(w, string(result))
	defer fs.Close()
}
// 獲取整體文件夾大小
func getDirSize(path string) (int64, error) {
	var size int64
	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			size += info.Size()
		}
		return err
	})
	return size, err
}
func main() {
	http.HandleFunc("/", home) // set router
	http.HandleFunc("/uploadFile", uploadFile)
	http.HandleFunc("/file/chunks", chunks)
	err := http.ListenAndServe(":8080", nil) // set listen port
	if err != nil {
		log.Fatal("Error while starting GO http server on port - 8080 : ", err) //log error and exit in case of error at server boot up
	}
}

效果:

JS改進(jìn):

將文件分割和上傳同步進(jìn)行,提升整體上傳速度

 
    const chunkSize = 2 * 1024 * 1024; // 每個chunk的大小,設(shè)置為2兆
    const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
    const spark = new SparkMD5.ArrayBuffer();
    const getHash = (file) => {
        const result = spark.end();
        const sparkMd5 = new SparkMD5();
        sparkMd5.append(result);
        sparkMd5.append(file.name);
        return sparkMd5.end();
    }
    const uploadFile = async (file, hash) => {
        return
    }
    const upload = async () => {
        const fileDom = $('#file')[0];
        // 獲取到的files為一個File對象數(shù)組,如果允許多選的時候,文件為多個
        const files = fileDom.files;
        const file = files[0];
        if (!file) {
            alert('沒有獲取文件');
            return;
        }
        const blockCount = Math.ceil(file.size / chunkSize); // 分片總數(shù)
        const axiosPromiseArray = []; // axiosPromise數(shù)組
        const hash = getHash(file); //文件 hash
        // 獲取文件hash之后,如果需要做斷點(diǎn)續(xù)傳,可以根據(jù)hash值去后臺進(jìn)行校驗(yàn)。
        // 看看是否已經(jīng)上傳過該文件,并且是否已經(jīng)傳送完成以及已經(jīng)上傳的切片。
        new Promise((resolve, reject) => {
            const chunks = Math.ceil(file.size / chunkSize);
            let currentChunk = 0;
            const fileReader = new FileReader();
            const loadNext = () => {
                const start = currentChunk * chunkSize;
                const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
                // 構(gòu)建表單
                const form = new FormData();
                form.append('file', blobSlice.call(file, start, end));
                form.append('name', file.name);
                form.append('total', blockCount);
                form.append('index', currentChunk);
                form.append('size', file.size);
                form.append('hash', hash);
                // ajax提交 分片,此時 content-type 為 multipart/form-data
                const axiosOptions = {
                    onUploadProgress: e => {
                        ct = document.getElementById("ct")
                        ct.textContent = ct.textContent + `第${currentChunk}分片上傳完成\n\r`
                    },
                };
                // 加入到 Promise 數(shù)組中
                axiosPromiseArray.push(axios.post('/uploadFile', form, axiosOptions));
            }
            while (currentChunk < chunks){
                loadNext()
                currentChunk++;
            }
        }).catch(err => {
            console.log(err);
        });
        await axios.all(axiosPromiseArray).then((result) => {
            // 合并chunks
            const data = {
                size: file.size,
                name: file.name,
                total: blockCount,
                hash
            };
            const form = new FormData();
            form.append('size', file.size);
            form.append('name', file.name);
            form.append('total', blockCount);
            form.append('hash', hash);
            console.log(result);
            axios.post("/file/chunks", form).then(res => {
                //console.log(res)
                ct = document.getElementById("ct")
                ct.textContent = ct.textContent + `上傳完成\n\r`
                console.log("全部上傳完畢");
            })
        }).catch((err) => {
        });
    }

總結(jié)

到此這篇關(guān)于golang實(shí)現(xiàn)大文件上傳功能的文章就介紹到這了,更多相關(guān)golang實(shí)現(xiàn)大文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Go語言中如何高效遍歷目錄

    詳解Go語言中如何高效遍歷目錄

    目錄遍歷是一個很常見的操作,它的使用場景有如文件目錄查看、文件系統(tǒng)清理、日志分析、項(xiàng)目構(gòu)建等,本文將詳細(xì)介紹在Go中幾種遍歷目錄文件的方法,需要的可以參考下
    2024-02-02
  • Golang時間處理中容易踩的坑分析解決

    Golang時間處理中容易踩的坑分析解決

    這篇文章主要為大家介紹了Golang時間處理中容易踩的坑分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 淺析Go語言中的同步與異步處理

    淺析Go語言中的同步與異步處理

    在開發(fā)過程中,當(dāng)需要同時處理多個操作時,開發(fā)者經(jīng)常面臨同步和異步兩種處理方式的選擇,下面小編就來和大家詳細(xì)介紹一下Go語言中的同步與異步處理吧
    2023-11-11
  • golang中sync.Map并發(fā)創(chuàng)建、讀取問題實(shí)戰(zhàn)記錄

    golang中sync.Map并發(fā)創(chuàng)建、讀取問題實(shí)戰(zhàn)記錄

    這篇文章主要給大家介紹了關(guān)于golang中sync.Map并發(fā)創(chuàng)建、讀取問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式

    golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式

    這篇文章介紹了golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-04-04
  • Go中Goroutines輕量級并發(fā)的特性及效率探究

    Go中Goroutines輕量級并發(fā)的特性及效率探究

    這篇文章主要為大家介紹了Go中Goroutines輕量級并發(fā)的特性及效率探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Golang中Channel實(shí)戰(zhàn)技巧與一些說明

    Golang中Channel實(shí)戰(zhàn)技巧與一些說明

    channel是Go語言內(nèi)建的first-class類型,也是Go語言與眾不同的特性之一,下面這篇文章主要給大家介紹了關(guān)于Golang中Channel實(shí)戰(zhàn)技巧與一些說明的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11
  • 詳解go-micro微服務(wù)consul配置及注冊中心

    詳解go-micro微服務(wù)consul配置及注冊中心

    這篇文章主要為大家介紹了go-micro微服務(wù)consul配置及注冊中心示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Go語言面試題之select和channel的用法

    Go語言面試題之select和channel的用法

    金九銀十面試季到了(PS:貌似今年一年都是面試季),就業(yè)環(huán)境很差,導(dǎo)致從業(yè)人員不得不卷。本文將重點(diǎn)講解一下Go面試進(jìn)階知識點(diǎn)之select和channel,需要的可以參考一下
    2022-09-09
  • Golang加密解密之RSA(附帶php)

    Golang加密解密之RSA(附帶php)

    安全總是很重要的,各個語言對于通用的加密算法都會有實(shí)現(xiàn)。本文先是對RSA算法進(jìn)行了簡單介紹,后才進(jìn)行介紹如何用Go實(shí)現(xiàn)RSA的加密解密,下面一起來看看吧。
    2016-08-08

最新評論