gouploader

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 9, 2026 License: MIT Imports: 13 Imported by: 0

README

gouploader

生产级文件上传工具包,适用于 Gin 框架。支持普通上传和分片断点续传。

功能特性

  • 普通上传 — 单文件上传,流式 I/O,内存恒定
  • 分片上传 — 大文件拆分为分片,支持并行上传
  • 断点续传 — 查询缺失分片,只补传断开的部分
  • 可插拔存储 — 本地磁盘或 S3 兼容服务(MinIO / 阿里云 OSS / 腾讯云 COS / 华为云 OBS / AWS S3)
  • 自定义存储 — 实现 Storage 接口即可对接任意后端
  • 文件校验 — MD5 / SHA256 校验和验证(普通上传和分片上传均支持)
  • 并发安全 — 会话级互斥锁、原子文件写入、幂等分片上传
  • 内存安全 — 全链路流式 I/O,永远不会将完整文件加载到内存
  • 安全防护 — 目录穿越防护、表单字段大小限制、分片大小强制限制
  • 跨域支持 — 内置 CORS 中间件
  • 回调机制 — 上传完成后触发业务逻辑(存库、通知等)

安装

go get github.com/gtkit/gouploader

快速开始

package main

import (
    "log"

    "github.com/gin-gonic/gin"
    "github.com/gtkit/gouploader"
)

