You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

object.go 17 kB

7 months ago
7 months ago
7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. package api
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "mime"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "time"
  11. v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
  12. "github.com/aws/aws-sdk-go-v2/credentials"
  13. "gitlink.org.cn/cloudream/common/consts/errorcode"
  14. "gitlink.org.cn/cloudream/common/pkgs/iterator"
  15. "gitlink.org.cn/cloudream/common/sdks"
  16. "gitlink.org.cn/cloudream/common/utils/http2"
  17. "gitlink.org.cn/cloudream/common/utils/serder"
  18. "gitlink.org.cn/cloudream/jcs-pub/client/types"
  19. )
  20. type ObjectService struct {
  21. *Client
  22. }
  23. func (c *Client) Object() *ObjectService {
  24. return &ObjectService{
  25. Client: c,
  26. }
  27. }
  28. const ObjectListPathByPath = "/object/listByPath"
  29. type ObjectListByPath struct {
  30. PackageID types.PackageID `form:"packageID" binding:"required" url:"packageID" json:"packageID"`
  31. Path string `form:"path" url:"path" json:"path"` // 允许为空字符串
  32. IsPrefix bool `form:"isPrefix" url:"isPrefix" json:"isPrefix"`
  33. NoRecursive bool `form:"noRecursive" url:"noRecursive" json:"noRecursive"` // 仅当isPrefix为true时有效,表示仅查询直接属于Prefix下的对象,对于更深的对象,返回它们的公共前缀
  34. MaxKeys int `form:"maxKeys" url:"maxKeys" json:"maxKeys"`
  35. ContinuationToken string `form:"continuationToken" url:"continuationToken" json:"continuationToken"` // 用于分页,如果为空字符串,表示从头开始
  36. }
  37. func (r *ObjectListByPath) MakeParam() *sdks.RequestParam {
  38. return sdks.MakeQueryParam(http.MethodGet, ObjectListPathByPath, r)
  39. }
  40. type ObjectListByPathResp struct {
  41. CommonPrefixes []string `json:"commonPrefixes"` // 仅在IsPrefix为true且NoRecursive为true时有效,包含更深层对象的shared prefix
  42. Objects []types.Object `json:"objects"` // 如果IsPrefix为true且NoRecursive为false,则返回所有匹配的对象,否则只返回直接属于Prefix下的对象
  43. IsTruncated bool `json:"isTruncated"` // 是否还有更多对象
  44. NextContinuationToken string `json:"nextContinuationToken"` // 用于分页,如果IsTruncated为true,则下次请求的ContinuationToken为该值
  45. }
  46. func (r *ObjectListByPathResp) ParseResponse(resp *http.Response) error {
  47. return sdks.ParseCodeDataJSONResponse(resp, r)
  48. }
  49. func (c *ObjectService) ListByPath(req ObjectListByPath) (*ObjectListByPathResp, error) {
  50. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectListByPathResp{})
  51. }
  52. const ObjectListByIDsPath = "/object/listByIDs"
  53. type ObjectListByIDs struct {
  54. ObjectIDs []types.ObjectID `form:"objectIDs" binding:"required" url:"objectIDs"`
  55. }
  56. func (r *ObjectListByIDs) MakeParam() *sdks.RequestParam {
  57. return sdks.MakeQueryParam(http.MethodGet, ObjectListByIDsPath, r)
  58. }
  59. type ObjectListByIDsResp struct {
  60. Objects []*types.Object `json:"object"` // 与ObjectIDs一一对应,如果某个ID不存在,则对应位置为nil
  61. }
  62. func (r *ObjectListByIDsResp) ParseResponse(resp *http.Response) error {
  63. return sdks.ParseCodeDataJSONResponse(resp, r)
  64. }
  65. func (c *ObjectService) ListByIDs(req ObjectListByIDs) (*ObjectListByIDsResp, error) {
  66. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectListByIDsResp{})
  67. }
  68. const ObjectUploadPath = "/object/upload"
  69. type ObjectUpload struct {
  70. ObjectUploadInfo
  71. Files UploadObjectIterator `json:"-"`
  72. }
  73. type ObjectUploadInfo struct {
  74. PackageID types.PackageID `json:"packageID" binding:"required"`
  75. Affinity types.UserSpaceID `json:"affinity"`
  76. CopyTo []types.UserSpaceID `json:"copyTo"`
  77. CopyToPath []string `json:"copyToPath"`
  78. }
  79. type UploadingObject struct {
  80. Path string
  81. File io.ReadCloser
  82. }
  83. type UploadObjectIterator = iterator.Iterator[*UploadingObject]
  84. type ObjectUploadResp struct {
  85. Uploadeds []types.Object `json:"uploadeds"`
  86. }
  87. func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) {
  88. type uploadInfo struct {
  89. Info string `url:"info"`
  90. }
  91. url, err := url.JoinPath(c.cfg.URL, ObjectUploadPath)
  92. if err != nil {
  93. return nil, err
  94. }
  95. infoJSON, err := serder.ObjectToJSON(req)
  96. if err != nil {
  97. return nil, fmt.Errorf("upload info to json: %w", err)
  98. }
  99. resp, err := PostMultiPart(c.cfg, url,
  100. uploadInfo{Info: string(infoJSON)},
  101. iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) {
  102. return &http2.IterMultiPartFile{
  103. FieldName: "files",
  104. FileName: src.Path,
  105. File: src.File,
  106. }, nil
  107. }))
  108. if err != nil {
  109. return nil, err
  110. }
  111. contType := resp.Header.Get("Content-Type")
  112. if strings.Contains(contType, http2.ContentTypeJSON) {
  113. var err error
  114. var codeResp response[ObjectUploadResp]
  115. if codeResp, err = serder.JSONToObjectStreamEx[response[ObjectUploadResp]](resp.Body); err != nil {
  116. return nil, fmt.Errorf("parsing response: %w", err)
  117. }
  118. if codeResp.Code == errorcode.OK {
  119. return &codeResp.Data, nil
  120. }
  121. return nil, codeResp.ToError()
  122. }
  123. return nil, fmt.Errorf("unknow response content type: %s", contType)
  124. }
  125. const ObjectDownloadPath = "/object/download"
  126. type ObjectDownload struct {
  127. ObjectID types.ObjectID `form:"objectID" url:"objectID" binding:"required"`
  128. Offset int64 `form:"offset" url:"offset,omitempty"`
  129. Length *int64 `form:"length" url:"length,omitempty"`
  130. }
  131. func (r *ObjectDownload) MakeParam() *sdks.RequestParam {
  132. return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadPath, r)
  133. }
  134. type DownloadingObject struct {
  135. Path string
  136. File io.ReadCloser
  137. }
  138. func (c *ObjectService) Download(req ObjectDownload) (*DownloadingObject, error) {
  139. httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL)
  140. if err != nil {
  141. return nil, err
  142. }
  143. if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" {
  144. prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "")
  145. cred, err := prod.Retrieve(context.TODO())
  146. if err != nil {
  147. return nil, err
  148. }
  149. signer := v4.NewSigner()
  150. err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now())
  151. if err != nil {
  152. return nil, err
  153. }
  154. }
  155. resp, err := http.DefaultClient.Do(httpReq)
  156. if err != nil {
  157. return nil, err
  158. }
  159. contType := resp.Header.Get("Content-Type")
  160. if strings.Contains(contType, http2.ContentTypeJSON) {
  161. var codeResp response[any]
  162. if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil {
  163. return nil, fmt.Errorf("parsing response: %w", err)
  164. }
  165. return nil, codeResp.ToError()
  166. }
  167. _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
  168. if err != nil {
  169. return nil, fmt.Errorf("parsing content disposition: %w", err)
  170. }
  171. return &DownloadingObject{
  172. Path: params["filename"],
  173. File: resp.Body,
  174. }, nil
  175. }
  176. const ObjectDownloadByPathPath = "/object/downloadByPath"
  177. type ObjectDownloadByPath struct {
  178. PackageID types.PackageID `form:"packageID" url:"packageID" binding:"required"`
  179. Path string `form:"path" url:"path" binding:"required"`
  180. Offset int64 `form:"offset" url:"offset,omitempty"`
  181. Length *int64 `form:"length" url:"length,omitempty"`
  182. }
  183. func (r *ObjectDownloadByPath) MakeParam() *sdks.RequestParam {
  184. return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadByPathPath, r)
  185. }
  186. func (c *ObjectService) DownloadByPath(req ObjectDownloadByPath) (*DownloadingObject, error) {
  187. httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL)
  188. if err != nil {
  189. return nil, err
  190. }
  191. if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" {
  192. prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "")
  193. cred, err := prod.Retrieve(context.TODO())
  194. if err != nil {
  195. return nil, err
  196. }
  197. signer := v4.NewSigner()
  198. err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now())
  199. if err != nil {
  200. return nil, err
  201. }
  202. }
  203. resp, err := http.DefaultClient.Do(httpReq)
  204. if err != nil {
  205. return nil, err
  206. }
  207. contType := resp.Header.Get("Content-Type")
  208. if strings.Contains(contType, http2.ContentTypeJSON) {
  209. var codeResp response[any]
  210. if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil {
  211. return nil, fmt.Errorf("parsing response: %w", err)
  212. }
  213. return nil, codeResp.ToError()
  214. }
  215. _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
  216. if err != nil {
  217. return nil, fmt.Errorf("parsing content disposition: %w", err)
  218. }
  219. return &DownloadingObject{
  220. Path: params["filename"],
  221. File: resp.Body,
  222. }, nil
  223. }
  224. const ObjectUpdateInfoPath = "/object/updateInfo"
  225. type UpdatingObject struct {
  226. ObjectID types.ObjectID `json:"objectID" binding:"required"`
  227. UpdateTime time.Time `json:"updateTime" binding:"required"`
  228. }
  229. func (u *UpdatingObject) ApplyTo(obj *types.Object) {
  230. obj.UpdateTime = u.UpdateTime
  231. }
  232. type ObjectUpdateInfo struct {
  233. Updatings []UpdatingObject `json:"updatings" binding:"required"`
  234. }
  235. func (r *ObjectUpdateInfo) MakeParam() *sdks.RequestParam {
  236. return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoPath, r)
  237. }
  238. type ObjectUpdateInfoResp struct {
  239. Successes []types.ObjectID `json:"successes"`
  240. }
  241. func (r *ObjectUpdateInfoResp) ParseResponse(resp *http.Response) error {
  242. return sdks.ParseCodeDataJSONResponse(resp, r)
  243. }
  244. func (c *ObjectService) UpdateInfo(req ObjectUpdateInfo) (*ObjectUpdateInfoResp, error) {
  245. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectUpdateInfoResp{})
  246. }
  247. const ObjectUpdateInfoByPathPath = "/object/updateInfoByPath"
  248. type ObjectUpdateInfoByPath struct {
  249. PackageID types.PackageID `json:"packageID" binding:"required"`
  250. Path string `json:"path" binding:"required"`
  251. UpdateTime time.Time `json:"updateTime" binding:"required"`
  252. }
  253. func (r *ObjectUpdateInfoByPath) MakeParam() *sdks.RequestParam {
  254. return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoByPathPath, r)
  255. }
  256. type ObjectUpdateInfoByPathResp struct{}
  257. func (r *ObjectUpdateInfoByPathResp) ParseResponse(resp *http.Response) error {
  258. return sdks.ParseCodeDataJSONResponse(resp, r)
  259. }
  260. func (c *ObjectService) UpdateInfoByPath(req ObjectUpdateInfoByPath) (*ObjectUpdateInfoByPathResp, error) {
  261. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectUpdateInfoByPathResp{})
  262. }
  263. const ObjectMovePath = "/object/move"
  264. type MovingObject struct {
  265. ObjectID types.ObjectID `json:"objectID" binding:"required"`
  266. PackageID types.PackageID `json:"packageID" binding:"required"`
  267. Path string `json:"path" binding:"required"`
  268. }
  269. func (m *MovingObject) ApplyTo(obj *types.Object) {
  270. obj.PackageID = m.PackageID
  271. obj.Path = m.Path
  272. }
  273. type ObjectMove struct {
  274. Movings []MovingObject `json:"movings" binding:"required"`
  275. }
  276. func (r *ObjectMove) MakeParam() *sdks.RequestParam {
  277. return sdks.MakeJSONParam(http.MethodPost, ObjectMovePath, r)
  278. }
  279. type ObjectMoveResp struct {
  280. Successes []types.ObjectID `json:"successes"`
  281. }
  282. func (r *ObjectMoveResp) ParseResponse(resp *http.Response) error {
  283. return sdks.ParseCodeDataJSONResponse(resp, r)
  284. }
  285. func (c *ObjectService) Move(req ObjectMove) (*ObjectMoveResp, error) {
  286. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectMoveResp{})
  287. }
  288. const ObjectDeletePath = "/object/delete"
  289. type ObjectDelete struct {
  290. ObjectIDs []types.ObjectID `json:"objectIDs" binding:"required"`
  291. }
  292. func (r *ObjectDelete) MakeParam() *sdks.RequestParam {
  293. return sdks.MakeJSONParam(http.MethodPost, ObjectDeletePath, r)
  294. }
  295. type ObjectDeleteResp struct{}
  296. func (r *ObjectDeleteResp) ParseResponse(resp *http.Response) error {
  297. return sdks.ParseCodeDataJSONResponse(resp, r)
  298. }
  299. func (c *ObjectService) Delete(req ObjectDelete) error {
  300. return JSONAPINoData(c.cfg, http.DefaultClient, &req)
  301. }
  302. const ObjectDeleteByPathPath = "/object/deleteByPath"
  303. type ObjectDeleteByPath struct {
  304. PackageID types.PackageID `json:"packageID" binding:"required"`
  305. Path string `json:"path" binding:"required"`
  306. }
  307. func (r *ObjectDeleteByPath) MakeParam() *sdks.RequestParam {
  308. return sdks.MakeJSONParam(http.MethodPost, ObjectDeleteByPathPath, r)
  309. }
  310. type ObjectDeleteByPathResp struct{}
  311. func (r *ObjectDeleteByPathResp) ParseResponse(resp *http.Response) error {
  312. return sdks.ParseCodeDataJSONResponse(resp, r)
  313. }
  314. func (c *ObjectService) DeleteByPath(req ObjectDeleteByPath) error {
  315. return JSONAPINoData(c.cfg, http.DefaultClient, &req)
  316. }
  317. const ObjectClonePath = "/object/clone"
  318. type ObjectClone struct {
  319. Clonings []CloningObject `json:"clonings" binding:"required"`
  320. }
  321. func (r *ObjectClone) MakeParam() *sdks.RequestParam {
  322. return sdks.MakeJSONParam(http.MethodPost, ObjectClonePath, r)
  323. }
  324. type CloningObject struct {
  325. ObjectID types.ObjectID `json:"objectID" binding:"required"`
  326. NewPath string `json:"newPath" binding:"required"`
  327. NewPackageID types.PackageID `json:"newPackageID" binding:"required"`
  328. }
  329. type ObjectCloneResp struct {
  330. Objects []*types.Object `json:"objects"`
  331. }
  332. func (r *ObjectCloneResp) ParseResponse(resp *http.Response) error {
  333. return sdks.ParseCodeDataJSONResponse(resp, r)
  334. }
  335. func (c *ObjectService) Clone(req ObjectClone) (*ObjectCloneResp, error) {
  336. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectCloneResp{})
  337. }
  338. const ObjectGetPackageObjectsPath = "/object/getPackageObjects"
  339. type ObjectGetPackageObjects struct {
  340. PackageID types.PackageID `form:"packageID" url:"packageID" binding:"required"`
  341. }
  342. func (r *ObjectGetPackageObjects) MakeParam() *sdks.RequestParam {
  343. return sdks.MakeQueryParam(http.MethodGet, ObjectGetPackageObjectsPath, r)
  344. }
  345. type ObjectGetPackageObjectsResp struct {
  346. Objects []types.Object `json:"objects"`
  347. }
  348. func (r *ObjectGetPackageObjectsResp) ParseResponse(resp *http.Response) error {
  349. return sdks.ParseCodeDataJSONResponse(resp, r)
  350. }
  351. func (c *ObjectService) GetPackageObjects(req ObjectGetPackageObjects) (*ObjectGetPackageObjectsResp, error) {
  352. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectGetPackageObjectsResp{})
  353. }
  354. const ObjectNewMultipartUploadPath = "/v1/object/newMultipartUpload"
  355. type ObjectNewMultipartUpload struct {
  356. PackageID types.PackageID `json:"packageID" binding:"required"`
  357. Path string `json:"path" binding:"required"`
  358. }
  359. func (r *ObjectNewMultipartUpload) MakeParam() *sdks.RequestParam {
  360. return sdks.MakeJSONParam(http.MethodPost, ObjectNewMultipartUploadPath, r)
  361. }
  362. type ObjectNewMultipartUploadResp struct {
  363. Object types.Object `json:"object"`
  364. }
  365. func (r *ObjectNewMultipartUploadResp) ParseResponse(resp *http.Response) error {
  366. return sdks.ParseCodeDataJSONResponse(resp, r)
  367. }
  368. func (c *ObjectService) NewMultipartUpload(req ObjectNewMultipartUpload) (*ObjectNewMultipartUploadResp, error) {
  369. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectNewMultipartUploadResp{})
  370. }
  371. const ObjectUploadPartPath = "/v1/object/uploadPart"
  372. type ObjectUploadPart struct {
  373. ObjectUploadPartInfo
  374. File io.ReadCloser `json:"-"`
  375. }
  376. type ObjectUploadPartInfo struct {
  377. ObjectID types.ObjectID `json:"objectID" binding:"required"`
  378. Index int `json:"index"`
  379. }
  380. type ObjectUploadPartResp struct{}
  381. func (c *ObjectService) UploadPart(req ObjectUploadPart) (*ObjectUploadPartResp, error) {
  382. url, err := url.JoinPath(c.cfg.URL, ObjectUploadPartPath)
  383. if err != nil {
  384. return nil, err
  385. }
  386. infoJSON, err := serder.ObjectToJSON(req)
  387. if err != nil {
  388. return nil, fmt.Errorf("upload info to json: %w", err)
  389. }
  390. resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{
  391. Form: map[string]string{"info": string(infoJSON)},
  392. Files: iterator.Array(&http2.IterMultiPartFile{
  393. FieldName: "file",
  394. File: req.File,
  395. }),
  396. })
  397. if err != nil {
  398. return nil, err
  399. }
  400. contType := resp.Header.Get("Content-Type")
  401. if strings.Contains(contType, http2.ContentTypeJSON) {
  402. var err error
  403. var codeResp response[ObjectUploadPartResp]
  404. if codeResp, err = serder.JSONToObjectStreamEx[response[ObjectUploadPartResp]](resp.Body); err != nil {
  405. return nil, fmt.Errorf("parsing response: %w", err)
  406. }
  407. if codeResp.Code == errorcode.OK {
  408. return &codeResp.Data, nil
  409. }
  410. return nil, codeResp.ToError()
  411. }
  412. return nil, fmt.Errorf("unknow response content type: %s", contType)
  413. }
  414. const ObjectCompleteMultipartUploadPath = "/v1/object/completeMultipartUpload"
  415. type ObjectCompleteMultipartUpload struct {
  416. ObjectID types.ObjectID `json:"objectID" binding:"required"`
  417. Indexes []int `json:"indexes" binding:"required"`
  418. }
  419. func (r *ObjectCompleteMultipartUpload) MakeParam() *sdks.RequestParam {
  420. return sdks.MakeJSONParam(http.MethodPost, ObjectCompleteMultipartUploadPath, r)
  421. }
  422. type ObjectCompleteMultipartUploadResp struct {
  423. Object types.Object `json:"object"`
  424. }
  425. func (r *ObjectCompleteMultipartUploadResp) ParseResponse(resp *http.Response) error {
  426. return sdks.ParseCodeDataJSONResponse(resp, r)
  427. }
  428. func (c *ObjectService) CompleteMultipartUpload(req ObjectCompleteMultipartUpload) (*ObjectCompleteMultipartUploadResp, error) {
  429. return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectCompleteMultipartUploadResp{})
  430. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。