【实践分享】Golang高效实现文件上传下载详解
【实践分享】Golang高效实现文件上传下载详解
随着互联网的飞速发展,文件上传和下载已经成为我们日常开发工作中不可避免的一部分。面对海量数据的上传和下载,如何提高效率、保证数据安全是我们需要考虑的问题。本文将通过实践分享,介绍如何使用Golang高效实现文件上传下载。
一、上传文件
1.1 静态文件上传
以下是一个简单的静态文件上传的示例:
```
func uploadFile(w http.ResponseWriter, r *http.Request) {
file, handler, err := r.FormFile("file") // 从表单中获取文件
if err != nil {
fmt.Println("Error Retrieving the File")
fmt.Println(err)
return
}
defer file.Close()
fmt.Printf("Uploaded File: %+v\n", handler.Filename)
fmt.Printf("File Size: %+v\n", handler.Size)
fmt.Printf("MIME Header: %+v\n", handler.Header)
// 将文件写入本地磁盘
f, err := os.OpenFile(handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
io.Copy(f, file)
fmt.Fprintf(w, "Successfully Uploaded File\n")
}
```
代码中通过r.FormFile从表单中获取文件,然后通过os.OpenFile将文件写入本地磁盘。代码简单易懂,但存在以下问题:
- 无法处理大文件,会导致内存占用过大甚至崩溃;
- 无法处理文件上传的进度反馈;
- 无法处理同时上传多个文件的情况。
1.2 分块上传
分块上传是解决上传大文件的有效方法,即将大文件分成若干个块分别上传,最终合并成一个完整的文件。下面是一个简单的分块上传示例:
```
type UploadReq struct {
FileChunk *multipart.FileHeader
ChunkIndex int64
TotalChunks int64
FileName string
}
type UploadRes struct {
URL string `json:"url"`
}
type Chunk struct {
Index int64
Data []byte
}
func uploadChunk(w http.ResponseWriter, r *http.Request) {
req := UploadReq{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
file, err := req.FileChunk.Open()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
chunk := Chunk{
Index: req.ChunkIndex,
}
chunk.Data, err = ioutil.ReadAll(file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 保存文件块
key := fmt.Sprintf("%v/%v/%v", req.FileName, req.TotalChunks, req.ChunkIndex)
err = saveChunkToFile(key, chunk.Data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
res := UploadRes{URL: fmt.Sprintf("%v/%v/%v", req.FileName, req.TotalChunks, req.ChunkIndex)}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
```
代码中接收上传的文件块,将文件块保存到本地,最后返回块的URL。使用分块上传可以解决上传大文件的内存占用过大的问题,并且可以处理上传文件的进度反馈。
1.3 并发上传
并发上传是提升上传效率的重要方式,通过将文件分成若干个块并发上传,可以有效提高上传速度。以下是一个简单的并发上传示例:
```
func uploadFileConcurrently(w http.ResponseWriter, r *http.Request) {
file, handler, err := r.FormFile("file")
if err != nil {
fmt.Println("Error Retrieving the File")
fmt.Println(err)
return
}
defer file.Close()
fmt.Printf("Uploaded File: %+v\n", handler.Filename)
fmt.Printf("File Size: %+v\n", handler.Size)
fmt.Printf("MIME Header: %+v\n", handler.Header)
// 将文件分成若干个块,每个块大小为4MB
chunkSize := 4 * 1024 * 1024
totalChunks := int(math.Ceil(float64(handler.Size) / float64(chunkSize)))
chunks := make([]Chunk, totalChunks)
for i := 0; i < totalChunks; i++ {
offset := int64(i * chunkSize)
size := int64(chunkSize)
if i == totalChunks-1 {
size = handler.Size - offset
}
data := make([]byte, size)
_, err := file.ReadAt(data, offset)
if err != nil {
fmt.Println(err)
return
}
chunks[i] = Chunk{Index: int64(i), Data: data}
}
// 并发上传文件块
wg := sync.WaitGroup{}
for i, chunk := range chunks {
wg.Add(1)
go func(index int, data []byte) {
defer wg.Done()
key := fmt.Sprintf("%v/%v/%v", handler.Filename, totalChunks, index)
err := saveChunkToFile(key, data)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Uploaded Chunk %v Successfully\n", index)
}(i, chunk.Data)
}
wg.Wait()
// 合并文件块
err = mergeChunksToFile(handler.Filename, totalChunks)
if err != nil {
fmt.Println(err)
return
}
fmt.Fprintf(w, "Successfully Uploaded File\n")
}
```
代码中将文件分成若干个块,每个块大小为4MB,并发上传文件块,最后将块合并成一个完整的文件。使用并发上传可以提高上传效率,适用于大文件上传的场景。
二、下载文件
2.1 直接下载文件
以下是一个简单的文件下载示例:
```
func downloadFile(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("test.zip")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileInfo.Name()))
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
_, err = io.Copy(w, file)
if err != nil {
fmt.Println(err)
return
}
}
```
代码中将文件直接下载,通过Set方法设置文件名、文件类型、文件长度等信息。代码简单易懂,但存在以下问题:
- 无法处理大文件,会导致内存占用过大甚至崩溃;
- 无法处理文件下载的进度反馈。
2.2 分块下载
与分块上传类似,分块下载也是解决下载大文件的有效方法,即将大文件分成若干个块分别下载,最终合并成一个完整的文件。以下是一个简单的分块下载示例:
```
func downloadFileChunked(w http.ResponseWriter, r *http.Request) {
fileName := "test.zip"
file, err := os.Open(fileName)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
chunkSize := 4 * 1024 * 1024
totalChunks := int(math.Ceil(float64(fileInfo.Size()) / float64(chunkSize)))
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileInfo.Name()))
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
// 分块下载文件
for i := 0; i < totalChunks; i++ {
offset := int64(i * chunkSize)
size := int64(chunkSize)
if i == totalChunks-1 {
size = fileInfo.Size() - offset
}
data := make([]byte, size)
_, err := file.ReadAt(data, offset)
if err != nil {
fmt.Println(err)
return
}
_, err = w.Write(data)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Downloaded Chunk %v Successfully\n", i)
}
}
```
代码中将文件分成若干个块,分块下载文件,并在下载完成后进行合并。使用分块下载可以解决下载大文件的内存占用过大的问题,并且可以处理下载文件的进度反馈。
总结:
本文阐述了Golang高效实现文件上传下载的方法和技巧,通过使用分块上传和分块下载,可以提高上传下载效率并避免内存占用过大的问题。通过示例代码,读者可以更好地了解Golang实现文件上传下载的具体实现。