Gin框架(3)

文件上传与下载

文件上传

单文件上传

单文件上传指的是一次只上传一个文件。在Gin中,可以使用c.SaveUploadedFile方法来保存单个上传的文件。

Go 复制代码
// SaveUploadedFile uploads the form file to specific dst.
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
   src, err := file.Open()
  if err != nil {
        return err
  }
  defer src.Close()
  if err=os.MkdirAll(filepath,Dir(dst),0750);err!=nil{
    return err
  }
  out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, src)
	return err
}
Go 复制代码
func singleFileUpload(c *gin.Context) {
// 从表单中获取上传的文件
file, err := c.FormFile("file")
if err != nil {
// 处理错误,可能是文件未找到或其他原因
c.JSON(http.StatusBadRequest, gin.H{"error": "File not found in form"})
return
}
  // 指定保存文件的路径,这里使用了文件的原始文件名
path := "./" + file.Filename

// 保存上传的文件到指定路径
if err := c.SaveUploadedFile(file, path); err != nil {
    // 处理错误
    c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    return
}

// 返回成功响应
c.JSON(http.StatusOK, gin.H{"message": "File uploaded successfully", "file_path": path})
}

多文件上传

Go 复制代码
// MultipartForm is the parsed multipart form, including file uploads.
//MultipartForm 方法用于解析和处理 multipart/form-data 类型的请求,这通常用于文件上传
func (c *Context) MultipartForm() (*multipart.Form, error) {
	err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
	return c.Request.MultipartForm, err
}

Gin框架处理多文件上传的实例

Go 复制代码
func uploadHandler(c *gin.Context){
  form,err:=c.MultipartForm()
  if err!=nil{
    c.JSON(http.StatusBadRequest,gin.H{"error":"Failed to parse multipart form"})
    return
  }
 // 从form中获取文件切片
    files := form.File["files"] // "files" 是HTML表单中的input[type=file]的name属性值

    // 遍历所有上传的文件
    for _, fileHeader := range files {
        // 打开文件
        file, err := fileHeader.Open()
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
        defer file.Close()

        // 保存文件到服务器
        filePath := "path/to/save/" + fileHeader.Filename
        err = c.SaveUploadedFile(file, filePath)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        // 处理成功后的逻辑
        c.JSON(http.StatusOK, gin.H{"message": "File uploaded successfully", "file_path": filePath})
    }
}

文件下载

1. 使用c.File方法

Gin提供了c.File方法,可以直接从HTTP响应中提供文件。这是最简单的方法之一,但它仅适用于服务于静态文件的场景。

Go 复制代码
func downloadFile(c *gin.Context) {
    // 指定文件路径
    filePath := "path/to/your/file.txt"

    // 检查文件是否存在
    if _, err := os.Stat(filePath); err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "File not found"})
        return
    }

    // 设置文件名作为Content-Disposition响应头的一部分
    c.Header("Content-Disposition", "attachment; filename="+filepath.Base(filePath))

    // 使用c.File方法发送文件
    c.File(filePath)
}

2. 使用c.Writerio.Copy

如果你需要更多的控制,比如动态生成文件内容或者处理大文件,可以使用c.Writerio.Copy来手动写入文件内容。

go 复制代码
func downloadFile(c *gin.Context) {
    // 指定文件内容
    fileContent := "This is the file content."

    // 设置响应头
    c.Header("Content-Disposition", "attachment; filename=file.txt")
    c.Header("Content-Type", "text/plain")

    // 写入文件内容
    _, err := io.WriteString(c.Writer, fileContent)
    if err != nil {
        c.AbortWithStatus(http.StatusInternalServerError)
    }
}

3. 流式下载大文件

对于大文件,可以使用流式传输来避免一次性加载整个文件到内存中。

go 复制代码
func downloadLargeFile(c *gin.Context) {
    // 指定大文件路径
    filePath := "path/to/your/largefile.zip"

    // 打开文件
    file, err := os.Open(filePath)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "File not found"})
        return
    }
    defer file.Close()

    // 设置响应头
    c.Header("Content-Disposition", "attachment; filename="+filepath.Base(filePath))
    c.Header("Content-Type", "application/octet-stream")

    // 流式传输文件内容
    _, err = io.Copy(c.Writer, file)
    if err != nil {
        c.AbortWithStatus(http.StatusInternalServerError)
    }
}

4. 使用http.ServeContent

Go标准库中的http.ServeContent函数可以用于提供文件下载。这个方法允许你利用http.ServeContent的缓存控制和其他功能。

