go-kratos / kratos

Your ultimate Go microservices framework for the cloud-native era.
https://go-kratos.dev
MIT License
23.34k stars 4.01k forks source link

这个框架如何做文件上传呢 #958

Closed dcsunny closed 3 years ago

dcsunny commented 3 years ago

有些业务需要用到文件上传接口,但是发现这个框架好像不支持? 如果支持应该如何使用

tonybase commented 3 years ago

可以参考下 examples/http 的例子:https://github.com/go-kratos/kratos/tree/main/examples/http

dcsunny commented 3 years ago

这个是router单独出来,那通过protobuf生成出来的接口呢?

dcsunny commented 3 years ago
// defaultRequestDecoder decodes the request body to object.
func defaultRequestDecoder(req *http.Request, v interface{}) error {
    subtype := httputil.ContentSubtype(req.Header.Get("Content-Type"))
    if codec := encoding.GetCodec(subtype); codec != nil {
        data, err := ioutil.ReadAll(req.Body)
        if err != nil {
            return errors.BadRequest("CODEC", err.Error())
        }
        if err := codec.Unmarshal(data, v); err != nil {
            return errors.BadRequest("CODEC", err.Error())
        }
    } else {
        if err := binding.BindForm(req, v); err != nil {
            return errors.BadRequest("CODEC", err.Error())
        }
    }
    return nil
}

就是这块好像不能将上传的文件转成需要的对象

tonybase commented 3 years ago

proto 目前不支持文档上传,可以考虑直接手写接口实现

Kevin-free commented 2 years ago

@dcsunny 你好,我现在也有这个困扰,请问你怎么解决的啊?能分享一下吗 谢谢!

shanezhiu commented 2 years ago

可以尝试chunk上传,需要前后端配合。

YaEvan commented 1 year ago

可以直接写接口, 不过具体代码组织需要自己解决

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"

    "github.com/go-kratos/kratos/v2/middleware/std"
    "github.com/go-kratos/kratos/v2/transport/http"
)

func main() {
    httpSrv: = http.NewServer(
        http.Address(":8000"),
        http.Middleware(std.ServerRecovery()),
    )

        httpSrv.Handle(http.MethodPost, "/upload", func(w http.ResponseWriter, r * http.Request) {
        // 设置文件大小限制
        r.ParseMultipartForm(32 << 20)
        file, header, err: = r.FormFile("file")
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        defer file.Close()

        // 检查文件大小
        if header.Size > (10 << 20) { // 限制文件大小为10MB
            http.Error(w, "file size exceeds the limit", http.StatusBadRequest)
            return
        }

        // 检查文件类型
        ext: = path.Ext(header.Filename)
        if ext != ".jpg" && ext != ".png" { // 限制文件类型为jpg和png
            http.Error(w, "unsupported file type", http.StatusBadRequest)
            return
        }

        // 创建一个新的文件
        dst, err: = os.Create(header.Filename)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer dst.Close()

        // 将上传的文件内容复制到目标文件中
        if _, err: = io.Copy(dst, file);
        err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // 返回成功响应
        fmt.Fprintln(w, "File uploaded successfully")
    })

    if err: = httpSrv.Start();err != nil {
        panic(err)
    }
}
func TestUploadFile(t *testing.T) {
    file, err := os.Open("testdata/test.jpg") // 测试文件路径
    if err != nil {
        t.Fatal(err)
    }
    defer file.Close()

    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile("file", filepath.Base(file.Name()))
    if err != nil {
        t.Fatal(err)
    }
    if _, err = io.Copy(part, file); err != nil {
        t.Fatal(err)
    }
    if err = writer.Close(); err != nil {
        t.Fatal(err)
    }

    req, err := http.NewRequest(http.MethodPost, "/upload", body)
    if err != nil {
        t.Fatal(err)
    }
    req.Header.Set("Content-Type", writer.FormDataContentType())

    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(uploadHandler)
    handler.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
    }

    // 检查文件是否成功上传到服务器
    _, err = os.Stat("test.jpg")
    if err != nil {
        t.Errorf("file not found on server")
    }
}
KafuuEriri commented 1 year ago
// UploadRequestDecoder CustomRequestDecoder 将用户上传的文件序列化后写入Request Body中
func UploadRequestDecoder() http.ServerOption {
    return http.RequestDecoder(func(r *http.Request, v interface{}) error {
        // 拦截所有上传请求
        if r.URL.Path != "/upload" {
            return nil
        }
        codec, ok := http.CodecForRequest(r, "Content-Type")
        if !ok {
            return errors.BadRequest("CODEC", fmt.Sprintf("unregister Content-Type: %s", r.Header.Get("Content-Type")))
        }
        file, header, err := r.FormFile("file")
        if err != nil {
            return errors.BadRequest("CODEC", err.Error())
        }
        if ext := path.Ext(header.Filename); ext != ".xlsx" { // 限制文件类型为xlsx
            return errors.BadRequest("CODEC", err.Error())
        }
        reqData, err := handleExcelAttachment(r, file)
        if err != nil {
            return err
        }
        // 序列化之后写入body
        var buf []byte
        defer func() {
            // 给 BODY 重新赋值
            r.Body = io.NopCloser(bytes.NewBuffer(buf))
        }()
        if err = codec.Unmarshal(buf, reqData); err != nil {
            return errors.BadRequest("CODEC", fmt.Sprintf("body unmarshal %s", err.Error()))
        }
        return nil
    })
}

试了一下这样写一个opt也获取不到文件数据,有大佬看看为啥吗