| @@ -2,6 +2,7 @@ package sdks | |||
| import ( | |||
| "bytes" | |||
| "crypto/sha256" | |||
| "fmt" | |||
| "io" | |||
| "mime" | |||
| @@ -12,6 +13,7 @@ import ( | |||
| "github.com/google/go-querystring/query" | |||
| "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/io2" | |||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||
| @@ -86,6 +88,8 @@ type RequestBody interface { | |||
| Length() int64 | |||
| // 将内部值变成一个流,用于发送请求 | |||
| IntoStream() io.ReadCloser | |||
| // 计算Sha256哈希,如果不方便计算,则返回nil | |||
| Hash() []byte | |||
| } | |||
| type StringBody struct { | |||
| @@ -100,6 +104,11 @@ func (s *StringBody) IntoStream() io.ReadCloser { | |||
| return io.NopCloser(bytes.NewReader([]byte(s.Value))) | |||
| } | |||
| func (s *StringBody) Hash() []byte { | |||
| hash := sha256.Sum256([]byte(s.Value)) | |||
| return hash[:] | |||
| } | |||
| type BytesBody struct { | |||
| Value []byte | |||
| } | |||
| @@ -112,6 +121,11 @@ func (b *BytesBody) IntoStream() io.ReadCloser { | |||
| return io.NopCloser(bytes.NewReader(b.Value)) | |||
| } | |||
| func (b *BytesBody) Hash() []byte { | |||
| hash := sha256.Sum256(b.Value) | |||
| return hash[:] | |||
| } | |||
| type StreamBody struct { | |||
| Stream io.ReadCloser | |||
| LengthHint int64 // 长度提示,如果长度未知,可以设置为-1 | |||
| @@ -125,6 +139,10 @@ func (s *StreamBody) IntoStream() io.ReadCloser { | |||
| return s.Stream | |||
| } | |||
| func (s *StreamBody) Hash() []byte { | |||
| return nil | |||
| } | |||
| type APIRequest interface { | |||
| 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) | |||
| if err != nil { | |||
| // 开发人员应该保证param是可序列化的 | |||
| @@ -168,19 +191,20 @@ func MakeMultipartParam(method string, path string, info any, stream io.ReadClos | |||
| mw := multipart.NewWriter(pw) | |||
| go func() { | |||
| defer mw.Close() | |||
| err := mw.WriteField("info", string(data)) | |||
| if err != nil { | |||
| pw.CloseWithError(err) | |||
| return | |||
| } | |||
| fw, err := mw.CreateFormFile("file", "file") | |||
| fw, err := mw.CreateFormFile("file", file.FileName) | |||
| if err != nil { | |||
| pw.CloseWithError(err) | |||
| return | |||
| } | |||
| _, err = io.Copy(fw, stream) | |||
| _, err = io.Copy(fw, file.File) | |||
| if err != nil { | |||
| pw.CloseWithError(err) | |||
| 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 { | |||
| ParseResponse(resp *http.Response) error | |||
| } | |||