Golang中无法表示文件数据类型的问题

Golang中无法表示文件数据类型的问题 大家好,我正在尝试创建一个音乐商店API,用户基本上可以在这里收听和购买歌曲。我遇到的问题是如何在GORM(Golang ORM库)中表示艺术作品(图片文件)和歌曲(音乐文件),以及存储后应以何种格式将其返回给客户端?是否有可以简化操作的外部包可用?谢谢。

5 回复

我会尝试一下。谢谢

更多关于Golang中无法表示文件数据类型的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


非常感谢您的回复, 能否请您进一步解释一下第二个选项?

两者都是二进制数据,基本上你有两个选择:

  1. 将它们作为二进制对象存储在数据库中。
  2. 在数据库中存储对象的引用,并将对象存储在磁盘或像 S3 这样的外部存储中。

我不确定 Gorm 是否支持第一种方式。

假设你所说的 API 是指 HTTP,你应该使用正确的 MIME 类型(如 image/jpegaudio/mpeg)来返回这些对象。

如果在数据库中存储对某个对象的引用,URL将是最具可移植性的方式。URL可以是类似以下的形式:

  • file:///usr/share/objects/123.jpg 用于引用本地文件系统中的文件
  • https://www.example.com/objects/123.jpg 用于引用HTTP服务器上的文件

这样,数据库中只需存储这个URL。

为了响应客户端对某个对象的请求(例如一个HTTP请求),你的应用程序将使用Gorm或其他SQL库从数据库中获取URL。然后,它会从该URL获取对象的内容,这将得到一个 []byte。最后,它会将这个字节数组作为HTTP响应返回给客户端,并设置适当的HTTP头,例如 Content-Type: image/jepg

在Golang中处理文件数据时,通常不直接将文件内容存储在数据库中,而是存储文件的元数据和路径。以下是针对你的音乐商店API的解决方案:

1. 数据库模型设计

package models

import (
    "time"
    "gorm.io/gorm"
)

type Song struct {
    ID          uint      `gorm:"primaryKey"`
    Title       string    `gorm:"not null"`
    Artist      string
    Album       string
    Duration    int       // 以秒为单位
    Price       float64
    FilePath    string    `gorm:"not null"` // 音频文件存储路径
    ArtworkPath string    // 封面图片路径
    FileSize    int64     // 文件大小(字节)
    MimeType    string    // 文件类型
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

type Artwork struct {
    ID        uint      `gorm:"primaryKey"`
    SongID    uint      `gorm:"not null"`
    FilePath  string    `gorm:"not null"`
    FileSize  int64
    MimeType  string
    CreatedAt time.Time
}

2. 文件上传处理

package handlers

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "path/filepath"
    "strconv"
    
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
    "your-app/models"
)

// 上传歌曲文件
func UploadSong(c *gin.Context) {
    db := c.MustGet("db").(*gorm.DB)
    
    // 解析表单数据
    file, header, err := c.Request.FormFile("song_file")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无法获取文件"})
        return
    }
    defer file.Close()
    
    // 创建存储目录
    uploadDir := "./uploads/songs"
    if err := os.MkdirAll(uploadDir, 0755); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "创建目录失败"})
        return
    }
    
    // 生成唯一文件名
    filename := fmt.Sprintf("%d_%s", time.Now().UnixNano(), header.Filename)
    filepath := filepath.Join(uploadDir, filename)
    
    // 保存文件
    out, err := os.Create(filepath)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"})
        return
    }
    defer out.Close()
    
    _, err = io.Copy(out, file)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "复制文件失败"})
        return
    }
    
    // 创建数据库记录
    song := models.Song{
        Title:     c.PostForm("title"),
        Artist:    c.PostForm("artist"),
        FilePath:  filepath,
        FileSize:  header.Size,
        MimeType:  header.Header.Get("Content-Type"),
    }
    
    if err := db.Create(&song).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "保存记录失败"})
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "message": "文件上传成功",
        "song_id": song.ID,
        "file_url": fmt.Sprintf("/api/songs/%d/file", song.ID),
    })
}

3. 文件服务端点

// 提供歌曲文件流
func ServeSongFile(c *gin.Context) {
    db := c.MustGet("db").(*gorm.DB)
    
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
        return
    }
    
    var song models.Song
    if err := db.First(&song, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "歌曲未找到"})
        return
    }
    
    // 设置正确的Content-Type
    c.Header("Content-Type", song.MimeType)
    c.Header("Content-Length", strconv.FormatInt(song.FileSize, 10))
    
    // 支持断点续传
    c.File(song.FilePath)
}

// 获取歌曲信息(包含可访问的URL)
func GetSong(c *gin.Context) {
    db := c.MustGet("db").(*gorm.DB)
    
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
        return
    }
    
    var song models.Song
    if err := db.First(&song, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "歌曲未找到"})
        return
    }
    
    // 返回包含访问URL的响应
    c.JSON(http.StatusOK, gin.H{
        "id":         song.ID,
        "title":      song.Title,
        "artist":     song.Artist,
        "album":      song.Album,
        "duration":   song.Duration,
        "price":      song.Price,
        "file_url":   fmt.Sprintf("/api/songs/%d/file", song.ID),
        "artwork_url": fmt.Sprintf("/api/songs/%d/artwork", song.ID),
        "file_size":  song.FileSize,
        "mime_type":  song.MimeType,
        "created_at": song.CreatedAt,
    })
}

4. 推荐的第三方包

// 使用github.com/gin-gonic/gin处理HTTP请求
// 使用github.com/google/uuid生成唯一文件名
// 使用github.com/disintegration/imaging处理图片
// 使用cloud.google.com/go/storage或github.com/aws/aws-sdk-go用于云存储

// 示例:使用UUID生成文件名
import (
    "github.com/google/uuid"
)

func generateFilename(originalName string) string {
    ext := filepath.Ext(originalName)
    return uuid.New().String() + ext
}

// 示例:图片处理
import (
    "github.com/disintegration/imaging"
)

func ProcessArtwork(inputPath, outputPath string) error {
    img, err := imaging.Open(inputPath)
    if err != nil {
        return err
    }
    
    // 调整大小为300x300
    img = imaging.Resize(img, 300, 300, imaging.Lanczos)
    
    // 保存为JPEG
    return imaging.Save(img, outputPath, imaging.JPEGQuality(85))
}

5. 路由配置

package main

import (
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
    "your-app/handlers"
)

func main() {
    r := gin.Default()
    
    // 歌曲相关路由
    songGroup := r.Group("/api/songs")
    {
        songGroup.POST("/upload", handlers.UploadSong)
        songGroup.GET("/:id", handlers.GetSong)
        songGroup.GET("/:id/file", handlers.ServeSongFile)
        songGroup.GET("/:id/artwork", handlers.ServeArtwork)
    }
    
    r.Run(":8080")
}

这种设计将文件存储在文件系统或对象存储中,数据库只保存文件路径和元数据。返回给客户端时,提供可以访问文件的URL端点。对于生产环境,建议使用云存储服务(如AWS S3、Google Cloud Storage)并配置CDN加速文件访问。

回到顶部