go 复制代码
func downloadFile(c *gin.Context) {
    // 指定文件路径
    filePath := "path/to/your/file.txt"

    // 检查文件是否存在
    if _, err := os.Stat(filePath); err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "File not found"})
        return
    }

    // 打开文件
    file, err := os.Open(filePath)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error opening file"})
        return
    }
    defer file.Close()

    // 获取文件信息
    fileInfo, _ := file.Stat()

    // 设置响应头
    c.Header("Content-Disposition", "attachment; filename="+filepath.Base(filePath))
    c.Header("Content-Type", "application/octet-stream")

    // 使用http.ServeContent进行流式传输
    http.ServeContent(c.Writer, c.Request, fileInfo, int64(fileInfo.Size()), file)
}

前后端分离

  1. 后端:提供文件下载的HTTP路由处理函数,设置正确的响应头,并发送文件内容给前端。

  2. 前端:通过HTTP请求与后端通信,并处理文件下载过程。

后端实现
go 复制代码
package main

import (
	"io"
	"net/http"
	"os"
	"path/filepath"

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

func main() {
	r := gin.Default()

	// 文件下载路由
	r.GET("/download/:filename", func(c *gin.Context) {
		filename := c.Param("filename") // 获取文件名参数
		filePath := filepath.Join("path/to/files/", filename) // 定义文件的路径

		// 检查文件是否存在
		if _, err := os.Stat(filePath); os.IsNotExist(err) {
			c.JSON(http.StatusNotFound, gin.H{"message": "File not found"})
			return
		}

		// 设置响应头
		c.Header("Content-Disposition", "attachment; filename="+filepath.Base(filePath))
		c.Header("Content-Type", "application/octet-stream")

		// 打开文件并将其写入响应流
		file, err := os.Open(filePath)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"message": "Error opening file"})
			return
		}
		defer file.Close()

		_, err = io.Copy(c.Writer, file)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"message": "Error writing file to response"})
			return
		}
	})

	r.Run(":8080") // 启动Gin服务器
}
前端实现

前端可以使用标准的HTML <a>标签或者JavaScript来发起文件下载请求。以下是两种常见的前端下载方法:

方法1:使用HTML链接

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Download Example</title>
</head>
<body>
    <!-- 下载链接 -->
    <a href="/download/myfile.txt" download>Download File</a>
</body>
</html>

在这个HTML示例中,<a>标签的href属性设置为文件下载的URL,download属性指定了要下载的文件名。

方法2:使用JavaScript

javascript 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Download Example</title>
</head>
<body>
    <button id="downloadButton">Download File</button>
    <script>
        document.getElementById('downloadButton').addEventListener('click', function() {
            fetch('/download/myfile.txt')
                .then(response => {
                    if (response.ok) return response.blob();
                    throw new Error('Network response was not ok.');
                })
                .then(blob => {
                    const url = window.URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.style.display = 'none';
                    a.href = url;
                    a.download = 'myfile.txt';
                    document.body.appendChild(a);
                    a.click();
                    window.URL.revokeObjectURL(url);
                })
                .catch(error => {
                    console.error('There has been a problem with your fetch operation:', error);
                });
        });
    </script>
</body>
</html>

在这个JavaScript示例中,当用户点击按钮时,会发起一个GET请求到文件下载的URL。然后,它将响应转换为Blob,并创建一个临时的URL。接着,它创建一个隐藏的<a>标签,设置href为Blob URL,并指定download属性来触发文件下载。

中间件

Middleware

中间件是Gin中的一个强大的功能,它允许开发者在处理请求的流程中插入自定义的逻辑。中间件可以用于日志记录、用户认证、跨域资源共享(CORS)处理等。

在Gin中,中间件可以通过Use方法全局注册,或者在特定路由组中注册。Gin中的中间件必须是一个gin.HandlerFunc类型

HandlerFunc

处理函数是实际处理请求的函数。它们接收一个*gin.Context对象作为参数,并返回一个响应。处理函数可以是任何满足gin.HandlerFunc类型的函数。

go 复制代码
package main

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

func main() {
	r := gin.Default()

	// 全局中间件
	r.Use(LoggingMiddleware)

	// 特定路由组的中间件
	v1 := r.Group("/v1")
	v1.Use(AuthenticationMiddleware)
	v1.GET("/books", GetBooks)

	r.Run(":8080")
}

// LoggingMiddleware 是一个记录日志的中间件
func LoggingMiddleware(c *gin.Context) {
	c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // 允许跨域请求
	start := time.Now()
	c.Next() // 处理请求
	latency := time.Since(start)
	log.Printf("Request took %v", latency)
}

多个中间件

r.GET,后面可以跟很多HandlerFunc方法,这些方法其实都可以叫中间件

