| @@ -2,6 +2,7 @@ package sdks | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "crypto/sha256" | |||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "mime" | "mime" | ||||
| @@ -12,6 +13,7 @@ import ( | |||||
| "github.com/google/go-querystring/query" | "github.com/google/go-querystring/query" | ||||
| "gitlink.org.cn/cloudream/common/consts/errorcode" | "gitlink.org.cn/cloudream/common/consts/errorcode" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/iterator" | |||||
| "gitlink.org.cn/cloudream/common/utils/http2" | "gitlink.org.cn/cloudream/common/utils/http2" | ||||
| "gitlink.org.cn/cloudream/common/utils/io2" | "gitlink.org.cn/cloudream/common/utils/io2" | ||||
| "gitlink.org.cn/cloudream/common/utils/serder" | "gitlink.org.cn/cloudream/common/utils/serder" | ||||
| @@ -86,6 +88,8 @@ type RequestBody interface { | |||||
| Length() int64 | Length() int64 | ||||
| // 将内部值变成一个流,用于发送请求 | // 将内部值变成一个流,用于发送请求 | ||||
| IntoStream() io.ReadCloser | IntoStream() io.ReadCloser | ||||
| // 计算Sha256哈希,如果不方便计算,则返回nil | |||||
| Hash() []byte | |||||
| } | } | ||||
| type StringBody struct { | type StringBody struct { | ||||
| @@ -100,6 +104,11 @@ func (s *StringBody) IntoStream() io.ReadCloser { | |||||
| return io.NopCloser(bytes.NewReader([]byte(s.Value))) | return io.NopCloser(bytes.NewReader([]byte(s.Value))) | ||||
| } | } | ||||
| func (s *StringBody) Hash() []byte { | |||||
| hash := sha256.Sum256([]byte(s.Value)) | |||||
| return hash[:] | |||||
| } | |||||
| type BytesBody struct { | type BytesBody struct { | ||||
| Value []byte | Value []byte | ||||
| } | } | ||||
| @@ -112,6 +121,11 @@ func (b *BytesBody) IntoStream() io.ReadCloser { | |||||
| return io.NopCloser(bytes.NewReader(b.Value)) | return io.NopCloser(bytes.NewReader(b.Value)) | ||||
| } | } | ||||
| func (b *BytesBody) Hash() []byte { | |||||
| hash := sha256.Sum256(b.Value) | |||||
| return hash[:] | |||||
| } | |||||
| type StreamBody struct { | type StreamBody struct { | ||||
| Stream io.ReadCloser | Stream io.ReadCloser | ||||
| LengthHint int64 // 长度提示,如果长度未知,可以设置为-1 | LengthHint int64 // 长度提示,如果长度未知,可以设置为-1 | ||||
| @@ -125,6 +139,10 @@ func (s *StreamBody) IntoStream() io.ReadCloser { | |||||
| return s.Stream | return s.Stream | ||||
| } | } | ||||
| func (s *StreamBody) Hash() []byte { | |||||
| return nil | |||||
| } | |||||
| type APIRequest interface { | type APIRequest interface { | ||||
| MakeParam() *RequestParam | MakeParam() *RequestParam | ||||
| } | } | ||||
| @@ -157,7 +175,12 @@ func MakeQueryParam(method string, path string, q any) *RequestParam { | |||||
| } | } | ||||
| } | } | ||||
| func MakeMultipartParam(method string, path string, info any, stream io.ReadCloser) *RequestParam { | |||||
| type UploadFileInfo struct { | |||||
| FileName string | |||||
| File io.ReadCloser | |||||
| } | |||||
| func MakeUploadParam(method string, path string, info any, file UploadFileInfo) *RequestParam { | |||||
| data, err := serder.ObjectToJSONEx(info) | data, err := serder.ObjectToJSONEx(info) | ||||
| if err != nil { | if err != nil { | ||||
| // 开发人员应该保证param是可序列化的 | // 开发人员应该保证param是可序列化的 | ||||
| @@ -168,19 +191,20 @@ func MakeMultipartParam(method string, path string, info any, stream io.ReadClos | |||||
| mw := multipart.NewWriter(pw) | mw := multipart.NewWriter(pw) | ||||
| go func() { | go func() { | ||||
| defer mw.Close() | defer mw.Close() | ||||
| err := mw.WriteField("info", string(data)) | err := mw.WriteField("info", string(data)) | ||||
| if err != nil { | if err != nil { | ||||
| pw.CloseWithError(err) | pw.CloseWithError(err) | ||||
| return | return | ||||
| } | } | ||||
| fw, err := mw.CreateFormFile("file", "file") | |||||
| fw, err := mw.CreateFormFile("file", file.FileName) | |||||
| if err != nil { | if err != nil { | ||||
| pw.CloseWithError(err) | pw.CloseWithError(err) | ||||
| return | return | ||||
| } | } | ||||
| _, err = io.Copy(fw, stream) | |||||
| _, err = io.Copy(fw, file.File) | |||||
| if err != nil { | if err != nil { | ||||
| pw.CloseWithError(err) | pw.CloseWithError(err) | ||||
| return | return | ||||
| @@ -201,6 +225,68 @@ func MakeMultipartParam(method string, path string, info any, stream io.ReadClos | |||||
| } | } | ||||
| } | } | ||||
| type MultiUploadIter = iterator.Iterator[UploadFileInfo] | |||||
| func MakeMultiUploadParam(method string, path string, info any, files MultiUploadIter) *RequestParam { | |||||
| data, err := serder.ObjectToJSONEx(info) | |||||
| if err != nil { | |||||
| // 开发人员应该保证param是可序列化的 | |||||
| panic(err) | |||||
| } | |||||
| pr, pw := io.Pipe() | |||||
| mw := multipart.NewWriter(pw) | |||||
| go func() { | |||||
| defer mw.Close() | |||||
| err := mw.WriteField("info", string(data)) | |||||
| if err != nil { | |||||
| pw.CloseWithError(err) | |||||
| return | |||||
| } | |||||
| for { | |||||
| file, err := files.MoveNext() | |||||
| if err == iterator.ErrNoMoreItem { | |||||
| break | |||||
| } | |||||
| if err != nil { | |||||
| pw.CloseWithError(fmt.Errorf("opening file: %w", err)) | |||||
| return | |||||
| } | |||||
| w, err := mw.CreateFormFile("files", url.PathEscape(file.FileName)) | |||||
| if err != nil { | |||||
| file.File.Close() | |||||
| pw.CloseWithError(fmt.Errorf("create form file failed, err: %w", err)) | |||||
| return | |||||
| } | |||||
| _, err = io.Copy(w, file.File) | |||||
| if err != nil { | |||||
| file.File.Close() | |||||
| pw.CloseWithError(err) | |||||
| return | |||||
| } | |||||
| file.File.Close() | |||||
| } | |||||
| }() | |||||
| headers := http.Header{} | |||||
| headers.Set("Content-Type", fmt.Sprintf("%s;boundary=%s", http2.ContentTypeMultiPart, mw.Boundary())) | |||||
| return &RequestParam{ | |||||
| Method: method, | |||||
| Path: path, | |||||
| Header: headers, | |||||
| Body: &StreamBody{ | |||||
| Stream: pr, | |||||
| LengthHint: -1, | |||||
| }, | |||||
| } | |||||
| } | |||||
| type APIResponse interface { | type APIResponse interface { | ||||
| ParseResponse(resp *http.Response) error | ParseResponse(resp *http.Response) error | ||||
| } | } | ||||