如何在Go Gin中从JSON中获取发布的文件?

huangapple go评论87阅读模式
英文:

How to get file posted from JSON in go gin?

问题

我想保存由JSON发布的图像文件。

这是发布的结构:

type Article struct {
    Title string `json:"title"`
    Body  string `json:"body"`
    File  []byte `json:"file"`
}

处理程序是:

func PostHandler(c *gin.Context) {
    var err error
    var json Article
    err = c.BindJSON(&json)
    if err != nil {
        log.Panic(err)
    }

    // 处理照片上传
    var filename string
    file := json.File
    if file == nil {
        fmt.Println("文件为空")
        filename = ""
    } else {
        data := file
        filename = path.Join("media", shared.RandString(5)+path.Ext(header.Filename))

        err = ioutil.WriteFile(filename, data, 0777)
        if err != nil {
            io.WriteString(w, err.Error())
            return
        }
    }
    ...
}

但是我得到了

assignment count mismatch: 3 = 1

我从一个工作的多部分表单处理程序中复制了文件处理部分,它工作得很好,但显然

file, header, err := r.FormFile("uploadfile")

不能转换为JSON处理。

我查看了gin文档,但找不到涉及JSON文件处理的示例。那么我该如何解决这个问题?

英文:

I want to save image file posted by JSON.

Here is the struct of the post:

type Article struct {
	Title   string `json:"title"`
	Body string `json:"body"`
	File    []byte `json:"file"`
}