func main() {
    r := gin.Default()

    uploader, err := gouploader.New(
        gouploader.WithLocalStorage("./uploads"),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer uploader.Close()

    uploader.RegisterRoutes(r.Group("/api/v1/upload"))
    r.Run(":8080")
}

配置选项

所有选项通过函数式选项传入 gouploader.New()

uploader, err := gouploader.New(
    // 存储后端(三选一)
    gouploader.WithLocalStorage("./uploads"),
    // 或
    gouploader.WithS3Storage(gouploader.S3Config{...}),
    // 或
    gouploader.WithStorage(myCustomStorage),

    // 上传限制
    gouploader.WithMaxFileSize(10 << 30),                        // 10GB,默认 10GB
    gouploader.WithAllowedTypes(".jpg", ".png", ".mp4", ".pdf"), // 空=允许所有类型
    gouploader.WithHashAlgorithm("sha256"),                      // "md5" 或 "sha256",默认 "sha256"

    // 分片设置
    gouploader.WithChunkTempDir("./tmp/chunks"),                 // 默认 "./tmp/chunks"
    gouploader.WithChunkSize(5<<20, 1<<20, 20<<20),             // 默认/最小/最大,默认 5MB/1MB/20MB
    gouploader.WithChunkSessionTTL(24 * time.Hour),              // 默认 24 小时
    gouploader.WithChunkCleanupInterval(1 * time.Hour),          // 默认 1 小时

    // STS 直传(可选,配置后启用前端直传云存储)
    gouploader.WithAliyunSTS(gouploader.AliyunSTSConfig{
        AccessKeyID:     "LTAI...",
        AccessKeySecret: "xxx",
        RoleArn:         "acs:ram::123456:role/oss-upload-role",
        OSSEndpoint:     "oss-cn-hangzhou.aliyuncs.com",
        Bucket:          "my-bucket",
        Region:          "cn-hangzhou",
    }),
    gouploader.WithOnDirectUploadComplete(func(result *gouploader.DirectUploadResult) {
        db.SaveDirectFile(result.Key, result.Size)
    }),

    // 回调
    gouploader.WithOnUploadComplete(func(result *gouploader.UploadResult) {
        // 每次上传成功后触发(普通上传和分片合并均触发)
        // 用途:存数据库、发通知、触发后续处理等
        db.SaveFile(result.FileID, result.FileName, result.StorageKey, result.Checksum)
    }),
)
选项参考
选项 默认值 说明
WithLocalStorage(path) "./uploads" 本地磁盘存储
WithS3Storage(S3Config) - S3 兼容对象存储
WithStorage(Storage) - 自定义 Storage 实现
WithMaxFileSize(bytes) 10GB 允许的最大文件大小
WithAllowedTypes(exts...) 全部 允许的文件扩展名
WithHashAlgorithm(algo) "sha256" 校验和算法
WithChunkTempDir(dir) "./tmp/chunks" 分片临时目录
WithChunkSize(默认, 最小, 最大) 5MB/1MB/20MB 分片大小限制
WithChunkSessionTTL(duration) 24h 上传会话过期时间
WithChunkCleanupInterval(duration) 1h 过期会话清理间隔
WithOnUploadComplete(fn) - 上传完成回调

S3 兼容存储

一套实现覆盖所有 S3 兼容服务,只需修改 Endpoint

// MinIO
gouploader.WithS3Storage(gouploader.S3Config{
    Endpoint:  "127.0.0.1:9000",
    AccessKey: "minioadmin",
    SecretKey: "minioadmin",
    Bucket:    "uploads",
    UseSSL:    false,
})

// 阿里云 OSS
gouploader.WithS3Storage(gouploader.S3Config{
    Endpoint:  "oss-cn-hangzhou.aliyuncs.com",
    AccessKey: "your-access-key",
    SecretKey: "your-secret-key",
    Bucket:    "my-bucket",
    Region:    "oss-cn-hangzhou",
    UseSSL:    true,
})

// 腾讯云 COS
gouploader.WithS3Storage(gouploader.S3Config{
    Endpoint:  "cos.ap-guangzhou.myqcloud.com",
    AccessKey: "your-access-key",
    SecretKey: "your-secret-key",
    Bucket:    "my-bucket-1250000000",
    Region:    "ap-guangzhou",
    UseSSL:    true,
})

// 华为云 OBS
gouploader.WithS3Storage(gouploader.S3Config{
    Endpoint:  "obs.cn-north-4.myhuaweicloud.com",
    AccessKey: "your-access-key",
    SecretKey: "your-secret-key",
    Bucket:    "my-bucket",
    Region:    "cn-north-4",
    UseSSL:    true,
})

// AWS S3
gouploader.WithS3Storage(gouploader.S3Config{
    Endpoint:  "s3.us-east-1.amazonaws.com",
    AccessKey: "AKIAIOSFODNN7EXAMPLE",
    SecretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    Bucket:    "my-bucket",
    Region:    "us-east-1",
    UseSSL:    true,
})

自定义存储

实现 Storage 接口即可对接任意后端:

type Storage interface {
    Save(ctx context.Context, key string, reader io.Reader, size int64) error
    Get(ctx context.Context, key string) (io.ReadCloser, error)
    Delete(ctx context.Context, key string) error
    Exists(ctx context.Context, key string) (bool, error)
}
uploader, _ := gouploader.New(
    gouploader.WithStorage(myFTPStorage),
)

跨域配置

内置 CORS 中间件,支持前端跨域请求:

group := r.Group("/api/v1/upload")
group.Use(gouploader.CORS(gouploader.CORSConfig{
    AllowOrigins:     []string{"https://your-frontend.com"},
    AllowCredentials: true,
}))
uploader.RegisterRoutes(group)

路由注册

自动注册(推荐)
uploader.RegisterRoutes(r.Group("/api/v1/upload"))

注册以下 6 个路由:

方法 路径 说明
POST /api/v1/upload 普通单文件上传
POST /api/v1/upload/chunk/init 初始化分片上传会话
POST /api/v1/upload/chunk 上传单个分片
GET /api/v1/upload/chunk/status/:upload_id 查询上传进度(断点续传)
POST /api/v1/upload/chunk/merge/:upload_id 合并所有分片
DELETE /api/v1/upload/chunk/:upload_id 中止并清理分片上传

如果配置了 STS 直传,还会注册:

方法 路径 说明
POST /api/v1/upload/direct/token 签发 STS 临时凭证
POST /api/v1/upload/direct/complete 前端确认上传完成
POST /api/v1/upload/direct/callback 云存储回调(仅当 Provider 实现了 CallbackVerifier 时注册)
手动注册(搭配自定义中间件)
auth := myAuthMiddleware()

r.POST("/upload",                        auth, uploader.HandleUpload())
r.POST("/upload/chunk/init",             auth, uploader.HandleChunkInit())
r.POST("/upload/chunk",                  auth, uploader.HandleChunkUpload())
r.GET("/upload/chunk/status/:upload_id", auth, uploader.HandleChunkStatus())
r.POST("/upload/chunk/merge/:upload_id", auth, uploader.HandleChunkMerge())
r.DELETE("/upload/chunk/:upload_id",     auth, uploader.HandleChunkAbort())

接口文档

普通上传

POST /api/v1/uploadmultipart/form-data

字段 类型 必填 说明
file file 要上传的文件
checksum string 期望的文件哈希值
algorithm string 哈希算法:md5sha256

响应:

{
  "code": 0,
  "message": "ok",
  "data": {
    "file_id": "550e8400-e29b-41d4-a716-446655440000",
    "file_name": "photo.jpg",
    "file_size": 1048576,
    "storage_key": "2026/04/09/550e8400-e29b-41d4-a716-446655440000.jpg",
    "checksum": "a1b2c3d4..."
  }
}
分片上传 — 初始化

POST /api/v1/upload/chunk/initapplication/json

{
  "file_name": "video.mp4",
  "file_size": 1073741824,
  "chunk_size": 5242880,
  "checksum": "整个文件的sha256",
  "algorithm": "sha256"
}

响应:

{
  "code": 0,
  "data": {
    "upload_id": "550e8400-e29b-41d4-a716-446655440000",
    "chunk_size": 5242880,
    "total_chunks": 205
  }
}
分片上传 — 上传分片

POST /api/v1/upload/chunkmultipart/form-data

重要: upload_idchunk_index 字段必须在 chunk 字段之前。

字段 类型 必填 说明
upload_id string 初始化返回的会话 ID
chunk_index int 分片索引(从 0 开始)
chunk_checksum string 该分片的哈希值
chunk file 分片二进制数据

响应:

{
  "code": 0,
  "data": {
    "upload_id": "550e8400-...",
    "chunk_index": 3,
    "uploaded_count": 4,
    "total_chunks": 205
  }
}
分片上传 — 查询状态(断点续传)

GET /api/v1/upload/chunk/status/:upload_id

响应:

{
  "code": 0,
  "data": {
    "upload_id": "550e8400-...",
    "total_chunks": 205,
    "uploaded_chunks": [0, 1, 2, 3, 7, 8],
    "missing_chunks": [4, 5, 6, 9, 10, 11]
  }
}
分片上传 — 合并

POST /api/v1/upload/chunk/merge/:upload_id

响应:

{
  "code": 0,
  "data": {
    "file_id": "f17c99c7-...",
    "file_name": "video.mp4",
    "file_size": 1073741824,
    "storage_key": "2026/04/09/f17c99c7-....mp4",
    "checksum": "a1b2c3d4..."
  }
}
分片上传 — 中止

DELETE /api/v1/upload/chunk/:upload_id

响应:

{
  "code": 0,
  "message": "ok"
}
前端直传 — 获取 STS 凭证

POST /api/v1/upload/direct/tokenapplication/json

{
  "file_name": "photo.jpg",
  "file_size": 1048576
}

响应:

{
  "code": 0,
  "data": {
    "access_key_id": "STS.xxx",
    "access_key_secret": "xxx",
    "security_token": "xxx",
    "expiration": "2026-04-09T02:00:00Z",
    "region": "cn-hangzhou",
    "bucket": "my-bucket",
    "endpoint": "oss-cn-hangzhou.aliyuncs.com",
    "key_prefix": "direct/2026/04/09/",
    "key": "direct/2026/04/09/550e8400-....jpg"
  }
}
前端直传 — 确认完成

POST /api/v1/upload/direct/completeapplication/json

{
  "key": "direct/2026/04/09/550e8400-....jpg",
  "etag": "\"d41d8cd98f00b204e9800998ecf8427e\""
}

响应:

{
  "code": 0,
  "data": {
    "key": "direct/2026/04/09/550e8400-....jpg",
    "etag": "\"d41d8cd98f00b204e9800998ecf8427e\""
  }
}
错误响应

所有错误遵循统一格式:

{
  "code": 10001,
  "message": "文件超过大小限制",
  "details": "最大允许: 10737418240 字节"
}
错误码 HTTP 状态码 说明
10001 413 文件超过大小限制
10002 400 文件类型不允许
10003 400 校验和不匹配
10005 400 参数无效
10006 400 multipart 解析错误
20001 404 上传会话不存在
20002 410 上传会话已过期
20003 400 分片索引越界
20005 400 分片校验和不匹配
20006 409 分片未全部上传
20007 500 合并后文件校验失败
30001 500 存储写入失败
30002 500 存储读取失败
40001 503 STS 直传未配置
40002 500 STS 凭证签发失败
40003 403 回调验签失败
40004 404 直传文件不存在

前端对接

普通上传(JavaScript)
async function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);

  const response = await fetch('/api/v1/upload', {
    method: 'POST',
    body: formData,
  });
  return response.json();
}
分片断点续传(JavaScript)
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB

