文件上传下载 #

一、文件上传基础 #

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