抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

上传、下载和重定向

上传下载和重定向

文件上传是后端开发中很常见的需求。

文件上传的原理无非是客户端 选择文件并点击上传,浏览器将文件内容读取然后传送到服务器;

服务端读取数据并新建文件,写入浏览器传输过来的内容。

单个文件上传

前端部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
</head>
<body>

<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f1"/>
<input type="submit" value="上传"/>
</form>

</body>
</html>


前端需要用一个表单来进行上传,这里我们设置了表单提交的请求方式为 POST。

注意一定要指定编码类型 enctype="multipart/form-data"

上传文件后点击上传按钮会向 http://localhost:9090/upload 这个路径发起POST请求。

后端部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
r := gin.Default()

r.POST("/upload", func(c *gin.Context) {
// 从请求中读取文件
f, _ := c.FormFile("f1")
// 保存文件
c.SaveUploadedFile(f, "./" + f.Filename)
// 给客户端返回上传成功的消息
c.JSON(200, gin.H{"code": 2003, "msg": "上传成功"})
})

r.Run(":9090")
}

后端的处理主要分3步:

  1. 从请求中读取文件
  2. 保存文件
  3. 给客户端返回上传成功的消息

通过 gin.FormFile() 可以获取前端上传的文件,参数是 <input /> 标签的 name 属性,通过参数可以定位到上传的任何一个文件。

获取到文件后可以保存在数据库中,或者通过 gin.SaveUploadedFile() 将文件保存在某个位置,只需要将文件对象 f 和 路径作为参数传入即可。

最后给客户端返回一条上传成功的消息。

测试

上传文件

上传成功

成功保存

使用 Postman

使用 PostMan 时需要给 Header 添加一个属性 Content-Type: multipart/form-data

然后在 Body 中的 form-data 中上传数据

使用 curl 命令

1
2
3
curl -X POST http://localhost:9090/upload \
-F "file=@/xx/xx/xxx.zip" \
-H "Content-Type: multipart/form-data"

多个文件上传

前端部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
</head>

<body>
<form action="/upload_multi" method="post" enctype="multipart/form-data">

<input type="file" name="files" multiple>
<input type="submit" value="上传">

</form>

</body>

</html>

后端部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
r := gin.Default()

r.POST("/upload", func(c *gin.Context) {
// 从请求中读取文件
form, _ := c.MultipartForm()
files := form.File["files"]

// 逐个保存文件
for _, file := range files {
c.SaveUploadedFile(f, "./"+f.Filename)
}

// 给客户端返回上传成功的消息
c.JSON(200, gin.H{"code": 2003, "msg": "上传成功"})
})

r.Run(":9090")
}

单个文件上传和多个文件上传只有一点点区别

多个文件上传使用 gin.Context.MultipartForm() 来获取,该方法会返回一个 Form 结构体,结构体中map类型字段 File 存储了所有已上传的文件

通过遍历来逐个处理,然后给客户端返回上传成功的消息即可。

上传文件大小限制

gin 默认上传的文件大小最大为 32Mb,如果要调小或调大,可以设置 gin.Engine.MaxMultipartMemory

eg:

1
2
3
4
5
6
7
func main() {
r := gin.Default()
// 为 multipart forms 设置较高的内存限制 (默认是 32 MiB)
r.MaxMultipartMemory = 2 << 30; // 2*2^30 byte = 2Gb

r.Run(":9090")
}

下载文件

实现下载功能要先获取文件名,然后给响应头添加一个属性 Content-Disposition: attachment; filename=文件名

然后调用 gin.Context.File() 方法,传入文件路径即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
r := gin.Default()
filename := "a.txt"
r.GET("/download", func(c *gin.Context) {
// 获取文件名
filename := "a.txt"
// 设置响应头
c.Header("Content-Disposition", "attachment; filename="+filename)
// 向客户端返回文件数据
c.File("./"+filename)
}
r.Run(":9090")
}

当然,还可以在响应头中添加更多信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
r := gin.Default()
filename := "a.txt"
r.GET("/download", func(c *gin.Context) {
// 获取文件名
filename := "a.txt"
// 设置状态码
c.Writer.WriteHeader(http.StatusOK)
// 添加内容位置声明
c.Header("Content-Disposition", "attachment; filename="+filename)
// 添加内容类型声明
c.Header("Content-Type", "application/text/plain")
// 向客户端返回数据
c.File("./" + filename)
}
r.Run(":9090")
}

注意!c.File(path) 中的参数 path 最好是绝对路径,可以使用 path.Join(路径, 文件名) 来得到绝对路径。
处理不好 path 参数容易出错。

重定向 和 转发

重定向和转发在实际开发过程中很有必要。

比如对 API 进行升级后,一些原本的请求路径不再使用,但是为了兼容,防止一些旧版本继续请求的时候请求失败,所以需要对原本的路由做重定向或转发处理。

区别:

  • 重定向:用户请求一个旧的地址的时候,返回301,并告知新地址;然后用户再次请求新地址,得到正确返回。

  • 重定向:重定向过后浏览器的URL会发生变化。

  • 重定向:可以将请求转到别的地址,这个地址可以是外部链接,也可以是内部其他路由。

  • 转发:用户请求一个旧地址的时候,服务端内部将请求路径修改为新地址,然后返回给用户

  • 转发:转发过后浏览器的URL不会发生变化。

  • 转发:只能转发到站内路由。

重定向和转发

重定向

1
c.Redirect(http.StatusMovedPermanently, "重定向的路径")

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
r := gin.Default()

r.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": c.FullPath() })
})

// 重定向到站内其他路由
r.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/index")
})

// 重定向到站外链接
r.GET("/redirect2", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.boii.xyz")
})

r.Run(":9090")
}

重定向到站内

重定向到站外

转发

1
c.Request.URL.Path = "转发的目标路径"

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
r := gin.Default()

r.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "msg": c.FullPath() })
})

// 转发到站内其他路由
r.GET("/forward", func(c *gin.Context) { // URL 不会改变
c.Request.URL.Path = "/index" // 修改请求的目标路径
r.HandleContext(c)
})

r.Run(":9090")
}

转发到站内

哔哔