async function uploadLargeFile(file) {
  // 第一步:计算文件哈希(可选,用于完整性校验)
  const checksum = await calculateSHA256(file);

  // 第二步:初始化上传会话
  const initRes = await fetch('/api/v1/upload/chunk/init', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      file_name: file.name,
      file_size: file.size,
      chunk_size: CHUNK_SIZE,
      checksum: checksum,
      algorithm: 'sha256',
    }),
  }).then(r => r.json());

  const { upload_id, total_chunks } = initRes.data;

  // 第三步:查询已上传的分片(用于断点续传)
  const statusRes = await fetch(`/api/v1/upload/chunk/status/${upload_id}`)
    .then(r => r.json());
  const missingChunks = statusRes.data.missing_chunks;

  // 第四步:并发上传缺失的分片
  const CONCURRENCY = 3;
  const queue = [...missingChunks];

  async function uploadWorker() {
    while (queue.length > 0) {
      const index = queue.shift();
      const start = index * CHUNK_SIZE;
      const end = Math.min(start + CHUNK_SIZE, file.size);
      const blob = file.slice(start, end);

      const formData = new FormData();
      formData.append('upload_id', upload_id);
      formData.append('chunk_index', String(index));
      formData.append('chunk', blob);

      const res = await fetch('/api/v1/upload/chunk', {
        method: 'POST',
        body: formData,
      }).then(r => r.json());

      console.log(`分片 ${index}/${total_chunks} 完成,共 ${res.data.uploaded_count} 片`);
    }
  }

  await Promise.all(Array.from({ length: CONCURRENCY }, () => uploadWorker()));

  // 第五步:合并
  const mergeRes = await fetch(`/api/v1/upload/chunk/merge/${upload_id}`, {
    method: 'POST',
  }).then(r => r.json());

  console.log('上传完成:', mergeRes.data);
  return mergeRes.data;
}
前端直传云存储(JavaScript)
async function directUpload(file) {
  // 第一步:从后端获取 STS 临时凭证
  const tokenRes = await fetch('/api/v1/upload/direct/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      file_name: file.name,
      file_size: file.size,
    }),
  }).then(r => r.json());

  const { access_key_id, access_key_secret, security_token, endpoint, bucket, key } = tokenRes.data;

  // 第二步:直传到云存储(以阿里云 OSS 为例)
  const ossUrl = `https://${bucket}.${endpoint}/${key}`;
  await fetch(ossUrl, {
    method: 'PUT',
    headers: {
      'Authorization': '...', // 使用 STS 凭证签名(建议使用阿里云 OSS JS SDK)
      'x-oss-security-token': security_token,
    },
    body: file,
  });

  // 第三步:通知后端上传完成
  const completeRes = await fetch('/api/v1/upload/direct/complete', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ key }),
  }).then(r => r.json());

  console.log('直传完成:', completeRes.data);
  return completeRes.data;
}

