【实践分享】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实现文件上传下载的具体实现。