使用 GORM(Go 的 ORM 库)实现文件上传到本地 | 青训营

266 阅读4分钟

router层

package router

import (
	"homepage/controller"
	"homepage/middleware"

	"github.com/gin-gonic/gin"
)

func InitRouter(r *gin.Engine) {
    file := r.Group("api/file")
    {
        fileAndAudioController := controller.FileAudio{}
        file.POST("/upload", fileAndAudioController.HandleUpload)
    }
}

用于给前端提供相应的接口

controller层

package controller

import (
	"homepage/service"
	"net/http"

	"github.com/gin-gonic/gin"
)

type FileAudio struct{}

func (f FileAudio) HandleUpload(c *gin.Context) {
	fileAudioService := service.FileAudioService{}
	resp, err := fileAudioService.HandleFileUpload(c)
	if err != nil {
		c.Error(&gin.Error{
			Err:  err,
			Type: service.SysErr,
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    resp,
	})
}

这段代码是一个处理音频或者文件上传的函数 HandleUpload,它使用了 gin 框架。以下是对代码的详细解释:

  1. func (f FileAudio) HandleUpload(c *gin.Context):这是一个方法,其接收者是类型 FileAudio。这意味着 HandleUploadFileAudio 的一个方法。
  2. fileAudioService := service.FileAudioService{}:这里创建了一个 service.FileAudioService 的实例 fileAudioService。这个实例用于处理音频文件的上传操作。
  3. resp, err := fileAudioService.HandleFileUpload(c):调用 fileAudioServiceHandleFileUpload 方法来处理上传的文件。cgin.Context 对象,用于接收上传的文件。
  4. if err != nil { ... }:如果在文件上传处理过程中出现错误,会进入这个条件分支。
  5. c.Error(&gin.Error{ ... }):使用 gin.ContextError 方法将错误信息包装成 gin.Error 对象,并返回给客户端。
  6. c.JSON(http.StatusOK, gin.H{ ... }):在没有错误的情况下,使用 c.JSON 方法返回一个 JSON 响应给客户端。http.StatusOK 表示 HTTP 状态码 200(请求成功),gin.H{ ... } 是一个 gin.H 类型的 Map,用于构建响应的键值对。

总结:这个函数的作用是在接收到客户端上传的音频文件后,调用 fileAudioService 对象处理上传的文件,并根据处理结果返回相应的 JSON 响应给客户端。如果在文件上传处理过程中出现错误,将返回错误信息给客户端。

service层

package service

import (
	"fmt"
	"homepage/model"
	"mime/multipart"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

type FileAudioService struct {
	// 定义你的 FileAudioService 结构体
}

func (f FileAudioService) HandleFileUpload(c *gin.Context) (*model.FileUpload, error) {
	var request struct {
		File    *multipart.FileHeader `form:"file" binding:"required"`
		Title   string                `form:"title"`
		Content string                `form:"content"`
		Type    int                   `form:"type" binding:"required"`
	}
	if err := c.ShouldBind(&request); err != nil {
		return nil, err
	}

	// 生成保存文件的路径和文件名
	uuidFilename := generateUUIDFilename(request.File.Filename)
	var destinationPath string
	var destinationPathForOthers string = "./file/others"
	var destinationPathForAudio string = "./file/audio"
	if err := createFolderIfNotExists(destinationPathForOthers); err != nil {
		return nil, err
	}
	if err := createFolderIfNotExists(destinationPathForAudio); err != nil {
		return nil, err
	}
	if request.Type == 1 {
		// 音频文件
		destinationPath = filepath.Join(destinationPathForAudio, uuidFilename)
		fmt.Println(destinationPath)
	} else {
		destinationPath = filepath.Join(destinationPathForOthers, uuidFilename)
		fmt.Println(destinationPath)
	}
	// 保存上传文件到本地
	if err := c.SaveUploadedFile(request.File, destinationPath); err != nil {
		return nil, err
	}

	fmt.Println("File saved to:", destinationPath)

	// 提取文件后缀作为 type 字段的一部分
	fileExtension := getFileExtension(request.File.Filename)

	// 构建保存到数据库的数据
	now := time.Now()
	fileUpload := model.FileUpload{
		Title:    request.Title,
		Content:  request.Content,
		Url:      destinationPath,
		Type:     fileExtension,
		Sort:     0,
		Status:   1,
		CreateAt: int(now.Unix()),
	}

	// TODO: 在此处执行数据库保存操作
	// yourDB.Save(&fileUpload)
	if err := model.DB.Create(&fileUpload).Error; err != nil {
		return nil, err
	}
	return &fileUpload, nil
}

func generateUUIDFilename(originalFilename string) string {
	extension := getFileExtension(originalFilename)
	uuidString := uuid.New().String()
	return uuidString + "." + extension
}

func getFileExtension(filename string) string {
	return strings.TrimPrefix(filepath.Ext(filename), ".")
}

func createFolderIfNotExists(folderPath string) error {
	// 检查文件夹是否存在
	if _, err := os.Stat(folderPath); os.IsNotExist(err) {
		// 文件夹不存在,创建文件夹
		err := os.MkdirAll(folderPath, 0755)
		if err != nil {
			return fmt.Errorf("无法创建文件夹: %v", err)
		}
	}
	return nil
}

这段代码是对文件上传功能的具体逻辑实现

这段代码定义了一个 FileAudioService 结构体和其关联的方法 HandleFileUpload 以及一些辅助函数。

  • type FileAudioService struct { ... }:定义了一个 FileAudioService 结构体,该结构体可以用于封装与音频文件相关的操作和功能。
  • func (f FileAudioService) HandleFileUpload(c *gin.Context) (*model.FileUpload, error) { ... }:这是一个方法,接收者是 FileAudioService 结构体。该方法用于处理文件上传请求。具体流程如下:
    • 创建了一个空结构体 request 来接收包含 form 表单字段的请求数据。
    • 使用 c.ShouldBind(&request) 方法将请求数据绑定到 request 结构体中。
    • 根据请求的 Type 字段判断文件类型,生成保存文件的路径和文件名。
    • 调用 createFolderIfNotExists 函数来检查并创建文件夹。
    • 使用 c.SaveUploadedFile 方法保存上传的文件到本地指定的路径。
    • 根据文件名获取文件扩展名,并构建保存到数据库中的文件信息。
    • 在数据库中保存文件信息。
    • 返回保存成功的文件信息。
  • func generateUUIDFilename(originalFilename string) string { ... }:这个函数用于生成一个以 UUID 为文件名的唯一文件名,文件名包含原始文件名的扩展名。
  • func getFileExtension(filename string) string { ... }:此函数用于从文件名中提取文件的扩展名。
  • func createFolderIfNotExists(folderPath string) error { ... }:该函数用于检查文件夹是否存在,如果不存在则创建文件夹。

总结:这段代码定义了一个 FileAudioService 结构体及其关联方法,用于处理音频文件上传请求。它将上传的文件保存到本地指定的路径并将文件信息保存到数据库。另外,它还包含了一些辅助函数,用于生成文件名和获取文件扩展名,以及检查并创建文件夹