分片上传流程

前端                                  后端
 |                                     |
 |-- POST /chunk/init --------------->|  创建会话,返回 upload_id
 |<-- { upload_id, total_chunks } ----|
 |                                     |
 |-- POST /chunk(并发 x N)---------->|  流式写入每个分片到磁盘
 |   { upload_id, chunk_index, data } |  校验分片哈希
 |<-- { uploaded_count } -------------|  标记分片已上传(幂等)
 |                                     |
 |   (网络中断、应用重启)              |
 |                                     |
 |-- GET /chunk/status/:id ---------->|  返回已上传和缺失的分片列表
 |<-- { missing_chunks: [4,5,6] } ----|
 |                                     |
 |-- POST /chunk(仅缺失分片)-------->|  只补传断开的部分
 |                                     |
 |-- POST /chunk/merge/:id ---------->|  流式合并所有分片
 |                                     |  校验最终文件哈希
 |<-- { storage_key, checksum } ------|  清理临时数据

前端直传流程

前端                                后端                        云存储
 |                                   |                            |
 |-- POST /direct/token ----------->|                            |
 |   { file_name, file_size }       | 生成 keyPrefix + key       |
 |                                   | 调用 STSProvider 签发凭证  |
 |<-- { credential, key } ----------|                            |
 |                                   |                            |
 |-- PUT 直传到云存储 ------------------------------------------>|
 |   使用 STS 临时凭证签名            |                            |
 |<-- 200 OK ---------------------------------------------------|
 |                                   |                            |
 |-- POST /direct/complete -------->|                            |
 |   { key }                         | 验证文件存在               |
 |                                   | 触发 onDirectUploadComplete|
 |<-- { key, etag } ----------------|                            |

项目结构

gouploader/
├── gouploader.go              # 入口:Uploader + New() + RegisterRoutes()
├── options.go               # 函数式选项:WithXxx()
├── storage.go               # Storage 接口(公共类型)
├── model.go                 # 请求/响应类型(公共类型)
├── errors.go                # 错误码(公共类型)
├── cors.go                  # CORS 中间件
├── internal/
│   ├── types/               # 共享类型(打破循环依赖)
│   │   ├── storage.go       # Storage 接口定义
│   │   ├── errors.go        # 错误类型 + 错误码
│   │   ├── model.go         # 数据传输对象
│   │   └── sanitize.go      # 目录穿越防护
│   ├── hash/                # 流式 MD5/SHA256 计算
│   ├── response/            # 统一 JSON 响应
│   ├── chunk/
│   │   ├── store.go         # ChunkStore 接口
│   │   └── filestore.go     # 文件系统实现(sync.Map 缓存 + 原子写入)
│   ├── store/
│   │   ├── local.go         # 本地磁盘存储
│   │   └── s3.go            # S3 兼容存储
│   ├── service/
│   │   ├── upload.go        # 普通上传业务逻辑
│   │   └── chunk_upload.go  # 分片上传业务逻辑
│   └── handler/
│       ├── upload.go        # 普通上传 Gin 处理器
│       └── chunk_upload.go  # 分片上传 Gin 处理器

设计决策

内存安全
  • 所有 Handler 使用 multipart.NewReader() 而非 c.FormFile(),避免 Gin 默认的 32MB 内存缓冲
  • io.TeeReader 在流式传输的同时计算哈希——无需二次读取
  • io.LimitReader 在 I/O 层面强制文件/分片大小限制
  • io.MultiReader 合并分片时不将其加载到内存
  • 表单字段读取限制为 1KB,防止内存耗尽攻击
并发安全
  • 每个 Sessionsync.Mutex 保护元数据更新(亚毫秒级持锁)
  • sync.Map 缓存活跃会话,避免每次分片上传都读磁盘
  • 分片数据文件按索引命名——不同分片的并行写入互不冲突
  • 唯一临时文件名(通过 atomic.Int64)防止同一分片的覆盖竞态
  • 分片上传幂等——重复上传已存在的分片直接返回成功
