Skip to content

第22章:文件服务与附件处理 ​

本章目标 ​

学完本章后,你将能够:

  1. 实现文件下载(带文件名)
  2. 理解断点续传的原理
  3. 了解大文件分片上传的思路
  4. 区分文件预览和下载的场景

前置知识 ​

  • 需要先学习:第20章(文件上传)

22.1 文件下载 ​

go
// 直接下载
r.GET("/download/:filename", func(c *gin.Context) {
	filename := c.Param("filename")
	filepath := "./uploads/" + filename

	// 检查文件是否存在
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		c.JSON(404, gin.H{"error": "文件不存在"})
		return
	}

	// 方式一:自动识别 Content-Type
	c.File(filepath)

	// 方式二:指定下载文件名
	// c.FileAttachment(filepath, filename)
})

22.2 区分预览和下载 ​

go
r.GET("/file/:filename", func(c *gin.Context) {
	filename := c.Param("filename")
	action := c.DefaultQuery("action", "inline")  // inline=预览, download=下载

	if action == "download" {
		c.Header("Content-Disposition", "attachment; filename="+filename)
	} else {
		c.Header("Content-Disposition", "inline")
	}
	c.File("./uploads/" + filename)
})

22.3 断点续传思路 ​

go
r.GET("/video/:filename", func(c *gin.Context) {
	filepath := "./uploads/" + c.Param("filename")
	
	// 获取 Range 请求头
	rangeHeader := c.GetHeader("Range")
	// 解析 Range,只返回请求的字节范围
	// 实际项目中用 http.ServeFile 或专门的流媒体服务器
	
	http.ServeFile(c.Writer, c.Request, filepath)
})

22.4 大文件分片上传思路 ​

分片上传的核心思路:前端把大文件切成小块,逐块上传,服务端记录每块的状态,最后合并:

go
// 1. 初始化分片上传
r.POST("/upload/init", func(c *gin.Context) {
	// 生成 uploadID,返回给客户端
})

// 2. 上传每个分片
r.POST("/upload/chunk", func(c *gin.Context) {
	// uploadID + chunkIndex + chunkData
	// 保存为:uploads/{uploadID}/chunk_{index}
})

// 3. 合并分片
r.POST("/upload/merge", func(c *gin.Context) {
	// 将所有分片合并为完整文件
	// 校验 MD5,返回文件 URL
})

22.5 文件服务最佳实践 ​

场景方案
小文件(<10MB)本地磁盘 + Gin 静态服务
中等文件(<100MB)对象存储(OSS/S3)+ 签名 URL
大文件(>100MB)对象存储 + 分片上传
视频/音频CDN + 流媒体服务器

本章小结 ​

  • c.File() 文件下载/预览
  • Content-Disposition: attachment 强制下载
  • 断点续传需要解析 Range 请求头
  • 大文件用分片上传 + 合并策略
  • 生产环境文件推荐存对象存储(OSS/S3)

练习题 ​

  1. 实现文件下载接口,支持指定文件名。
  2. 实现一个根据 action=download 参数控制预览/下载的接口。
  3. 描述大文件分片上传的流程(不需要写完整代码)。
  4. (思考题)为什么大多数 Web 应用不把用户上传的文件直接存在 Web 服务器的磁盘上?

把零散知识,整理成可以反复走的路。