Go 复制代码
package main

import (
  "fmt"
  "github.com/gin-gonic/gin"
)

func m1(c *gin.Context) {
  fmt.Println("m1 ...in")
}
func m2(c *gin.Context) {
  fmt.Println("m2 ...in")
}

func main() {
  router := gin.Default()

  router.GET("/", m1, func(c *gin.Context) {
    fmt.Println("index ...")
    c.JSON(200, gin.H{"msg": "响应数据"})
  }, m2)

  router.Run(":8080")
}

/*
m1  ...in
index ...
m2  ...in
*/

中间件拦截响

c.Abort() 方法用于立即终止当前请求的后续处理。当你调用这个方法时,Gin 会停止执行当前路由链中的后续中间件和处理函数,并直接返回给客户端一个错误响应(通常是 500 内部服务器错误)。c.Abort()拦截,后续的HandlerFunc就不会执行了

Go 复制代码
package main

import (
  "fmt"
  "github.com/gin-gonic/gin"
)

func m1(c *gin.Context) {
  fmt.Println("m1 ...in")
  c.JSON(200, gin.H{"msg": "第一个中间件拦截了"})
  c.Abort()
}
func m2(c *gin.Context) {
  fmt.Println("m2 ...in")
}

func main() {
  router := gin.Default()

  router.GET("/", m1, func(c *gin.Context) {
    fmt.Println("index ...")
    c.JSON(200, gin.H{"msg": "响应数据"})
  }, m2)

  router.Run(":8080")
}

中间件放行

c.Next() 方法用于继续执行当前路由链中的下一个中间件或处理函数。如果你的中间件需要在处理请求之前或之后执行某些操作,但不影响后续中间件的执行,那么你应该在适当的时候调用 c.Next()c.Next(),Next前后形成了其他语言中的请求中间件和响应中间件

Go 复制代码
package main

import (
  "fmt"
  "github.com/gin-gonic/gin"
)

func m1(c *gin.Context) {
  fmt.Println("m1 ...in")
  c.Next()
  fmt.Println("m1 ...out")
}
func m2(c *gin.Context) {
  fmt.Println("m2 ...in")
  c.Next()
  fmt.Println("m2 ...out")
}

func main() {
  router := gin.Default()

  router.GET("/", m1, func(c *gin.Context) {
    fmt.Println("index ...in")
    c.JSON(200, gin.H{"msg": "响应数据"})
    c.Next()
    fmt.Println("index ...out")
  }, m2)

  router.Run(":8080")
}

/*
m1 ...in
index ...in
m2 ...in   
m2 ...out  
index ...out
m1 ...out
*/

路由

Router

路由是Gin的核心,负责将请求的URL和HTTP方法映射到相应的处理函数上。Gin使用httprouter作为其路由库,这是一个高性能的路由库,支持RESTful API设计。Gin的路由机制非常灵活,允许开发者定义路由和相应的处理函数。路由可以包含动态参数、查询参数、路由组等。

Group

组是Gin中的一个概念,它允许你将一组路由组织在一起,并且可以为这个组添加前缀路径和中间件。这使得路由的组织和管理变得更加灵活。

以下是定义路由的例子:

go 复制代码
func main() {
	r := gin.Default()

	// 定义GET请求的路由
	r.GET("/books/:id", GetBookByID)

	// 定义POST请求的路由
	r.POST("/books", CreateBook)

	// 定义PUT请求的路由
	r.PUT("/books/:id", UpdateBook)

	// 定义DELETE请求的路由
	r.DELETE("/books/:id", DeleteBook)

	r.Run(":8080")
}

// GetBookByID 处理GET请求
func GetBookByID(c *gin.Context) {
	bookID := c.Param("id")
	// 根据ID获取书籍信息...
	c.JSON(http.StatusOK, gin.H{"id": bookID, "message": "Book retrieved"})
}

// CreateBook 处理POST请求
func CreateBook(c *gin.Context) {
	var book Book
	if err := c.ShouldBindJSON(&book); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	// 创建新书籍...
	c.JSON(http.StatusOK, gin.H{"data": book})
}

// UpdateBook 处理PUT请求
func UpdateBook(c *gin.Context) {
	bookID := c.Param("id")
	var book Book
	if err := c.ShouldBindJSON(&book); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	// 更新书籍信息...
	c.JSON(http.StatusOK, gin.H{"data": book})
}

// DeleteBook 处理DELETE请求
func DeleteBook(c *gin.Context) {
	bookID := c.Param("id")
	// 删除书籍...
	c.JSON(http.StatusOK, gin.H{"id": bookID, "message": "Book deleted"})
}

