GoWeb开发的gin框架学习 part four

Posted by OuterCyrex on June 4, 2024

四.上传和下载

一.文件的上传

在网页开发中,常常需要上传文件内容到网页。

这里采用三种方法来实现文件的上传

1.ReadAll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

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

func _FileReader(c *gin.Context) {
	fileHeader, err := c.FormFile("file")
	if err != nil {
		fmt.Println(err)
		return
	}
	reader, _ := fileHeader.Open()
	defer reader.Close()
	data, _ := io.ReadAll(reader)
	fmt.Println(string(data))
	fmt.Println(fileHeader.Filename)
	fmt.Println(float64(fileHeader.Size)/1024.0, "KB")
	c.JSON(200, gin.H{"message": "success"})
}

func main() {
	router := gin.Default()
	router.POST("/upload", _FileReader)
	_ = router.Run(":8080")
}

通过封装,_FileReader可以通过ReadAll方法可以直接读取文件内容,用户上传的文件在被读取后,以字符串的形式被输出。

返回值:

1
2
3
4
//创建一个test.txt文件,内容为Hello World
Hello World
test.txt
0.0107421875 KB

对封装函数进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func _FileReader(c *gin.Context) {
	fileHeader, err := c.FormFile("file") 
	if err != nil {
		fmt.Println(err)
		return
	}
	reader, _ := fileHeader.Open()
	defer reader.Close()
	data, _ := io.ReadAll(reader)
	fmt.Println(string(data))
	fmt.Println(fileHeader.Filename)
	fmt.Println(float64(fileHeader.Size)/1024.0, "KB")
	c.JSON(200, gin.H{"message": "success"})
}

通过FormFile方法,将前端传入的文件的各种信息录入

1
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)

FormFile方法根据传入的键(name string)来确定读取哪个文件,并返回一个包含文件各种属性的*multipart.FileHeader类型和一个error值。

1
2
3
4
5
6
7
8
9
10
11
type FileHeader struct {
	Filename string
	Header   textproto.MIMEHeader
	Size     int64

	content   []byte
	tmpfile   string
	tmpoff    int64
	tmpshared bool
}
//FileHeader结构体的内容,包含文件的各种属性

之后的语句控制文件的打开延迟关闭

1
2
reader, _ := fileHeader.Open() //此处将error值忽略
	defer reader.Close()

然后通过ReadAll方法读取文件内容并以字符串形式输出

1
2
data, _ := io.ReadAll(reader)
	fmt.Println(string(data))

2.Copy

可以通过Copy函数将读入的文件内容Copy给另一个文件

封装结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func _FileReader(c *gin.Context) {
	fileHeader, err := c.FormFile("file")
	if err != nil {
		fmt.Println(err)
		return
	}
	reader, _ := fileHeader.Open()
	defer reader.Close()
	newFile, _ := os.Create("upload/xdzn2.png")
	_, _ = io.Copy(newFile, reader)
	fmt.Println(fileHeader.Filename)
	fmt.Println(float64(fileHeader.Size)/1024.0, "KB")
	c.JSON(200, gin.H{"message": "success"})
}

读取过程与ReadAll的内容一致

1
2
3
4
//os.Create函数,在项目对应的位置创建一个xdzn2.png文件(此时为空文件)
newFile, _ := os.Create("upload/xdzn2.png")
//io.Copy(file1,file2)可以将file2文件的内容Copy给file1
_, _ = io.Copy(newFile, reader)

其中,io.Copy的两个返回值为Copy的字节数以及一个error

1
2
3
func Copy(dst Writer, src Reader) (written int64, err error) {
	return copyBuffer(dst, src, nil)
}

如,定义nio.Copy的第一个返回值

则下述的代码输出内容是一致的。

1
2
fmt.Println(float64(n)/1024.0, "KB")
fmt.Println(float64(fileHeader.Size)/1024.0, "KB")

3.SaveUploadedFile

通过gin框架的SaveUploadedFile可以快速实现上传文件的保存。

1
2
3
4
5
6
7
8
9
10
11
12
func _FileReader(c *gin.Context) {
	fileHeader, err := c.FormFile("file")
	if err != nil {
		fmt.Println(err)
	}
	err = c.SaveUploadedFile(fileHeader, "upload/xdzn3.png")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(float64(fileHeader.Size)/1024.0, "KB")
	c.JSON(200, gin.H{"message": "success"})
}

上述代码将上传的文件保存为"upload.xdzn3.png"的文件

其中,SaveUploadedFile方法的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
}

不难发现,SaveUploadedFile方法实则是通过io.Copy来实现的。

4.批量上传文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

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

func main() {
	router := gin.Default()
	router.POST("/uploads", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files, _ := form.File["file"]
		for _, file := range files {
			_ = c.SaveUploadedFile(file, "upload"+file.Filename)
		}
        c.JSON(200, gin.H{"message": fmt.Sprintf("success in upload %d files", len(files))})
	})
	_ = router.Run(":8080")
}

通过MultipartForm方法,同时读取多个文件的内容并返回一个Form类型和error

1
2
3
4
type Form struct {
    Value map[string][]string
    File  map[string][]*FileHeader
}

Form.File["file"](file为文件对应的键)的所有内容赋值给files,这样我们就得到了一个装有所有上传文件的切片

通过对files进行遍历,逐个将其中的文件继续保存。

保存时,为了批量处理文件命名,可以采取"路径"+file.Filename,直接以file.Filename作为其文件名。

二.文件的下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

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

func main() {
	router := gin.Default()
	router.GET("/download", func(c *gin.Context) {
		c.Header("Content-Type", "application/octet-stream")
		c.Header("Content-Disposition", "attachment;filename="+"xdzn.png")
		c.Header("Content-Transfer-Encoding", "binary")
		c.File("upload/xdzn.png")
	})
	router.Run(":8080")
}

通过File方法,可以实现文件的下载,但大多数客户端可能会将其直接在网页中显示。

因此需要对文件的操作进行一定的限制。

1
2
3
4
5
6
c.Header("Content-Type", "application/octet-stream")
//将类型设置为文件传输流(octet-stream)
c.Header("Content-Disposition", "attachment;filename="+"xdzn.png")
//将文件下载后命名为"xdzn.png"
c.Header("Content-Transfer-Encoding", "binary")
//数据传输方式设置为二进制,防止传输后文件出现乱码