And the handler is :

   func PostHandler(c *gin.Context) {
    	var err error
    	var json Article
    	err = c.BindJSON(&json)
    	if err != nil {
    		log.Panic(err)
    	}
    
    //handle photo upload
    	var filename string
    	file, header, err := json.File  //assignment count mismatch: 3 = 1
    
    	if err != nil {
    		fmt.Println(err)
    		filename = ""
    
    	} else {
    		data, err := ioutil.ReadAll(file)
    		if err != nil {
    			fmt.Println(err)
    			return
    		}
    
    		filename = path.Join("media", +shared.RandString(5)+path.Ext(header.Filename))
    
    		err = ioutil.WriteFile(filename, data, 0777)
    		if err != nil {
    			io.WriteString(w, err.Error())
    			return
    		}
    
    	}
...

But I get

> assignment count mismatch: 3 = 1

I copied the file handling part from a working multipart form handler which worked fine but apparently,

file, header, err := r.FormFile("uploadfile")

can not be translated into JSON handling.

I have looked at gin docs but could not find examples involving json file handling.
So how can I fix this?

答案1

得分: 8

使用Gin获取上传的文件

我认为你的问题是“使用Gin,如何获取上传的文件?”。大多数开发者不会使用JSON编码来上传文件,虽然可以这样做,但需要将文件包含为base64字符串(并且会增加文件大小约33%)。

常见(且更高效)的做法是使用“multipart/form-data”编码来上传文件。其他人提供的代码file, header, err := c.Request.FormFile("file")可以工作,但这会劫持Gin扩展的底层“net/http”包。

我的建议是使用ShouldBind,但你也可以使用Gin包提供的FormFileMultipartForm方法。

以下是示例。Gin页面上也提供了类似(但不太详细)的解释,链接为https://github.com/gin-gonic/gin#model-binding-and-validation和https://github.com/gin-gonic/gin#upload-files。


上传单个文件


客户端

HTML

<form action="/upload" method="POST">
  <input type="file" name="file">
  <input type="submit">
</form>

Curl

curl -X POST http://localhost:8080/upload \
  -F "file=../path-to-file/file.zip" \
  -H "Content-Type: multipart/form-data"

服务器端

Go

package main

import (
	"github.com/gin-gonic/gin"
    "log"
	"net/http"
	"io/ioutil"
)

type Form struct {
	File *multipart.FileHeader `form:"file" binding:"required"`
}

func main() {
	router := gin.Default()

    // 设置较低的内存限制以处理多部分表单(默认为32 MiB)
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB

	router.POST("/upload", func(c *gin.Context) {

        // 使用`ShouldBind`
        // --------------------
		var form Form
        _ := c.ShouldBind(&form)

        // 获取原始文件字节 - 没有读取器方法
	    // openedFile, _ := form.File.Open()
	    // file, _ := ioutil.ReadAll(openedFile)

        // 上传到磁盘
        // `form.File`有io.reader方法
        // c.SaveUploadedFile(form.File, path)
        // --------------------

        // 使用`FormFile`
        // --------------------
        // formFile, _ := c.FormFile("file")

        // 获取原始文件字节 - 没有读取器方法
	    // openedFile, _ := formFile.Open()
	    // file, _ := ioutil.ReadAll(openedFile)

        // 上传到磁盘
        // `formFile`有io.reader方法
        // c.SaveUploadedFile(formFile, path)
        // --------------------
		
		c.String(http.StatusOK, "Files uploaded")
	})

	// 监听并在0.0.0.0:8080上提供服务
	router.Run(":8080")
}

上传多个文件


客户端

HTML

<form action="/upload" method="POST" multiple="multiple">
  <input type="file" name="files">
  <input type="submit">
</form>

Curl

curl -X POST http://localhost:8080/upload \
  -F "files=../path-to-file/file1.zip" \
  -F "files=../path-to-file/file2.zip" \
  -H "Content-Type: multipart/form-data"

服务器端

Go

package main

import (
	"github.com/gin-gonic/gin"
    "log"
	"net/http"
	"io/ioutil"
)

type Form struct {
	Files []*multipart.FileHeader `form:"files" binding:"required"`
}

func main() {
	router := gin.Default()

    // 设置较低的内存限制以处理多部分表单(默认为32 MiB)
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB

	router.POST("/upload", func(c *gin.Context) {

        // 使用`ShouldBind`
        // --------------------
		var form Form
        _ := c.ShouldBind(&form)

        // for _, formFile := range form.Files {

          // 获取原始文件字节 - 没有读取器方法
	      // openedFile, _ := formFile.Open()
	      // file, _ := ioutil.ReadAll(openedFile)

          // 上传到磁盘
          // `formFile`有io.reader方法
          // c.SaveUploadedFile(formFile, path)

        // }
        // --------------------

        // 使用`MultipartForm`
        // --------------------
        // form, _ := c.MultipartForm()
        // formFiles, _ := form["files[]"]

        // for _, formFile := range formFiles {

          // 获取原始文件字节 - 没有读取器方法
	      // openedFile, _ := formFile.Open()
	      // file, _ := ioutil.ReadAll(openedFile)

          // 上传到磁盘
          // `formFile`有io.reader方法
          // c.SaveUploadedFile(formFile, path)

        // }
        // --------------------
		
		c.String(http.StatusOK, "Files uploaded")
	})

	// 监听并在0.0.0.0:8080上提供服务
	router.Run(":8080")
}
英文:

Using Gin to get an uploaded file

I think your question is "Using Gin, how do I get an uploaded file?". Most developers don't upload files with JSON encoding, which could be done but requires the file to be included as a base64 string (and increases the file size about 33%).

The common (and more efficient) practice is to upload the file using the "multipart/form-data" encoding. The code that others provided file, header, err := c.Request.FormFile(&quot;file&quot;) works, but that hijacks the underlining "net/http" package that Gin extends.

My recommendation is to use ShouldBind, but you can also use the FormFile or MultipartForm methods provided by the Gin package.

Examples below. Similar (but less detailed) explanations are also offered on the Gin page https://github.com/gin-gonic/gin#model-binding-and-validation and https://github.com/gin-gonic/gin#upload-files.


Upload one file


Clients

HTML

&lt;form action=&quot;/upload&quot; method=&quot;POST&quot;&gt;
  &lt;input type=&quot;file&quot; name=&quot;file&quot;&gt;
  &lt;input type=&quot;submit&quot;&gt;
&lt;/form&gt;

Curl

curl -X POST http://localhost:8080/upload \
  -F &quot;file=../path-to-file/file.zip&quot; \
  -H &quot;Content-Type: multipart/form-data&quot;

Server

Go

package main

import (
	&quot;github.com/gin-gonic/gin&quot;
    &quot;log&quot;
	&quot;net/http&quot;
	&quot;io/ioutil&quot;
)

type Form struct {
	File *multipart.FileHeader `form:&quot;file&quot; binding:&quot;required&quot;`
}

func main() {
	router := gin.Default()

    // Set a lower memory limit for multipart forms (default is 32 MiB)
    // router.MaxMultipartMemory = 8 &lt;&lt; 20  // 8 MiB

	router.POST(&quot;/upload&quot;, func(c *gin.Context) {

        // Using `ShouldBind`
        // --------------------
		var form Form
        _ := c.ShouldBind(&amp;form)

        // Get raw file bytes - no reader method
	    // openedFile, _ := form.File.Open()
	    // file, _ := ioutil.ReadAll(openedFile)

        // Upload to disk
        // `form.File` has io.reader method
        // c.SaveUploadedFile(form.File, path)
        // --------------------

        // Using `FormFile`
        // --------------------
        // formFile, _ := c.FormFile(&quot;file&quot;)

        // Get raw file bytes - no reader method
	    // openedFile, _ := formFile.Open()
	    // file, _ := ioutil.ReadAll(openedFile)

        // Upload to disk
        // `formFile` has io.reader method
        // c.SaveUploadedFile(formFile, path)
        // --------------------
		
		c.String(http.StatusOK, &quot;Files uploaded&quot;)
	})

	// Listen and serve on 0.0.0.0:8080
	router.Run(&quot;:8080&quot;)
}

Upload multiple files


Clients

HTML

&lt;form action=&quot;/upload&quot; method=&quot;POST&quot; multiple=&quot;multiple&quot;&gt;
  &lt;input type=&quot;file&quot; name=&quot;files&quot;&gt;
  &lt;input type=&quot;submit&quot;&gt;
&lt;/form&gt;

Curl

curl -X POST http://localhost:8080/upload \
  -F &quot;files=../path-to-file/file1.zip&quot; \
  -F &quot;files=../path-to-file/file2.zip&quot; \
  -H &quot;Content-Type: multipart/form-data&quot;

Server

Go

package main

import (
	&quot;github.com/gin-gonic/gin&quot;
    &quot;log&quot;
	&quot;net/http&quot;
	&quot;io/ioutil&quot;
)

type Form struct {
	Files []*multipart.FileHeader `form:&quot;files&quot; binding:&quot;required&quot;`
}

func main() {
	router := gin.Default()

    // Set a lower memory limit for multipart forms (default is 32 MiB)
    // router.MaxMultipartMemory = 8 &lt;&lt; 20  // 8 MiB

	router.POST(&quot;/upload&quot;, func(c *gin.Context) {

        // Using `ShouldBind`
        // --------------------
		var form Form
        _ := c.ShouldBind(&amp;form)

        // for _, formFile := range form.Files {

          // Get raw file bytes - no reader method
	      // openedFile, _ := formFile.Open()
	      // file, _ := ioutil.ReadAll(openedFile)

          // Upload to disk
          // `formFile` has io.reader method
          // c.SaveUploadedFile(formFile, path)

        // }
        // --------------------

        // Using `MultipartForm`
        // --------------------
        // form, _ := c.MultipartForm()
        // formFiles, _ := form[&quot;files[]&quot;]

        // for _, formFile := range formFiles {

          // Get raw file bytes - no reader method
	      // openedFile, _ := formFile.Open()
	      // file, _ := ioutil.ReadAll(openedFile)

          // Upload to disk
          // `formFile` has io.reader method
          // c.SaveUploadedFile(formFile, path)

        // }
        // --------------------
		
		c.String(http.StatusOK, &quot;Files uploaded&quot;)
	})

	// Listen and serve on 0.0.0.0:8080
	router.Run(&quot;:8080&quot;)
}

答案2

得分: 2

在你的代码中,你使用了var json Article,其中类型Article被定义为:

type Article struct {
    Title string `json:"title"`
    Body  string `json:"body"`
    File  []byte `json:"file"`
}

File的类型是[]bytebyte类型除了保存它所包含的内容外,不返回任何其他信息。

你的Article.Filer.FormFile不同,其中FormFile是一个返回3个项目的方法。

所以file, header, err := json.File不等同于file, header, err := r.FormFile("foo")

请参考godocs中的实现和方法描述-> 这里

英文:

In your code you say var json Article where type article is defined as

type Article struct {
	Title   string `json:&quot;title&quot;`
	Body string `json:&quot;body&quot;`
	File    []byte `json:&quot;file&quot;`
}

And File is of type []byte. Type byte doesn't return anything other than what it holds

Your Article.File is not the same as r.FormFile where FormFile is a method that returns 3 items

So file, header, err := json.File isn't file, header, err := r.FormFile(&quot;foo&quot;)

See the implementation and method description from godocs -> here

huangapple
  • 本文由 发表于 2017年7月16日 02:28:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/45121457.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定