Go語(yǔ)言實(shí)現(xiàn)大文件分片上傳與斷點(diǎn)續(xù)傳功能
在現(xiàn)代 Web 應(yīng)用中,大文件上傳是一個(gè)常見(jiàn)需求。直接上傳大文件會(huì)遇到網(wǎng)絡(luò)中斷、上傳超時(shí)、失敗后無(wú)法續(xù)傳等問(wèn)題。為了解決這些痛點(diǎn),通常采用 分片上傳 與 斷點(diǎn)續(xù)傳 的方案。
本文將帶你使用 Go 實(shí)現(xiàn)一個(gè)簡(jiǎn)易的大文件分片上傳與斷點(diǎn)續(xù)傳服務(wù)。
核心思路
1.分片上傳
將大文件切分成多個(gè)小塊(chunk),逐塊上傳,避免單次上傳過(guò)大失敗。
2.斷點(diǎn)續(xù)傳
每個(gè)文件有唯一的標(biāo)識(shí)(如 MD5、UUID),服務(wù)端記錄已上傳的分片信息,上傳中斷后可繼續(xù)上傳剩余分片。
.3合并分片
當(dāng)所有分片上傳完成后,服務(wù)端將這些分片按順序合并成完整文件。
服務(wù)端實(shí)現(xiàn)
上傳接口設(shè)計(jì)
- 初始化上傳:生成 uploadId,返回客戶(hù)端。
- 上傳分片:
/upload/chunk?uploadId=xxx&chunkIndex=1 - 合并分片:所有分片傳完后,調(diào)用
/upload/merge合并。
Go 代碼示例
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
)
// 臨時(shí)存放分片目錄
const uploadDir = "./uploads"
func main() {
// 創(chuàng)建目錄
os.MkdirAll(uploadDir, os.ModePerm)
http.HandleFunc("/upload/chunk", uploadChunkHandler)
http.HandleFunc("/upload/merge", mergeChunksHandler)
fmt.Println("服務(wù)已啟動(dòng): http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
// 上傳分片
func uploadChunkHandler(w http.ResponseWriter, r *http.Request) {
uploadId := r.URL.Query().Get("uploadId")
chunkIndex := r.URL.Query().Get("chunkIndex")
if uploadId == "" || chunkIndex == "" {
http.Error(w, "缺少參數(shù)", http.StatusBadRequest)
return
}
// 保存分片目錄
chunkDir := filepath.Join(uploadDir, uploadId)
os.MkdirAll(chunkDir, os.ModePerm)
// 分片文件路徑
chunkPath := filepath.Join(chunkDir, chunkIndex)
// 寫(xiě)入文件
file, err := os.Create(chunkPath)
if err != nil {
http.Error(w, "無(wú)法保存分片", http.StatusInternalServerError)
return
}
defer file.Close()
_, err = io.Copy(file, r.Body)
if err != nil {
http.Error(w, "寫(xiě)入分片失敗", http.StatusInternalServerError)
return
}
w.Write([]byte("分片上傳成功: " + chunkIndex))
}
// 合并分片
func mergeChunksHandler(w http.ResponseWriter, r *http.Request) {
uploadId := r.URL.Query().Get("uploadId")
totalChunks := r.URL.Query().Get("totalChunks")
if uploadId == "" || totalChunks == "" {
http.Error(w, "缺少參數(shù)", http.StatusBadRequest)
return
}
chunkDir := filepath.Join(uploadDir, uploadId)
mergedFile := filepath.Join(uploadDir, uploadId+"_merged")
// 創(chuàng)建目標(biāo)文件
dst, err := os.Create(mergedFile)
if err != nil {
http.Error(w, "無(wú)法創(chuàng)建合并文件", http.StatusInternalServerError)
return
}
defer dst.Close()
// 合并分片
chunkCount, _ := strconv.Atoi(totalChunks)
for i := 1; i <= chunkCount; i++ {
chunkPath := filepath.Join(chunkDir, strconv.Itoa(i))
src, err := os.Open(chunkPath)
if err != nil {
http.Error(w, fmt.Sprintf("缺少分片 %d", i), http.StatusBadRequest)
return
}
_, err = io.Copy(dst, src)
src.Close()
if err != nil {
http.Error(w, "合并失敗", http.StatusInternalServerError)
return
}
}
w.Write([]byte("文件合并完成: " + mergedFile))
}
客戶(hù)端實(shí)現(xiàn)思路
前端/客戶(hù)端需要
- 將文件分割為多個(gè) 固定大小的 chunk。
- 順序調(diào)用 /upload/chunk 上傳分片。
- 上傳完成后調(diào)用 /upload/merge 合并文件。
- 如果上傳中斷,查詢(xún)已上傳分片,從斷點(diǎn)繼續(xù)。
示例偽代碼:
async function uploadFile(file) {
const chunkSize = 5 * 1024 * 1024; // 5MB
const totalChunks = Math.ceil(file.size / chunkSize);
const uploadId = generateUploadId(file); // 可用 MD5/UUID
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const chunk = file.slice(start, end);
let formData = new FormData();
formData.append("file", chunk);
await fetch(`/upload/chunk?uploadId=${uploadId}&chunkIndex=${i+1}`, {
method: "POST",
body: chunk
});
}
await fetch(`/upload/merge?uploadId=${uploadId}&totalChunks=${totalChunks}`);
}
優(yōu)化點(diǎn)
分片校驗(yàn):上傳前計(jì)算 MD5,避免重復(fù)上傳。
并行上傳:可同時(shí)并發(fā)上傳多個(gè)分片,提升速度。
斷點(diǎn)續(xù)傳:服務(wù)端保存已上傳分片信息,客戶(hù)端查詢(xún)后跳過(guò)已完成分片。
秒傳功能:通過(guò)文件 Hash 判斷文件是否已存在,直接返回完成。
總結(jié)
通過(guò) Go 實(shí)現(xiàn)大文件分片上傳與斷點(diǎn)續(xù)傳,可以有效提升上傳成功率和用戶(hù)體驗(yàn)。實(shí)際項(xiàng)目中,還需要結(jié)合 數(shù)據(jù)庫(kù)記錄、鑒權(quán)、并發(fā)控制、文件存儲(chǔ)(如 OSS、S3、MinIO) 等功能,構(gòu)建更健壯的文件上傳系統(tǒng)。
到此這篇關(guān)于Go語(yǔ)言實(shí)現(xiàn)大文件分片上傳與斷點(diǎn)續(xù)傳功能的文章就介紹到這了,更多相關(guān)Go大文件上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言并發(fā)編程之控制并發(fā)數(shù)量實(shí)現(xiàn)實(shí)例
這篇文章主要為大家介紹了Go語(yǔ)言并發(fā)編程之控制并發(fā)數(shù)量實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Golang切片連接成字符串的實(shí)現(xiàn)示例
本文主要介紹了Golang切片連接成字符串的實(shí)現(xiàn)示例,可以使用Go語(yǔ)言中的內(nèi)置函數(shù)"String()"可以將字節(jié)切片轉(zhuǎn)換為字符串,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11
基于Go語(yǔ)言實(shí)現(xiàn)一個(gè)并發(fā)下載器
這篇文章主要為大家詳細(xì)介紹了如何利用GO語(yǔ)言實(shí)現(xiàn)一個(gè)并發(fā)的文件下載器,可以在不重新啟動(dòng)整個(gè)下載的情況下處理錯(cuò)誤,感興趣的小伙伴可以了解一下2023-10-10
Go語(yǔ)言使用Zap輕松搞定結(jié)構(gòu)化日志
在?Go?語(yǔ)言中,有許多日志庫(kù)可供選擇,但在性能和靈活性方面,Zap?是其中的佼佼者,下面我們就來(lái)看看Go?項(xiàng)目中如何使用?Zap?進(jìn)行結(jié)構(gòu)化日志記錄吧2024-11-11
go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)錢(qián)包的原理解析
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)錢(qián)包的原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Go語(yǔ)言sync.Map詳解及使用場(chǎng)景
Go語(yǔ)言中的sync.Map是一個(gè)高效的并發(fā)安全映射結(jié)構(gòu),適用于高并發(fā)讀多寫(xiě)少的場(chǎng)景,它通過(guò)讀寫(xiě)分離、無(wú)鎖讀取路徑、寫(xiě)入時(shí)的鎖保護(hù)等機(jī)制,提高了讀取性能并減少了鎖競(jìng)爭(zhēng),sync.Map不需要手動(dòng)管理鎖,降低了編程復(fù)雜性,適合需要簡(jiǎn)單并發(fā)訪(fǎng)問(wèn)的場(chǎng)合2024-10-10
淺談Go連接池的設(shè)計(jì)與實(shí)現(xiàn)
本文主要介紹了淺談Go連接池的設(shè)計(jì)與實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04

