四.上传和下载
一.文件的上传
在网页开发中,常常需要上传文件内容到网页。
这里采用三种方法来实现文件的上传
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)
}
如,定义n
为io.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")
//数据传输方式设置为二进制,防止传输后文件出现乱码