go 图片上传:文件和Base64方式
参考:https://www.sobyte.net/post/2022-03/go-multipart-form-data/
网上找了好些教程,go客户端上传文件这一块,大部分的例子都是从文件读取然后上传的,但是项目中因为调用了第三方SDK,返回的是 base64 格式的图片,却没找到这方面的例子,特此分享一下解决方案,避免已经拿到 base64 图片数据,却还要写一遍文件上传这种低效的行为
。
文件上传
go客户端
client.go:
var (
filePath string
addr string
)
func init() {
flag.StringVar(&filePath, "file", "", "the file to upload")
flag.StringVar(&addr, "addr", "localhost:8080", "the addr of file server")
flag.Parse()
}
func main() {
if filePath == "" {
fmt.Println("file must not be empty")
return
}
err := doUpload(addr, filePath)
if err != nil {
fmt.Printf("upload file [%s] error: %s", filePath, err)
return
}
fmt.Printf("upload file [%s] ok\n", filePath)
}
func createReqBody(filePath string) (string, io.Reader, error) {
var err error
buf := new(bytes.Buffer)
bw := multipart.NewWriter(buf) // body writer
f, err := os.Open(filePath)
if err != nil {
return "", nil, err
}
defer f.Close()
// text part1
p1w, _ := bw.CreateFormField("name")
p1w.Write([]byte("Tony Bai"))
// text part2
p2w, _ := bw.CreateFormField("age")
p2w.Write([]byte("15"))
// file part1
_, fileName := filepath.Split(filePath)
fw1, _ := bw.CreateFormFile("file1", fileName)
io.Copy(fw1, f)
bw.Close() //write the tail boundry
return bw.FormDataContentType(), buf, nil
}
func doUpload(addr, filePath string) error {
// create body
contType, reader, err := createReqBody(filePath)
if err != nil {
return err
}
url := fmt.Sprintf("http://%s/upload", addr)
req, err := http.NewRequest("POST", url, reader)
// add headers
req.Header.Add("Content-Type", contType)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("request send error:", err)
return err
}
resp.Body.Close()
return nil
}
go服务端
server.go:
package main
import (
"fmt"
"io"
"net/http"
"os"
)
const uploadPath = "./upload"
func handleUploadFile(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(100)
mForm := r.MultipartForm
for k, _ := range mForm.File {
// k is the key of file part
file, fileHeader, err := r.FormFile(k)
if err != nil {
fmt.Println("inovke FormFile error:", err)
return
}
defer file.Close()
fmt.Printf("the uploaded file: name[%s], size[%d], header[%#v]\n",
fileHeader.Filename, fileHeader.Size, fileHeader.Header)
// store uploaded file into local path
localFileName := uploadPath + "/" + fileHeader.Filename
out, err := os.Create(localFileName)
if err != nil {
fmt.Printf("failed to open the file %s for writing", localFileName)
return
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
fmt.Printf("copy file err:%s\n", err)
return
}
fmt.Printf("file %s uploaded ok\n", fileHeader.Filename)
}
}
func main() {
http.HandleFunc("/upload", handleUploadFile)
http.ListenAndServe(":8080", nil)
}
Base64从内存上传
如果不从文件读取,而是直接 Base64解码(假设调用了别人的SDK),从内存中上传应该怎么实现呢?
把这个地方由:
func createReqBody(filePath string) (string, io.Reader, error) {
// ...
fw1, _ := bw.CreateFormFile("file1", fileName)
io.Copy(fw1, f)
// ...
}
改成:
func (b botUi) createReqBody(reader io.Reader) (string, *bytes.Buffer, error) {
// ...
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
escapeQuotes("image"), escapeQuotes("image.jpeg")))
h.Set("Content-Type", "image/jpeg")
// w, err := writer.CreateFormFile("image", "image.jpeg")
w, err := writer.CreatePart(h)
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
reader 也就是从内存读取图片的流,解码base64时获得:
func DecodePng(base64Png string) (*bytes.Buffer, error) {
out := bytes.NewBuffer([]byte{})
unBased, err := base64.StdEncoding.DecodeString(base64Png)
if err != nil {
return nil, err
}
im, err := png.Decode(bytes.NewReader(unBased))
if err != nil {
return nil, err
}
if err = png.Encode(out, im); err != nil {
return nil, err
}
return out, nil
}
即 *bytes.Buffer
,这个类型实现了 Read()
接口。
参考: