- Created by 郭强, last modified on Aug 01, 2022
GoFrame
支持非常方便的表单文件上传功能,并且HTTP客户端对上传功能进行了必要的封装并极大简化了上传功能调用。
注意哦:上传文件大小受到ghttp.Server
的ClientMaxBodySize
配置影响:https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig 默认支持的上传文件大小为8MB
。
服务端
在服务端通过Request
对象获取上传文件:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) // Upload uploads files to /tmp . func Upload(r *ghttp.Request) { files := r.GetUploadFiles("upload-file") names, err := files.Save("/tmp/") if err != nil { r.Response.WriteExit(err) } r.Response.WriteExit("upload successfully: ", names) } // UploadShow shows uploading simgle file page. func UploadShow(r *ghttp.Request) { r.Response.Write(` <html> <head> <title>GF Upload File Demo</title> </head> <body> <form enctype="multipart/form-data" action="/upload" method="post"> <input type="file" name="upload-file" /> <input type="submit" value="upload" /> </form> </body> </html> `) } // UploadShowBatch shows uploading multiple files page. func UploadShowBatch(r *ghttp.Request) { r.Response.Write(` <html> <head> <title>GF Upload Files Demo</title> </head> <body> <form enctype="multipart/form-data" action="/upload" method="post"> <input type="file" name="upload-file" /> <input type="file" name="upload-file" /> <input type="submit" value="upload" /> </form> </body> </html> `) } func main() { s := g.Server() s.Group("/upload", func(group *ghttp.RouterGroup) { group.POST("/", Upload) group.ALL("/show", UploadShow) group.ALL("/batch", UploadShowBatch) }) s.SetPort(8199) s.Run() }
该服务端提供了3个接口:
- http://127.0.0.1:8199/upload/show 地址用于展示单个文件上传的H5页面;
- http://127.0.0.1:8199/upload/batch 地址用于展示多个文件上传的H5页面;
- http://127.0.0.1:8199/upload 接口用于真实的表单文件上传,该接口同时支持单个文件或者多个文件上传;
我们这里访问 http://127.0.0.1:8199/upload/show 选择需要上传的单个文件,提交之后可以看到文件上传成功到服务器上。
关键代码说明
- 我们在服务端可以通过
r.GetUploadFiles
方法获得上传的所有文件对象,也可以通过r.GetUploadFile
获取单个上传的文件对象。 - 在
r.GetUploadFiles("upload-file")
中的参数"upload-file"
为本示例中客户端上传时的表单文件域名称,开发者可以根据前后端约定在客户端中定义,以方便服务端接收表单文件域参数。 - 通过
files.Save
可以将上传的多个文件方便地保存到指定的目录下,并返回保存成功的文件名。如果是批量保存,只要任意一个文件保存失败,都将会立即返回错误。此外,Save
方法的第二个参数支持随机自动命名上传文件。 - 通过
group.POST("/", Upload)
注册的路由仅支持POST
方式访问。
客户端
单文件上传
package main import ( "fmt" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/glog" ) func main() { var ( ctx = gctx.New() path = "/home/john/Workspace/Go/github.com/gogf/gf/v2/version.go" ) result, err := g.Client().Post(ctx, "http://127.0.0.1:8199/upload", "upload-file=@file:"+path) if err != nil { glog.Fatalf(ctx, `%+v`, err) } defer result.Close() fmt.Println(result.ReadAllString()) }
注意到了吗?文件上传参数格式使用了 参数名=@file:文件路径
,HTTP客户端将会自动解析文件路径对应的文件内容并读取提交给服务端。原本复杂的文件上传操作被gf
进行了封装处理,用户只需要使用 @file:+文件路径
来构成参数值即可。其中,文件路径
请使用本地文件绝对路径。
首先运行服务端程序之后,我们再运行这个上传客户端(注意修改上传的文件路径为本地真实文件路径),执行后可以看到文件被成功上传到服务器的指定路径下。
多文件上传
package main import ( "fmt" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/glog" ) func main() { var ( ctx = gctx.New() path1 = "/Users/john/Pictures/logo1.png" path2 = "/Users/john/Pictures/logo2.png" ) result, err := g.Client().Post( ctx, "http://127.0.0.1:8199/upload", fmt.Sprintf(`upload-file=@file:%s&upload-file=@file:%s`, path1, path2), ) if err != nil { glog.Fatalf(ctx, `%+v`, err) } defer result.Close() fmt.Println(result.ReadAllString()) }
可以看到,多个文件上传提交参数格式为参数名=@file:xxx&参数名=@file:xxx...
,也可以使用参数名[]=@file:xxx&参数名[]=@file:xxx...
的形式。
首先运行服务端程序之后,我们再运行这个上传客户端(注意修改上传的文件路径为本地真实文件路径),执行后可以看到文件被成功上传到服务器的指定路径下。
自定义文件名称
很简单,修改FileName
属性即可。
s := g.Server() s.BindHandler("/upload", func(r *ghttp.Request) { file := r.GetUploadFile("TestFile") if file == nil { r.Response.Write("empty file") return } file.Filename = "MyCustomFileName.txt" fileName, err := file.Save(gfile.TempDir()) if err != nil { r.Response.Write(err) return } r.Response.Write(fileName) }) s.SetPort(8999) s.Run()
规范路由接收上传文件
服务端如果通过规范路由方式,那么可以通过结构化的参数获取上传文件:
- 参数接收的数据类型使用
*ghttp.UploadFile
- 如果需要接口文档也支持文件类型,那么参数的标签中设置
type
为file
类型
Content Menu
- No labels
19 Comments
杨率帅
为这里补充一个 断点文件下载的服务端实现。 实现不一定是最完美的,通过了迅雷的测试
文件的断点下载服务端 · 语雀 (yuque.com)
智刚
兄弟,你总结的问题可以贴到咱们的FAQ里哈
FAQ地址:常见问题(FAQ)
王一飞
强哥请问一下,怎么约束上传文件的类型
海亮
可以写个正则判断一下
saner
文件上传进度怎么获取?
unite小伟
请问下,GetUploadFile方法拿到的不是文件流,是一个*UploadFile,所有第三方的云储存都要求要io的文件流,怎么拿到哦
郭强
笨,你看下
UploadFile
的定义撒,嵌套了一个multipart.FileHeader
结构,相当于是标准库multipart.FileHeader
的扩展结构体,因此你使用Open
方法即可。Victor
file = request.GetUploadFile("file")
在这个返回的 *ghttp.UploadFile对象里没有找到获取file路径的方法,
demo里是把file保存到指定目录
在不需要保存的情况下,如果能拿到file路径就可以 节省一次文件写入保存再删除的 磁盘io操作
比如数据导入的场景,导入文件解析完之后就不需要了.
有没有办法需要拿到上传后的临时文件路径名称以供其他解析库读取?感谢!
郭强
可以看下
*ghttp.UploadFile
里面的具体实现。18lkdev
我感觉upload库得完善一些,这个常用的
郭强
具体一点?欢迎提
issue
或者pr
?Lambert
规范路由能使用ghttp.UploadFiles吗
智刚
是可以的
Lambert
我使用ghttp.UploadFiles接受参数,在校验那里给我返回了"reflect.Value.Convert: value of type *ghttp.UploadFile cannot be converted to type ghttp.UploadFile"的错误
Lambert
type PutImagesReq struct {
g.Meta `path:"/images" method:"post" mime:"multipart/form-data" summary:"上传图片"`
ImageFiles ghttp.UploadFiles `json:"image_files" dc:"上传的图片"`
ImageType int `json:"image_type"`
HotelId uint64 `json:"id"`
}
请求的结构体是这样的
智刚
这个结构体在最新版本是可以上传的,请问你的gf版本是哪个
这个pr(https://github.com/gogf/gf/pull/1817)是修过这个问题的
Lambert
我用的2.06,不是*ghttp.UploadFile是ghttp.UpoladFiles 是定义的[]*ghttp.UploadFile的array,接收多个文件的
智刚
建议你升级gf版本,新版本修复和改进好多小技巧哦
Lambert
升级了还是不行,使用规范路由只能上传单个文件 用*ghttp.UploadFile接收,多个文件的ghttp.UploadFiles还是接收不到,校验不通过