可靠性
  • 所有文件写入使用 写临时文件 + fsync + rename 实现原子性
  • 合并操作幂等——失败重试返回相同结果
  • 合并失败时保留会话,分片不会丢失
  • 过期会话由后台协程自动清理

优雅关闭

使用完毕后必须调用 Close() 以停止后台清理协程:

uploader, _ := gouploader.New(...)
defer uploader.Close()

许可证

MIT

Documentation

Overview

Package gouploader 提供生产级文件上传工具包,适用于 Gin 框架。

功能特性:

  • 普通单文件上传(流式传输,内存恒定)
  • 分片上传,支持断点续传
  • 可插拔存储:本地磁盘、S3 兼容(MinIO/阿里云/腾讯云/华为云/AWS)
  • 文件完整性校验(MD5/SHA256)
  • 并发安全的分片状态管理
  • 可配置限制与回调

快速开始:

uploader, err := gouploader.New(
    gouploader.WithLocalStorage("./uploads"),
    gouploader.WithMaxFileSize(2 << 30), // 2GB
    gouploader.WithOnUploadComplete(func(r *gouploader.UploadResult) {
        log.Printf("上传完成: %s -> %s", r.FileName, r.StorageKey)
    }),
)
uploader.RegisterRoutes(r.Group("/api/v1/upload"))
Example (Basic)

Example_basic demonstrates the simplest usage with local storage.

package main

import (
	"fmt"
	"log"

	"github.com/gin-gonic/gin"
	"github.com/gtkit/gouploader"
)

func main() {
	r := gin.Default()

	uploader, err := gouploader.New(
		gouploader.WithLocalStorage("./uploads"),
	)
	if err != nil {
		log.Fatal(err)
	}

	uploader.RegisterRoutes(r.Group("/api/v1/upload"))
	defer uploader.Close()
	// r.Run(":8080")
	fmt.Println("routes registered")
}
Output:
routes registered
Example (CustomRoutes)

Example_customRoutes demonstrates registering routes individually with custom middleware.

package main

import (
	"fmt"
	"log"

	"github.com/gin-gonic/gin"
	"github.com/gtkit/gouploader"
)

