Gin框架(3)

文件上传与下载

文件上传

单文件上传

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

// 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
}
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})
}

多文件上传

// 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框架处理多文件上传的实例

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响应中提供文件。这是最简单的方法之一,但它仅适用于服务于静态文件的场景。

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来手动写入文件内容。

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. 流式下载大文件

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

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的缓存控制和其他功能。

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请求与后端通信,并处理文件下载过程。

后端实现
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链接

<!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


<!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类型的函数。

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方法,这些方法其实都可以叫中间件

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就不会执行了

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前后形成了其他语言中的请求中间件和响应中间件

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

以下是定义路由的例子:

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包。这个日志记录器会输出每个请求的基本信息,包括请求方法、路径、状态码和处理请求所花费的时间。

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包创建的自定义日志中间件的例子:

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库创建的自定义日志中间件的例子:


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

相关推荐

  1. Gin框架3

    2024-04-04 10:14:02       32 阅读
  2. gin框架复习

    2024-04-04 10:14:02       55 阅读
  3. Go(四)gin框架

    2024-04-04 10:14:02       49 阅读
  4. gin:01-框架安装

    2024-04-04 10:14:02       32 阅读
  5. GIN框架_会话

    2024-04-04 10:14:02       30 阅读
  6. GIN框架_模板渲染

    2024-04-04 10:14:02       33 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-04 10:14:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-04 10:14:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-04 10:14:02       82 阅读
  4. Python语言-面向对象

    2024-04-04 10:14:02       91 阅读

热门阅读

  1. Qt之QSoundEffect播放简单音效

    2024-04-04 10:14:02       27 阅读
  2. NLP学习路线指南总结

    2024-04-04 10:14:02       36 阅读
  3. 使用 XCTest 进行 iOS UI 自动化测试

    2024-04-04 10:14:02       32 阅读
  4. 每天学习一个Linux命令之tee

    2024-04-04 10:14:02       33 阅读
  5. 说明计算机视觉(CV)技术的优势和挑战

    2024-04-04 10:14:02       42 阅读
  6. Go-Gin-Example 第九部分 实现redis缓存

    2024-04-04 10:14:02       29 阅读
  7. 品深文司绿茶——传承千古的爱情佳话

    2024-04-04 10:14:02       35 阅读
  8. .NET 设计模式—单例模式(SingletonPattern)

    2024-04-04 10:14:02       38 阅读
  9. 蓝桥杯每日一题(筛质数、最大公约数)

    2024-04-04 10:14:02       34 阅读