Go 实现大文件分片上传与断点续传

在现代 Web 应用中,大文件上传是一个常见需求。直接上传大文件会遇到网络中断、上传超时、失败后无法续传等问题。为了解决这些痛点,通常采用 分片上传断点续传 的方案。

本文将带你使用 Go 实现一个简易的大文件分片上传与断点续传服务。


核心思路

  1. 分片上传

    将大文件切分成多个小块(chunk),逐块上传,避免单次上传过大失败。

  2. 断点续传

    每个文件有唯一的标识(如 MD5、UUID),服务端记录已上传的分片信息,上传中断后可继续上传剩余分片。

  3. 合并分片

    当所有分片上传完成后,服务端将这些分片按顺序合并成完整文件。


服务端实现

上传接口设计

  • 初始化上传:生成 uploadId,返回客户端。
  • 上传分片/upload/chunk?uploadId=xxx&chunkIndex=1
  • 合并分片 :所有分片传完后,调用 /upload/merge 合并。

Go 代码示例

go 复制代码
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
)

// 临时存放分片目录
const uploadDir = "./uploads"

func main() {
	// 创建目录
	os.MkdirAll(uploadDir, os.ModePerm)

	http.HandleFunc("/upload/chunk", uploadChunkHandler)
	http.HandleFunc("/upload/merge", mergeChunksHandler)

	fmt.Println("服务已启动: 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, "缺少参数", http.StatusBadRequest)
		return
	}

	// 保存分片目录
	chunkDir := filepath.Join(uploadDir, uploadId)
	os.MkdirAll(chunkDir, os.ModePerm)

	// 分片文件路径
	chunkPath := filepath.Join(chunkDir, chunkIndex)

	// 写入文件
	file, err := os.Create(chunkPath)
	if err != nil {
		http.Error(w, "无法保存分片", http.StatusInternalServerError)
		return
	}
	defer file.Close()

	_, err = io.Copy(file, r.Body)
	if err != nil {
		http.Error(w, "写入分片失败", 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, "缺少参数", http.StatusBadRequest)
		return
	}

	chunkDir := filepath.Join(uploadDir, uploadId)
	mergedFile := filepath.Join(uploadDir, uploadId+"_merged")

	// 创建目标文件
	dst, err := os.Create(mergedFile)
	if err != nil {
		http.Error(w, "无法创建合并文件", 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))
}

客户端实现思路

前端/客户端需要:

  1. 将文件分割为多个 固定大小的 chunk。
  2. 顺序调用 /upload/chunk 上传分片。
  3. 上传完成后调用 /upload/merge 合并文件。
  4. 如果上传中断,查询已上传分片,从断点继续。

示例伪代码:

go 复制代码
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}`);
}

优化点

分片校验:上传前计算 MD5,避免重复上传。

并行上传:可同时并发上传多个分片,提升速度。

断点续传:服务端保存已上传分片信息,客户端查询后跳过已完成分片。

秒传功能:通过文件 Hash 判断文件是否已存在,直接返回完成。

总结

通过 Go 实现大文件分片上传与断点续传,可以有效提升上传成功率和用户体验。实际项目中,还需要结合 数据库记录、鉴权、并发控制、文件存储(如 OSS、S3、MinIO) 等功能,构建更健壮的文件上传系统。

相关推荐
Cache技术分享2 小时前
186. Java 模式匹配 - Java 21 新特性:Record Pattern(记录模式匹配)
前端·javascript·后端
福大大架构师每日一题2 小时前
2025-09-12:删除元素后 K 个字符串的最长公共前缀。用go语言,给定一个字符串数组 words 和一个整数 k。对于数组中每个位置 i,先把下标为 i
后端
Python私教2 小时前
Django全栈班v1.01 Python简介与特点 20250910
后端·python·django
AAA修煤气灶刘哥2 小时前
从 Timer 到 XXL-Job,定时任务调度的 “进化史”,看完再也不怕漏跑任务~
java·后端·架构
zjjuejin2 小时前
Docker Swarm 完全指南:从原理到实战
后端·docker
shark_chili2 小时前
深入GPU核心:理解现代并行计算的硬件架构
后端
乘风破浪酱524362 小时前
MyBatis-Plus UserMpper接口示例
后端
无奈何杨2 小时前
风控系统的事中与事后一致性与闭环
前端·后端
这里有鱼汤3 小时前
为什么指数涨你却亏钱?80%的人忽略的市场宽度指标揭晓,我用Python实现了(附源码)
后端·python