GoFrame支持非常方便的表单文件上传功能,并且HTTP客户端对上传功能进行了必要的封装并极大简化了上传功能调用。

注意哦:上传文件大小受到ghttp.ServerClientMaxBodySize配置影响: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个接口:

  1. http://127.0.0.1:8199/upload/show 地址用于展示单个文件上传的H5页面;
  2. http://127.0.0.1:8199/upload/batch 地址用于展示多个文件上传的H5页面;
  3. http://127.0.0.1:8199/upload 接口用于真实的表单文件上传,该接口同时支持单个文件或者多个文件上传;

我们这里访问 http://127.0.0.1:8199/upload/show 选择需要上传的单个文件,提交之后可以看到文件上传成功到服务器上。

关键代码说明

  1. 我们在服务端可以通过r.GetUploadFiles方法获得上传的所有文件对象,也可以通过r.GetUploadFile获取单个上传的文件对象。
  2. r.GetUploadFiles("upload-file")中的参数"upload-file"为本示例中客户端上传时的表单文件域名称,开发者可以根据前后端约定在客户端中定义,以方便服务端接收表单文件域参数。
  3. 通过files.Save可以将上传的多个文件方便地保存到指定的目录下,并返回保存成功的文件名。如果是批量保存,只要任意一个文件保存失败,都将会立即返回错误。此外,Save方法的第二个参数支持随机自动命名上传文件。
  4. 通过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
  • 如果需要接口文档也支持文件类型,那么参数的标签中设置typefile类型



Content Menu

  • No labels

25 Comments

  1. 为这里补充一个 断点文件下载的服务端实现。 实现不一定是最完美的,通过了迅雷的测试

    文件的断点下载服务端 · 语雀 (yuque.com)

    1. 兄弟,你总结的问题可以贴到咱们的FAQ里哈 
      FAQ地址:常见问题(FAQ)

  2. 强哥请问一下,怎么约束上传文件的类型

    1. 可以写个正则判断一下

  3. 文件上传进度怎么获取?

  4. 请问下,GetUploadFile方法拿到的不是文件流,是一个*UploadFile,所有第三方的云储存都要求要io的文件流,怎么拿到哦

    1. 笨,你看下UploadFile的定义撒,嵌套了一个multipart.FileHeader结构,相当于是标准库multipart.FileHeader的扩展结构体,因此你使用Open方法即可。

  5. file    = request.GetUploadFile("file")
    在这个返回的 *ghttp.UploadFile对象里没有找到获取file路径的方法,

    demo里是把file保存到指定目录
    在不需要保存的情况下,如果能拿到file路径就可以 节省一次文件写入保存再删除的 磁盘io操作

    比如数据导入的场景,导入文件解析完之后就不需要了.

    有没有办法需要拿到上传后的临时文件路径名称以供其他解析库读取?感谢!

    1. 可以看下 *ghttp.UploadFile 里面的具体实现。

  6. 我感觉upload库得完善一些,这个常用的

    1. 具体一点?欢迎提issue或者pr

  7. 规范路由能使用ghttp.UploadFiles吗

      1. 我使用ghttp.UploadFiles接受参数,在校验那里给我返回了"reflect.Value.Convert: value of type *ghttp.UploadFile cannot be converted to type ghttp.UploadFile"的错误

        1. 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"`
          }

          请求的结构体是这样的

          1. // UploadInput .
            type UploadInput struct {
            	UploadType int               `v:"required|in:0,100,200,300,400" json:"uploadType" dc:"type 上传类型 0 默认 "`
            	UploadFile *ghttp.UploadFile `json:"uploadFile" dc:"uploadFile 上传文件file格式"`
            }
            
            // UploadReq .
            type UploadReq struct {
            	g.Meta `path:"/upload" tags:"公共接口" method:"POST" summary:"文件、图片上传"`
            	*UploadInput
            }

            这个结构体在最新版本是可以上传的,请问你的gf版本是哪个

            这个pr(https://github.com/gogf/gf/pull/1817)是修过这个问题的

            1. 我用的2.06,不是*ghttp.UploadFile是ghttp.UpoladFiles 是定义的[]*ghttp.UploadFile的array,接收多个文件的

              1. 建议你升级gf版本,新版本修复和改进好多小技巧哦 

                1. 升级了还是不行,使用规范路由只能上传单个文件 用*ghttp.UploadFile接收,多个文件的ghttp.UploadFiles还是接收不到,校验不通过

  8. type UploadReq struct {
    g.Meta `path:"/upload" method:"post" mime:"multipart/form-data" tags:"通用工具" summary:"上传文件"`
    File *ghttp.UploadFile `json:"file" type:"file" dc:"选择上传文件"`
    }

    func (c *cUpload) Upload(ctx context.Context, req *UploadReq) (res *UploadRes, err error) {
    if req.File == nil {
    fmt.Println("没有上传文件!")
    }
    fmt.Println(222)

    controller这里判断nil 不生效呢?控制台直接报错了:

    2022-10-24 14:42:19.376 [ERRO] {9080580c2dee2017f70e0c00e93337db} reflect.Value.Convert: value of type string cannot be converted to type ghttp.UploadFile
    1. reflect.Value.Convert: value of type string cannot be converted to type ghttp.UploadFile

    1. 这个我也遇到了,是因为你默认值传了个空字符串,所以直接报错了

      1. 是的,确实是这样的。

  9. type StreamDictationReq struct {
        g.Meta   `path:"/stream_dictation" method:"post" mime:"multipart/form-data" tags:"语音转写"  dc:"file 字段为必填且文件格式为.pcm"`
        Language string            `json:"language" v:"required" d:"zh_cn" dc:"语种。zh_cn:中文(支持简单的英文识别),en_us:英文"`
        Domain   string            `json:"domain" v:"required" d:"iat" dc:"应用领域"`
        Accent   string            `json:"accent" v:"required" d:"mandarin" dc:"方言"`
        File     *ghttp.UploadFile `json:"file" type:"file" v:"required" dc:"上传的文件"`
    }

    在api 的req 结构体上添加 上传文件类型的时候 ,在swagger文档上相关的字段介绍  不显示介绍内容  只显示   object (github.com.gogf.gf.v2.net.ghttp.UploadFile)

    郭强

  10. goframe g.RequestFromCtx(ctx).FormFile获取文件名称

  11. 在api结构体里指定type为file没有用,导进文档还是显示为string,而且文档里连注释都没了