Skip to content

第20章:静态文件与文件上传 ​

本章目标 ​

学完本章后,你将能够:

  1. 使用 r.Static() 提供静态文件服务
  2. 处理单文件和多文件上传
  3. 限制文件大小和类型
  4. 实现文件下载接口

前置知识 ​

  • 需要先学习:第06章(表单参数)、第07章(请求头)

20.1 静态文件服务 ​

go
// 提供整个目录(URL 路径映射到本地目录)
r.Static("/static", "./public")
// http://localhost:8080/static/css/style.css → ./public/css/style.css

// 提供单个文件
r.StaticFile("/favicon.ico", "./public/favicon.ico")

// 自定义文件系统
r.StaticFS("/assets", http.Dir("./assets"))

20.2 单文件上传 ​

go
r.POST("/upload", func(c *gin.Context) {
	// 1. 获取上传的文件
	file, err := c.FormFile("file")
	if err != nil {
		c.JSON(400, gin.H{"error": "请选择文件"})
		return
	}

	// 2. 保存到磁盘
	dst := "./uploads/" + file.Filename
	if err := c.SaveUploadedFile(file, dst); err != nil {
		c.JSON(500, gin.H{"error": "保存失败"})
		return
	}

	c.JSON(200, gin.H{
		"message":  "上传成功",
		"filename": file.Filename,
		"size":     file.Size,
	})
})
bash
curl -X POST http://localhost:8080/upload \
  -F "file=@/path/to/image.png"

20.3 多文件上传 ​

go
r.POST("/upload-multi", func(c *gin.Context) {
	form, err := c.MultipartForm()
	if err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}

	files := form.File["files"]  // "files" 是表单字段名
	var uploaded []string

	for _, file := range files {
		dst := "./uploads/" + file.Filename
		c.SaveUploadedFile(file, dst)
		uploaded = append(uploaded, file.Filename)
	}

	c.JSON(200, gin.H{
		"message": fmt.Sprintf("成功上传 %d 个文件", len(uploaded)),
		"files":   uploaded,
	})
})
bash
curl -X POST http://localhost:8080/upload-multi \
  -F "files=@img1.png" \
  -F "files=@img2.jpg" \
  -F "files=@doc.pdf"

20.4 文件大小限制 ​

go
// 全局限制:最大 8MB
r.MaxMultipartMemory = 8 << 20  // 8MB

// 或者用中间件限制请求体大小
r.Use(func(c *gin.Context) {
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10<<20)
	c.Next()
})

20.5 文件类型验证 ​

go
func allowedFileType(file *multipart.FileHeader) bool {
	allowed := map[string]bool{
		"image/jpeg": true,
		"image/png":  true,
		"image/gif":  true,
		"application/pdf": true,
	}
	contentType := file.Header.Get("Content-Type")
	return allowed[contentType]
}

20.6 文件下载 ​

go
// 方式一:直接返回文件
c.File("./files/report.pdf")

// 方式二:带下载文件名
c.FileAttachment("./files/report.pdf", "2024年度报告.pdf")

// 方式三:从内存返回
data := []byte("文件内容")
c.Data(http.StatusOK, "application/octet-stream", data)

常见错误 ​

错误1:忘记创建 uploads 目录 ​

go
// ❌ uploads 目录不存在 → 保存失败
c.SaveUploadedFile(file, "./uploads/"+file.Filename)

// ✅ 先确保目录存在
os.MkdirAll("./uploads", 0755)

错误2:静态文件路径和路由冲突 ​

go
// ❌ /static 既是静态文件又是路由
r.GET("/static", handler)
r.Static("/static", "./public")  // 冲突!

错误3:大文件上传默认存内存 ​

超过 MaxMultipartMemory 的文件会存到临时文件,但小于它的全放内存——如果同时很多人上传 5MB 文件,内存可能爆。


本章小结 ​

  • r.Static("/url", "./local") 提供静态文件
  • c.FormFile("field") + c.SaveUploadedFile() 处理上传
  • c.MultipartForm() 批量处理多文件
  • 文件大小用 MaxMultipartMemory 或 MaxBytesReader 限制
  • c.File() / c.FileAttachment() 实现下载

练习题 ​

  1. 配置静态文件服务,将 ./public 目录映射到 /static 路径。
  2. 实现单文件上传接口,保存到 ./uploads/ 目录。
  3. 实现多文件上传接口,限制最多上传 5 个文件。
  4. 实现文件下载接口,根据 URL 参数返回不同的文件。
  5. (思考题)大文件(>100MB)上传时直接存内存会有什么问题?如何优化?

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