在上面的例子中,我们定义了四个不同的HTTP方法(GET, POST, PUT, DELETE)的路由,每个路由都有一个对应的处理函数。:id是一个动态参数,可以在处理函数中通过c.Param("id")获取。

日志

Gin框架提供了一个内置的日志中间件,可以帮助开发者记录HTTP请求和响应的详细信息。这对于调试、监控和分析Web应用程序的行为非常有用。Gin的日志中间件可以通过标准库中的log包或其他第三方日志库来实现。

默认的日志记录

Gin框架的默认实例(通过gin.Default()创建)已经包含了一个默认的日志记录器,它使用标准库的log包。这个日志记录器会输出每个请求的基本信息,包括请求方法、路径、状态码和处理请求所花费的时间。

go 复制代码
package main

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

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "pong"})
	})
	r.Run(":8080")
}

在这个例子中,当你访问/ping路由时,Gin会自动记录请求的日志信息。

自定义日志中间件

如果你需要更详细的日志记录或想要使用不同的日志库,你可以创建自定义的日志中间件。以下是一个使用标准库log包创建的自定义日志中间件的例子:

go 复制代码
package main

import (
	"log"
	"net/http"
	"time"

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

func requestLogger(c *gin.Context) {
	// 访问开始时间
	startTime := time.Now()

	// 处理请求
	c.Next()

	// 访问结束时间
	endTime := time.Now()

	// 访问耗时
	latency := endTime.Sub(startTime)

	// 记录日志
	log.Printf("%s %s %s %d %s\n", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Writer.Status(), latency)
}

func main() {
	r := gin.New()
	r.Use(requestLogger)

	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "pong"})
	})

	r.Run(":8080")
}

在这个例子中,我们定义了一个requestLogger函数,它会在每个请求处理之前记录客户端IP、请求方法、请求路径、状态码和处理请求的耗时。然后,我们通过r.Use(requestLogger)将这个日志中间件添加到Gin路由器中。

使用第三方日志库

Gin也可以与第三方日志库一起使用,例如zaplogruszerolog等。这些库提供了更丰富的日志记录功能,包括日志级别、结构化日志记录和异步日志记录等。

使用logrus库创建的自定义日志中间件的例子:

go 复制代码
package main

import (
	"io/ioutil"
	"net/http"
	"time"

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

// 日志中间件
func LoggerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 在请求处理之前记录请求开始的时间
		start := time.Now()
		c.Next()

		// 请求结束后记录请求信息
		log.WithFields(logrus.Fields{
			"method": c.Request.Method,
			"path":    c.Request.URL.Path,
			"ip":      c.ClientIP(),
			"status":  c.Writer.Status(),
			"duration": time.Since(start),
		}).Info("Request completed")
	}
}

func main() {
	// 初始化logrus
	log := logrus.New()
	log.SetFormatter(&logrus.JSONFormatter{})
	log.SetOutput(ioutil.Discard) // 设置日志输出到控制台,生产环境可以设置为文件或其他输出

	// 初始化Gin路由器
	r := gin.Default()

	// 使用自定义的日志中间件
	r.Use(LoggerMiddleware())

	// 定义一个示例路由
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "Hello, World!"})
	})

	// 启动Gin服务器
	r.Run(":8080")
}

我们首先定义了一个LoggerMiddleware函数,它返回一个gin.HandlerFunc。这个中间件在每个请求处理之前记录请求开始的时间,并在请求处理之后记录请求的详细信息,包括HTTP方法、路径、客户端IP、状态码和处理请求的耗时。

我们使用logrusWithFields方法来添加结构化的日志数据,并使用Info方法来记录日志。logrus.JSONFormatter用于格式化日志输出为JSON格式,而SetOutput方法设置了日志的输出目标,这里我们将其设置为控制台(ioutil.Discard

相关推荐
HappRobot9 小时前
# Go语法基础
go
Go_error12 小时前
在 Go 服务层中如何优雅地管理事务?
后端·go
王的宝库18 小时前
Gin + GORM
开发语言·golang·gin
妙码生花18 小时前
golang 应用服务端部署(使用 systemd 服务)
开发语言·人工智能·后端·golang·node.js·php·gin
超级无敌霹雳大狗熊19 小时前
以最小demo讲解Eino中的graph编排
go·agent
神秘的猪头19 小时前
Gin 项目为什么要分 Handler、Service、Repository?顺便讲透接口、依赖注入与指针
后端·go
妙码生花2 天前
PHP 各框架下和 Go 的性能比较
前端·后端·go
江湖十年2 天前
Go 还是 Golang?可能你一直都搞错了!
后端·面试·go