func main() {
	r := gin.Default()

	uploader, err := gouploader.New(
		gouploader.WithLocalStorage("./uploads"),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Register routes individually with custom middleware.
	auth := func(c *gin.Context) { c.Next() } // your auth middleware

	v1 := r.Group("/api/v1", auth)
	{
		v1.POST("/upload", uploader.HandleUpload())
		v1.POST("/upload/chunk/init", uploader.HandleChunkInit())
		v1.POST("/upload/chunk", uploader.HandleChunkUpload())
		v1.GET("/upload/chunk/status/:upload_id", uploader.HandleChunkStatus())
		v1.POST("/upload/chunk/merge/:upload_id", uploader.HandleChunkMerge())
		v1.DELETE("/upload/chunk/:upload_id", uploader.HandleChunkAbort())
	}

	fmt.Println("custom routes registered")
}
Output:
custom routes registered
Example (S3)

Example_s3 demonstrates using S3-compatible storage (MinIO/阿里云/腾讯云/华为云).

package main

import (
	"fmt"
	"log"

	"github.com/gin-gonic/gin"
	"github.com/gtkit/gouploader"
)

func main() {
	r := gin.Default()

	uploader, err := gouploader.New(
		gouploader.WithS3Storage(gouploader.S3Config{
			// MinIO:
			//   Endpoint: "127.0.0.1:9000",
			// 阿里云 OSS:
			//   Endpoint: "oss-cn-hangzhou.aliyuncs.com",
			// 腾讯云 COS:
			//   Endpoint: "cos.ap-guangzhou.myqcloud.com",
			// 华为云 OBS:
			//   Endpoint: "obs.cn-north-4.myhuaweicloud.com",
			Endpoint:  "127.0.0.1:9000",
			AccessKey: "minioadmin",
			SecretKey: "minioadmin",
			Bucket:    "uploads",
			Region:    "us-east-1",
			UseSSL:    false,
		}),
		gouploader.WithMaxFileSize(10<<30), // 10GB
		gouploader.WithChunkSize(
			10<<20, // default: 10MB
			1<<20,  // min: 1MB
			50<<20, // max: 50MB
		),
	)
	if err != nil {
		log.Fatal(err)
	}

	uploader.RegisterRoutes(r.Group("/api/v1/upload"))
	fmt.Println("s3 routes registered")
}
Example (WithCallback)

Example_withCallback demonstrates how to handle post-upload business logic.

package main

import (
	"fmt"
	"log"

	"github.com/gin-gonic/gin"
	"github.com/gtkit/gouploader"
)

func main() {
	r := gin.Default()

	uploader, err := gouploader.New(
		gouploader.WithLocalStorage("./uploads"),
		gouploader.WithMaxFileSize(2<<30), // 2GB
		gouploader.WithAllowedTypes(".jpg", ".png", ".mp4", ".pdf"),
		gouploader.WithHashAlgorithm("sha256"),
		gouploader.WithOnUploadComplete(func(result *gouploader.UploadResult) {
			// This callback fires after every successful upload.
			// Use it to save metadata to your database, send notifications, etc.
			log.Printf("file uploaded: %s -> %s (checksum: %s)",
				result.FileName, result.StorageKey, result.Checksum)
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	uploader.RegisterRoutes(r.Group("/api/v1/upload"))
	defer uploader.Close()
	// r.Run(":8080")
	fmt.Println("routes registered")
}
Output:
routes registered

Index

Examples

Constants

View Source
const (
	CodeFileTooLarge     = types.CodeFileTooLarge     // 文件超过大小限制
	CodeFileTypeNotAllow = types.CodeFileTypeNotAllow // 文件类型不允许
	CodeChecksumMismatch = types.CodeChecksumMismatch // 校验和不匹配
	CodeFileReadError    = types.CodeFileReadError    // 文件读取错误
	CodeInvalidParam     = types.CodeInvalidParam     // 参数无效
	CodeMultipartError   = types.CodeMultipartError   // multipart 解析错误

	CodeSessionNotFound    = types.CodeSessionNotFound    // 上传会话不存在
	CodeSessionExpired     = types.CodeSessionExpired     // 上传会话已过期
	CodeChunkIndexOutRange = types.CodeChunkIndexOutRange // 分片索引越界
	CodeChunkDuplicate     = types.CodeChunkDuplicate     // 分片重复上传
	CodeChunkHashMismatch  = types.CodeChunkHashMismatch  // 分片校验和不匹配
	CodeMergeIncomplete    = types.CodeMergeIncomplete    // 分片未全部上传
	CodeMergeHashFailed    = types.CodeMergeHashFailed    // 合并后校验失败

	CodeStorageWriteFail  = types.CodeStorageWriteFail  // 存储写入失败
	CodeStorageReadFail   = types.CodeStorageReadFail   // 存储读取失败
	CodeStorageDeleteFail = types.CodeStorageDeleteFail // 存储删除失败

	CodeSTSNotConfigured   = types.CodeSTSNotConfigured   // STS 未配置
	CodeSTSIssueFailed     = types.CodeSTSIssueFailed     // STS 凭证签发失败
	CodeCallbackVerifyFail = types.CodeCallbackVerifyFail // 回调验签失败
	CodeDirectFileNotFound = types.CodeDirectFileNotFound // 直传文件不存在
)

错误码常量。

View Source
const Version = "v1.0.0"

Variables

View Source
var (
	ErrFileTooLarge     = types.ErrFileTooLarge
	ErrFileTypeNotAllow = types.ErrFileTypeNotAllow
	ErrChecksumMismatch = types.ErrChecksumMismatch
	ErrInvalidParam     = types.ErrInvalidParam
	ErrMultipartError   = types.ErrMultipartError

	ErrSessionNotFound    = types.ErrSessionNotFound
	ErrSessionExpired     = types.ErrSessionExpired
	ErrChunkIndexOutRange = types.ErrChunkIndexOutRange
	ErrChunkHashMismatch  = types.ErrChunkHashMismatch
	ErrMergeIncomplete    = types.ErrMergeIncomplete
	ErrMergeHashFailed    = types.ErrMergeHashFailed

	ErrStorageWrite  = types.ErrStorageWrite
	ErrStorageRead   = types.ErrStorageRead
	ErrStorageDelete = types.ErrStorageDelete

	ErrSTSNotConfigured   = types.ErrSTSNotConfigured
	ErrSTSIssueFailed     = types.ErrSTSIssueFailed
	ErrCallbackVerifyFail = types.ErrCallbackVerifyFail
	ErrDirectFileNotFound = types.ErrDirectFileNotFound
)

预定义错误。

Functions

func CORS

func CORS(cfg CORSConfig) gin.HandlerFunc

CORS 返回处理跨域请求的 Gin 中间件。

用法:

group := r.Group("/api/v1/upload")
group.Use(gouploader.CORS(gouploader.CORSConfig{
    AllowOrigins: []string{"https://example.com"},
}))
uploader.RegisterRoutes(group)

Types

type AWSSTSConfig

type AWSSTSConfig = sts.AWSConfig

AWSSTSConfig AWS S3 STS 配置。

type AliyunSTSConfig

type AliyunSTSConfig = sts.AliyunConfig

AliyunSTSConfig 阿里云 OSS STS 配置。

type CORSConfig

type CORSConfig struct {
	// AllowOrigins 允许访问的源列表。
	// 使用 "*" 允许所有源(不建议生产环境使用)。
	AllowOrigins []string

	// AllowHeaders 客户端可以发送的额外请求头。
	// 默认请求头(Content-Type, X-Request-ID)始终包含。
	AllowHeaders []string

	// ExposeHeaders 客户端可以读取的额外响应头。
	ExposeHeaders []string

	// AllowCredentials 是否允许携带凭据(Cookie 等)。
	AllowCredentials bool

	// MaxAge 预检请求结果的缓存时间(秒)。默认 86400(24小时)。
	MaxAge int
}

CORSConfig 跨域资源共享配置。

type ChunkInitRequest

type ChunkInitRequest = types.ChunkInitRequest

ChunkInitRequest 初始化分片上传的 JSON 请求体。

type ChunkInitResponse

type ChunkInitResponse = types.ChunkInitResponse

ChunkInitResponse 分片上传会话创建后的响应。

type ChunkStatusResponse

type ChunkStatusResponse = types.ChunkStatusResponse

ChunkStatusResponse 查询分片上传状态的响应。

type ChunkUploadResponse

type ChunkUploadResponse = types.ChunkUploadResponse

ChunkUploadResponse 单个分片上传后的响应。

type DirectCompleteRequest

type DirectCompleteRequest = types.DirectCompleteRequest

DirectCompleteRequest 前端确认上传完成的请求体。

type DirectTokenRequest

type DirectTokenRequest = types.DirectTokenRequest

DirectTokenRequest 请求 STS 凭证的请求体。

type DirectTokenResponse

type DirectTokenResponse = types.DirectTokenResponse

DirectTokenResponse STS 凭证响应。

type DirectUploadResult

type DirectUploadResult = types.DirectUploadResult

DirectUploadResult 前端直传完成后的结果。

type Error

type Error = types.Error

Error 统一错误类型。

type HuaweiSTSConfig

type HuaweiSTSConfig = sts.HuaweiConfig

HuaweiSTSConfig 华为云 OBS STS 配置。

type Option

type Option func(*config)

Option 用于配置 Uploader。

func WithAWSSTS

func WithAWSSTS(cfg AWSSTSConfig) Option

WithAWSSTS 配置 AWS S3 STS 直传。

func WithAliyunSTS

func WithAliyunSTS(cfg AliyunSTSConfig) Option

WithAliyunSTS 配置阿里云 OSS STS 直传。

func WithAllowedTypes

func WithAllowedTypes(types ...string) Option

WithAllowedTypes 设置允许的文件扩展名(如 ".jpg", ".png", ".mp4")。 空列表表示允许所有类型。

func WithChunkCleanupInterval

func WithChunkCleanupInterval(interval time.Duration) Option

WithChunkCleanupInterval 设置过期会话的清理间隔。 interval 必须为正数,否则 panic。

func WithChunkSessionTTL

func WithChunkSessionTTL(ttl time.Duration) Option

WithChunkSessionTTL 设置分片上传会话的过期时间。

func WithChunkSize

func WithChunkSize(defaultSize, minSize, maxSize int64) Option

WithChunkSize 设置默认、最小和最大分片大小。

func WithChunkTempDir

func WithChunkTempDir(dir string) Option

WithChunkTempDir 设置分片数据的临时目录。

func WithHashAlgorithm

func WithHashAlgorithm(algo string) Option

WithHashAlgorithm 设置校验和算法("md5" 或 "sha256")。

func WithHuaweiSTS

func WithHuaweiSTS(cfg HuaweiSTSConfig) Option

WithHuaweiSTS 配置华为云 OBS STS 直传。

func WithLocalStorage

func WithLocalStorage(basePath string) Option

WithLocalStorage 配置本地磁盘存储。

func WithMaxFileSize

func WithMaxFileSize(size int64) Option

WithMaxFileSize 设置允许的最大文件大小(字节)。

func WithOnDirectUploadComplete

func WithOnDirectUploadComplete(fn func(result *DirectUploadResult)) Option

WithOnDirectUploadComplete 注册直传完成后的回调。 在 Complete 或 Callback 确认文件上传成功后触发。

func WithOnUploadComplete

func WithOnUploadComplete(fn func(result *UploadResult)) Option

WithOnUploadComplete 注册上传完成后的回调函数。 每次上传成功后触发(包括普通上传和分片合并)。 可用于保存数据库、发送通知等业务逻辑。

func WithS3Storage

func WithS3Storage(s3 S3Config) Option

WithS3Storage 配置 S3 兼容对象存储。

func WithSTSKeyPrefix

func WithSTSKeyPrefix(fn func(c *gin.Context) string) Option

WithSTSKeyPrefix 设置生成 keyPrefix 的函数。 默认按日期生成,如 "direct/2026/04/09/"。 可用于按用户 ID 隔离:func(c *gin.Context) string { return "user-" + getUserID(c) + "/" }

func WithSTSProvider

func WithSTSProvider(p STSProvider) Option

WithSTSProvider 设置自定义 STS Provider 实现。

func WithStorage

func WithStorage(s Storage) Option

WithStorage 设置自定义 Storage 实现。 会覆盖 WithLocalStorage 和 WithS3Storage。

func WithTencentSTS

func WithTencentSTS(cfg TencentSTSConfig) Option

WithTencentSTS 配置腾讯云 COS STS 直传。

type S3Config

type S3Config struct {
	Endpoint  string
	AccessKey string
	SecretKey string
	Bucket    string
	Region    string
	UseSSL    bool
}

S3Config 包含 S3 兼容对象存储的连接参数。

Endpoint 示例:

MinIO:      "127.0.0.1:9000"
阿里云 OSS:  "oss-cn-hangzhou.aliyuncs.com"
腾讯云 COS:  "cos.ap-guangzhou.myqcloud.com"
华为云 OBS:  "obs.cn-north-4.myhuaweicloud.com"
AWS S3:     "s3.us-east-1.amazonaws.com"

type STSCredential

type STSCredential = types.STSCredential

STSCredential STS 临时凭证。

type STSProvider

type STSProvider = types.STSProvider

STSProvider STS 凭证签发接口。

type Storage

type Storage = types.Storage

Storage 抽象文件持久化层。 所有方法均使用 io.Reader 流式传输,不会将完整文件缓冲到内存。

内置实现:本地磁盘、S3 兼容(MinIO/阿里云/腾讯云/华为云/AWS)。 也可通过 WithStorage 选项提供自定义实现。

type TencentSTSConfig

type TencentSTSConfig = sts.TencentConfig

TencentSTSConfig 腾讯云 COS STS 配置。

type UploadResult

type UploadResult = types.UploadResult

UploadResult 文件上传完成后返回的结果(普通上传和分片合并)。

type Uploader

type Uploader struct {
	// contains filtered or unexported fields
}

Uploader 是 gouploader 包的主入口。

func New

func New(opts ...Option) (*Uploader, error)

New 使用给定的选项创建 Uploader 实例。

func (*Uploader) Close

func (u *Uploader) Close()

Close 释放 Uploader 持有的资源(停止后台清理协程)。 在 Uploader 不再使用或应用关闭时必须调用。

func (*Uploader) HandleChunkAbort

func (u *Uploader) HandleChunkAbort() gin.HandlerFunc

HandleChunkAbort 返回中止分片上传的 Gin 处理函数。

func (*Uploader) HandleChunkInit

func (u *Uploader) HandleChunkInit() gin.HandlerFunc

HandleChunkInit 返回分片上传初始化的 Gin 处理函数。

func (*Uploader) HandleChunkMerge

func (u *Uploader) HandleChunkMerge() gin.HandlerFunc

HandleChunkMerge 返回合并分片的 Gin 处理函数。

func (*Uploader) HandleChunkStatus

func (u *Uploader) HandleChunkStatus() gin.HandlerFunc

HandleChunkStatus 返回查询分片上传状态的 Gin 处理函数。

func (*Uploader) HandleChunkUpload

func (u *Uploader) HandleChunkUpload() gin.HandlerFunc

HandleChunkUpload 返回分片上传的 Gin 处理函数。

func (*Uploader) HandleDirectCallback

func (u *Uploader) HandleDirectCallback() gin.HandlerFunc

HandleDirectCallback 返回云存储回调的 Gin 处理函数。 如果未配置 STS 直传,返回的处理函数会响应 503 错误。

func (*Uploader) HandleDirectComplete

func (u *Uploader) HandleDirectComplete() gin.HandlerFunc

HandleDirectComplete 返回前端确认上传完成的 Gin 处理函数。 如果未配置 STS 直传,返回的处理函数会响应 503 错误。

func (*Uploader) HandleDirectToken

func (u *Uploader) HandleDirectToken() gin.HandlerFunc

HandleDirectToken 返回签发 STS 凭证的 Gin 处理函数。 如果未配置 STS 直传,返回的处理函数会响应 503 错误。

func (*Uploader) HandleUpload

func (u *Uploader) HandleUpload() gin.HandlerFunc

HandleUpload 返回普通上传的 Gin 处理函数。 用于手动注册路由(搭配自定义中间件)。

func (*Uploader) RegisterRoutes

func (u *Uploader) RegisterRoutes(group *gin.RouterGroup)

RegisterRoutes 在给定的 Gin 路由组上注册所有上传接口。

注册的路由:

POST   {group}/                        - 普通单文件上传
POST   {group}/chunk/init              - 初始化分片上传会话
POST   {group}/chunk                   - 上传单个分片
GET    {group}/chunk/status/:upload_id - 查询上传进度(断点续传)
POST   {group}/chunk/merge/:upload_id  - 合并所有分片
DELETE {group}/chunk/:upload_id        - 中止并清理分片上传

如果配置了 STS 直传,还会注册:

POST   {group}/direct/token    - 签发 STS 临时凭证
POST   {group}/direct/callback - 云存储上传完成回调
POST   {group}/direct/complete - 前端确认上传完成

Directories

Path Synopsis
aliyun module
aws module
huawei module
internal
sts
minio module
tencent module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL