文件上传下载 #
一、文件上传基础 #
1.1 单文件上传 #
go
app.Post("/upload", func(c *fiber.Ctx) error {
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "File is required",
})
}
// 文件信息
fmt.Println("Filename:", file.Filename)
fmt.Println("Size:", file.Size)
fmt.Println("Header:", file.Header)
fmt.Println("Content-Type:", file.Header.Get("Content-Type"))
// 保存文件
err = c.SaveFile(file, "./uploads/"+file.Filename)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to save file",
})
}
return c.JSON(fiber.Map{
"message": "File uploaded successfully",
"filename": file.Filename,
"size": file.Size,
})
})
1.2 多文件上传 #
go
app.Post("/uploads", func(c *fiber.Ctx) error {
// 解析多部分表单
form, err := c.MultipartForm()
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid multipart form",
})
}
// 获取所有文件
files := form.File["files"]
var results []fiber.Map
for _, file := range files {
// 保存文件
err := c.SaveFile(file, "./uploads/"+file.Filename)
if err != nil {
results = append(results, fiber.Map{
"filename": file.Filename,
"error": err.Error(),
})
continue
}
results = append(results, fiber.Map{
"filename": file.Filename,
"size": file.Size,
"status": "uploaded",
})
}
return c.JSON(fiber.Map{
"total": len(files),
"files": results,
})
})
1.3 带表单数据的文件上传 #
go
app.Post("/upload-with-data", func(c *fiber.Ctx) error {
// 获取表单字段
name := c.FormValue("name")
description := c.FormValue("description")
// 获取文件
file, err := c.FormFile("file")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "File is required",
})
}
// 保存文件
if err := c.SaveFile(file, "./uploads/"+file.Filename); err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to save file",
})
}
return c.JSON(fiber.Map{
"name": name,
"description": description,
"filename": file.Filename,
"size": file.Size,
})
})
二、文件验证 #
2.1 文件类型验证 #
go
func validateFileType(file *multipart.FileHeader) bool {
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/gif": true,
"application/pdf": true,
}
f, err := file.Open()
if err != nil {
return false
}
defer f.Close()
buffer := make([]byte, 512)
_, err = f.Read(buffer)
if err != nil {
return false
}
contentType := http.DetectContentType(buffer)
return allowedTypes[contentType]
}
app.Post("/upload-safe", func(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "File is required",
})
}
if !validateFileType(file) {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid file type",
})
}
// 保存文件...
return c.JSON(fiber.Map{"message": "OK"})
})
2.2 文件大小验证 #
go
const maxSize = 10 * 1024 * 1024 // 10MB
app.Post("/upload-size-check", func(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "File is required",
})
}
if file.Size > maxSize {
return c.Status(400).JSON(fiber.Map{
"error": "File size exceeds limit (10MB)",
})
}
// 保存文件...
return c.JSON(fiber.Map{"message": "OK"})
})
2.3 文件扩展名验证 #
go
func validateExtension(filename string) bool {
allowedExts := map[string]bool{
".jpg": true,
".jpeg": true,
".png": true,
".gif": true,
".pdf": true,
}
ext := strings.ToLower(filepath.Ext(filename))
return allowedExts[ext]
}
app.Post("/upload-ext-check", func(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "File is required",
})
}
if !validateExtension(file.Filename) {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid file extension",
})
}
// 保存文件...
return c.JSON(fiber.Map{"message": "OK"})
})
三、文件存储 #
3.1 按日期存储 #
go
func saveFileByDate(file *multipart.FileHeader) (string, error) {
// 创建日期目录
dateDir := time.Now().Format("2006/01/02")
uploadDir := filepath.Join("./uploads", dateDir)
if err := os.MkdirAll(uploadDir, 0755); err != nil {
return "", err
}
// 生成唯一文件名
ext := filepath.Ext(file.Filename)
filename := uuid.New().String() + ext
filepath := filepath.Join(uploadDir, filename)
// 保存文件
if err := c.SaveFile(file, filepath); err != nil {
return "", err
}
return filepath, nil
}
3.2 按用户存储 #
go
func saveFileByUser(c *fiber.Ctx, file *multipart.FileHeader, userID string) (string, error) {
uploadDir := filepath.Join("./uploads", "users", userID)
if err := os.MkdirAll(uploadDir, 0755); err != nil {
return "", err
}
ext := filepath.Ext(file.Filename)
filename := uuid.New().String() + ext
filepath := filepath.Join(uploadDir, filename)
if err := c.SaveFile(file, filepath); err != nil {
return "", err
}
return filepath, nil
}
3.3 云存储上传 #
go
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
func uploadToS3(file *multipart.FileHeader, bucket string) (string, error) {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("us-east-1"),
})
if err != nil {
return "", err
}
svc := s3.New(sess)
f, err := file.Open()
if err != nil {
return "", err
}
defer f.Close()
key := uuid.New().String() + filepath.Ext(file.Filename)
_, err = svc.PutObject(&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: f,
})
if err != nil {
return "", err
}
return key, nil
}
四、文件下载 #
4.1 基本下载 #
go
app.Get("/download/:filename", func(c *fiber.Ctx) error {
filename := c.Params("filename")
filepath := "./uploads/" + filename
return c.Download(filepath)
})
4.2 指定下载文件名 #
go
app.Get("/download-custom/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
filepath := "./uploads/" + id
// 自定义下载文件名
return c.Download(filepath, "document.pdf")
})
4.3 发送文件 #
go
app.Get("/file/:filename", func(c *fiber.Ctx) error {
filename := c.Params("filename")
filepath := "./uploads/" + filename
return c.SendFile(filepath)
})
4.4 带权限验证的下载 #
go
app.Get("/download-protected/:id", authMiddleware, func(c *fiber.Ctx) error {
id := c.Params("id")
userID := c.Locals("user_id").(string)
// 检查文件所有权
file, err := getFileByID(id)
if err != nil {
return c.Status(404).JSON(fiber.Map{
"error": "File not found",
})
}
if file.OwnerID != userID {
return c.Status(403).JSON(fiber.Map{
"error": "Access denied",
})
}
return c.Download(file.Path, file.Name)
})
五、图片处理 #
5.1 图片缩略图 #
go
import (
"image"
"image/jpeg"
"github.com/disintegration/imaging"
)
func createThumbnail(src string, dst string, width int) error {
img, err := imaging.Open(src)
if err != nil {
return err
}
thumb := imaging.Resize(img, width, 0, imaging.Lanczos)
return imaging.Save(thumb, dst)
}
app.Post("/upload-image", func(c *fiber.Ctx) error {
file, err := c.FormFile("image")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Image is required",
})
}
// 保存原图
originalPath := "./uploads/original/" + file.Filename
c.SaveFile(file, originalPath)
// 创建缩略图
thumbPath := "./uploads/thumbnails/" + file.Filename
createThumbnail(originalPath, thumbPath, 200)
return c.JSON(fiber.Map{
"original": originalPath,
"thumbnail": thumbPath,
})
})
5.2 图片格式转换 #
go
func convertToPNG(src string, dst string) error {
img, err := imaging.Open(src)
if err != nil {
return err
}
return imaging.Save(img, dst)
}
六、完整示例 #
6.1 文件上传服务 #
go
package main
import (
"os"
"path/filepath"
"time"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)
const (
maxSize = 10 * 1024 * 1024
uploadDir = "./uploads"
allowedExts = ".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx"
)
func init() {
os.MkdirAll(uploadDir, 0755)
}
func main() {
app := fiber.New()
// 上传文件
app.Post("/upload", uploadFile)
// 下载文件
app.Get("/download/:filename", downloadFile)
// 列出文件
app.Get("/files", listFiles)
// 删除文件
app.Delete("/files/:filename", deleteFile)
app.Listen(":3000")
}
func uploadFile(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "File is required",
})
}
// 验证文件大小
if file.Size > maxSize {
return c.Status(400).JSON(fiber.Map{
"error": "File size exceeds limit",
})
}
// 验证扩展名
ext := strings.ToLower(filepath.Ext(file.Filename))
if !strings.Contains(allowedExts, ext) {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid file type",
})
}
// 生成唯一文件名
filename := uuid.New().String() + ext
filepath := filepath.Join(uploadDir, filename)
// 保存文件
if err := c.SaveFile(file, filepath); err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to save file",
})
}
return c.JSON(fiber.Map{
"message": "File uploaded successfully",
"filename": filename,
"size": file.Size,
})
}
func downloadFile(c *fiber.Ctx) error {
filename := c.Params("filename")
filepath := filepath.Join(uploadDir, filename)
if _, err := os.Stat(filepath); os.IsNotExist(err) {
return c.Status(404).JSON(fiber.Map{
"error": "File not found",
})
}
return c.Download(filepath)
}
func listFiles(c *fiber.Ctx) error {
files, err := os.ReadDir(uploadDir)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to read directory",
})
}
var result []fiber.Map
for _, file := range files {
info, _ := file.Info()
result = append(result, fiber.Map{
"name": file.Name(),
"size": info.Size(),
"modified": info.ModTime().Format(time.RFC3339),
"is_dir": file.IsDir(),
})
}
return c.JSON(result)
}
func deleteFile(c *fiber.Ctx) error {
filename := c.Params("filename")
filepath := filepath.Join(uploadDir, filename)
if err := os.Remove(filepath); err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to delete file",
})
}
return c.JSON(fiber.Map{
"message": "File deleted successfully",
})
}
七、总结 #
7.1 文件操作方法 #
| 方法 | 用途 |
|---|---|
| c.FormFile() | 获取上传文件 |
| c.SaveFile() | 保存文件 |
| c.Download() | 文件下载 |
| c.SendFile() | 发送文件 |
7.2 下一步 #
现在你已经掌握了文件上传下载,接下来让我们学习 数据绑定,了解数据验证!
最后更新:2026-03-28