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.

pub_shards.go 14 kB

4 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. package http
  2. import (
  3. "compress/gzip"
  4. "fmt"
  5. "io"
  6. "mime"
  7. "mime/multipart"
  8. "net/http"
  9. "net/url"
  10. "github.com/gin-gonic/gin"
  11. "github.com/samber/lo"
  12. "gitlink.org.cn/cloudream/common/pkgs/logger"
  13. "gitlink.org.cn/cloudream/common/utils/http2"
  14. "gitlink.org.cn/cloudream/common/utils/serder"
  15. "gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
  16. "gitlink.org.cn/cloudream/jcs-pub/client/internal/http/types"
  17. cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1"
  18. "gitlink.org.cn/cloudream/jcs-pub/common/ecode"
  19. stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
  20. corrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/coordinator"
  21. hubrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/hub"
  22. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  23. "gorm.io/gorm"
  24. )
  25. type PubShardsService struct {
  26. *Server
  27. }
  28. func (s *Server) PubShards() *PubShardsService {
  29. return &PubShardsService{s}
  30. }
  31. func (s *PubShardsService) Create(ctx *gin.Context) {
  32. log := logger.WithField("HTTP", "PubShards.Create")
  33. req, err := types.ShouldBindJSONEx[cliapi.PubShardsCreate](ctx)
  34. if err != nil {
  35. log.Warnf("binding body: %s", err.Error())
  36. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "%v", err))
  37. return
  38. }
  39. if stgglb.StandaloneMode {
  40. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "client is not online"))
  41. return
  42. }
  43. corCli := stgglb.CoordinatorRPCPool.Get()
  44. defer corCli.Release()
  45. resp, cerr := corCli.CreatePubShards(ctx.Request.Context(), &corrpc.CreatePubShards{
  46. Password: req.Password,
  47. MasterHub: req.MasterHub,
  48. Name: req.Name,
  49. Storage: req.Storage,
  50. Credential: req.Credential,
  51. ShardStore: req.ShardStore,
  52. Features: req.Features,
  53. WorkingDir: jcstypes.PathFromJcsPathString(req.WorkingDir),
  54. })
  55. if cerr != nil {
  56. ctx.JSON(http.StatusOK, types.Failed(ecode.ErrorCode(cerr.Code), cerr.Message))
  57. return
  58. }
  59. ctx.JSON(http.StatusOK, types.OK(cliapi.PubShardsCreateResp{
  60. PubShards: resp.PubShardStore,
  61. }))
  62. }
  63. func (s *PubShardsService) Join(ctx *gin.Context) {
  64. log := logger.WithField("HTTP", "PubShards.Join")
  65. var req cliapi.PubShardsJoin
  66. if err := ctx.ShouldBindJSON(&req); err != nil {
  67. log.Warnf("binding body: %s", err.Error())
  68. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "%v", err))
  69. return
  70. }
  71. if stgglb.StandaloneMode {
  72. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "client is not online"))
  73. return
  74. }
  75. corCli := stgglb.CoordinatorRPCPool.Get()
  76. defer corCli.Release()
  77. resp, cerr := corCli.UserGetPubShards(ctx.Request.Context(), &corrpc.UserGetPubShards{
  78. PubShardsID: req.PubShardsID,
  79. Password: req.Password,
  80. })
  81. if cerr != nil {
  82. ctx.JSON(http.StatusOK, types.Failed(ecode.ErrorCode(cerr.Code), cerr.Message))
  83. return
  84. }
  85. resp2, cerr2 := s.svc.UserSpaceSvc().Create(cliapi.UserSpaceCreate{
  86. Name: req.Name,
  87. Storage: &jcstypes.PubShardsType{
  88. Type: "PubShards",
  89. Base: resp.PubShards.Storage,
  90. PubShardsID: req.PubShardsID,
  91. Password: req.Password,
  92. MasterHub: resp.MasterHub.HubID,
  93. },
  94. Credential: resp.PubShards.Credential,
  95. ShardStore: &resp.PubShards.ShardStore,
  96. Features: resp.PubShards.Features,
  97. WorkingDir: resp.PubShards.WorkingDir.PushNew("parts", fmt.Sprintf("%v", s.svc.AccToken.GetToken().UserID)).String(),
  98. })
  99. if cerr2 != nil {
  100. ctx.JSON(http.StatusOK, types.Failed(ecode.ErrorCode(cerr2.Code), cerr2.Message))
  101. return
  102. }
  103. ctx.JSON(http.StatusOK, types.OK(cliapi.PubShardsJoinResp{
  104. PubShards: resp.PubShards,
  105. UserSpace: resp2.UserSpace,
  106. }))
  107. }
  108. func (s *PubShardsService) List(ctx *gin.Context) {
  109. log := logger.WithField("HTTP", "PubShards.List")
  110. var req cliapi.PubShardsList
  111. if err := ctx.ShouldBindQuery(&req); err != nil {
  112. log.Warnf("binding query: %s", err.Error())
  113. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "%v", err))
  114. return
  115. }
  116. if stgglb.StandaloneMode {
  117. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "client is not online"))
  118. return
  119. }
  120. pubUss, err := s.svc.DB.UserSpace().GetAllPubShards(s.svc.DB.DefCtx())
  121. if err != nil {
  122. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get pub shards: %v", err))
  123. return
  124. }
  125. corCli := stgglb.CoordinatorRPCPool.Get()
  126. defer corCli.Release()
  127. var pubs []jcstypes.PubShards
  128. for _, us := range pubUss {
  129. pubType := us.Storage.(*jcstypes.PubShardsType)
  130. resp, cerr := corCli.UserGetPubShards(ctx.Request.Context(), &corrpc.UserGetPubShards{
  131. PubShardsID: pubType.PubShardsID,
  132. Password: pubType.Password,
  133. })
  134. if cerr != nil {
  135. ctx.JSON(http.StatusOK, types.Failed(ecode.ErrorCode(cerr.Code), cerr.Message))
  136. return
  137. }
  138. pubs = append(pubs, resp.PubShards)
  139. }
  140. ctx.JSON(http.StatusOK, types.OK(cliapi.PubShardsListResp{
  141. PubShards: pubs,
  142. UserSpaces: pubUss,
  143. }))
  144. }
  145. func (s *PubShardsService) Get(ctx *gin.Context) {
  146. log := logger.WithField("HTTP", "PubShards.Get")
  147. var req cliapi.PubShardsGet
  148. if err := ctx.ShouldBindQuery(&req); err != nil {
  149. log.Warnf("binding query: %s", err.Error())
  150. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "%v", err))
  151. return
  152. }
  153. if stgglb.StandaloneMode {
  154. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "client is not online"))
  155. return
  156. }
  157. pubUss, err := s.svc.DB.UserSpace().GetAllPubShards(s.svc.DB.DefCtx())
  158. if err != nil {
  159. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get pub shards: %v", err))
  160. return
  161. }
  162. corCli := stgglb.CoordinatorRPCPool.Get()
  163. defer corCli.Release()
  164. for i, us := range pubUss {
  165. pubType := us.Storage.(*jcstypes.PubShardsType)
  166. resp, cerr := corCli.UserGetPubShards(ctx.Request.Context(), &corrpc.UserGetPubShards{
  167. PubShardsID: pubType.PubShardsID,
  168. Password: pubType.Password,
  169. })
  170. if cerr != nil {
  171. ctx.JSON(http.StatusOK, types.Failed(ecode.ErrorCode(cerr.Code), cerr.Message))
  172. return
  173. }
  174. if resp.PubShards.Name == req.Name {
  175. ctx.JSON(http.StatusOK, types.OK(cliapi.PubShardsGetResp{
  176. PubShards: resp.PubShards,
  177. UserSpace: pubUss[i],
  178. }))
  179. return
  180. }
  181. }
  182. ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "pub shards %v not found", req.Name))
  183. }
  184. func (s *PubShardsService) ExportPackage(ctx *gin.Context) {
  185. log := logger.WithField("HTTP", "PubShards.ExportPackage")
  186. var req cliapi.PubShardsExportPackage
  187. if err := ctx.ShouldBindQuery(&req); err != nil {
  188. log.Warnf("binding query: %s", err.Error())
  189. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "%v", err))
  190. return
  191. }
  192. if stgglb.StandaloneMode {
  193. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "client is not online"))
  194. return
  195. }
  196. lock, err := s.svc.PubLock.BeginMutex().Package().Pin(req.PackageID).End().Lock()
  197. if err != nil {
  198. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "lock package: %v", err))
  199. return
  200. }
  201. defer lock.Unlock()
  202. pubs := make(map[jcstypes.UserSpaceID]jcstypes.PubShardsID)
  203. for _, id := range req.AvailablePubShards {
  204. us, err := s.svc.DB.UserSpace().GetByPubShardsID(s.svc.DB.DefCtx(), id)
  205. if err != nil {
  206. if err == gorm.ErrRecordNotFound {
  207. ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "pub shards %v not found", id))
  208. } else {
  209. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get pub shards %v: %v", id, err))
  210. }
  211. return
  212. }
  213. pubs[us.UserSpaceID] = id
  214. }
  215. pkg, err := s.svc.DB.Package().GetByID(s.svc.DB.DefCtx(), req.PackageID)
  216. if err != nil {
  217. if err == gorm.ErrRecordNotFound {
  218. ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "package %v not found", req.PackageID))
  219. } else {
  220. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get package %v: %v", req.PackageID, err))
  221. }
  222. return
  223. }
  224. objs, err := db.DoTx11(s.svc.DB, s.svc.DB.Object().GetPackageObjectDetails, req.PackageID)
  225. if err != nil {
  226. if err == gorm.ErrRecordNotFound {
  227. ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "package %v not found", req.PackageID))
  228. } else {
  229. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get package %v: %v", req.PackageID, err))
  230. }
  231. return
  232. }
  233. var pack jcstypes.PubShardsPackFile
  234. for _, o := range objs {
  235. po := jcstypes.PackObject{
  236. Object: o.Object,
  237. }
  238. for _, b := range o.Blocks {
  239. pub := pubs[b.UserSpaceID]
  240. if pub != "" {
  241. po.Blocks = append(po.Blocks, jcstypes.PackObjectBlock{
  242. Index: b.Index,
  243. PubShardsID: pub,
  244. FileHash: b.FileHash,
  245. Size: b.Size,
  246. })
  247. }
  248. }
  249. pack.Objects = append(pack.Objects, po)
  250. }
  251. zw := gzip.NewWriter(ctx.Writer)
  252. defer zw.Close()
  253. ctx.Header("Content-Disposition", "attachment; filename="+url.PathEscape(pkg.Name)+".pack")
  254. ctx.Header("Content-Type", "application/octet-stream")
  255. ctx.Header("Content-Transfer-Encoding", "binary")
  256. ps := serder.ObjectToJSONStream(pack)
  257. defer ps.Close()
  258. io.Copy(zw, ps)
  259. }
  260. func (s *PubShardsService) ImportPackage(ctx *gin.Context) {
  261. // log := logger.WithField("HTTP", "PubShards.ImportPackage")
  262. if stgglb.StandaloneMode {
  263. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "client is not online"))
  264. return
  265. }
  266. contType := ctx.GetHeader("Content-Type")
  267. mtype, params, err := mime.ParseMediaType(contType)
  268. if err != nil {
  269. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "parse content-type: %v", err))
  270. return
  271. }
  272. if mtype != http2.ContentTypeMultiPart {
  273. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "content-type %v not supported", mtype))
  274. return
  275. }
  276. boundary := params["boundary"]
  277. if boundary == "" {
  278. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "missing boundary in content-type"))
  279. return
  280. }
  281. mr := multipart.NewReader(ctx.Request.Body, boundary)
  282. p, err := mr.NextPart()
  283. if err != nil {
  284. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "read info part: %v", err))
  285. return
  286. }
  287. var info cliapi.PubShardsImportPackage
  288. err = serder.JSONToObjectStream(p, &info)
  289. if err != nil {
  290. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "parse info: %v", err))
  291. return
  292. }
  293. if info.PackageID == 0 {
  294. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "missing packageID"))
  295. return
  296. }
  297. fr, err := mr.NextPart()
  298. if err != nil {
  299. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "read file part: %v", err))
  300. return
  301. }
  302. gr, err := gzip.NewReader(fr)
  303. if err != nil {
  304. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "read gzip: %v", err))
  305. return
  306. }
  307. defer gr.Close()
  308. pack, err := serder.JSONToObjectStreamEx[jcstypes.PubShardsPackFile](gr)
  309. if err != nil {
  310. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "parse pack: %v", err))
  311. return
  312. }
  313. pubShardsUs, err := s.svc.DB.UserSpace().GetAllPubShards(s.svc.DB.DefCtx())
  314. if err != nil {
  315. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get pub shards: %v", err))
  316. return
  317. }
  318. uss := make(map[jcstypes.PubShardsID]jcstypes.UserSpace)
  319. for _, us := range pubShardsUs {
  320. pub, ok := us.Storage.(*jcstypes.PubShardsType)
  321. if !ok {
  322. continue
  323. }
  324. uss[pub.PubShardsID] = us
  325. }
  326. var willAdds []jcstypes.ObjectDetail
  327. var invalidObjs []jcstypes.Object
  328. // 只添加本客户端加入的PubShards中的块
  329. pubShardsRefs := make(map[jcstypes.PubShardsID][]jcstypes.FileHash)
  330. for _, po := range pack.Objects {
  331. blkIdx := make(map[int]bool)
  332. var addBlks []jcstypes.ObjectBlock
  333. for _, b := range po.Blocks {
  334. u, ok := uss[b.PubShardsID]
  335. if !ok {
  336. continue
  337. }
  338. pubShardsRefs[b.PubShardsID] = append(pubShardsRefs[b.PubShardsID], b.FileHash)
  339. addBlks = append(addBlks, jcstypes.ObjectBlock{
  340. Index: b.Index,
  341. UserSpaceID: u.UserSpaceID,
  342. FileHash: b.FileHash,
  343. Size: b.Size,
  344. })
  345. blkIdx[b.Index] = true
  346. }
  347. switch red := po.Object.Redundancy.(type) {
  348. case *jcstypes.NoneRedundancy:
  349. if len(addBlks) < 1 {
  350. invalidObjs = append(invalidObjs, po.Object)
  351. continue
  352. }
  353. case *jcstypes.RepRedundancy:
  354. if len(addBlks) < 1 {
  355. invalidObjs = append(invalidObjs, po.Object)
  356. continue
  357. }
  358. case *jcstypes.ECRedundancy:
  359. if len(blkIdx) < red.K {
  360. invalidObjs = append(invalidObjs, po.Object)
  361. continue
  362. }
  363. }
  364. willAdds = append(willAdds, jcstypes.ObjectDetail{
  365. Object: po.Object,
  366. Blocks: addBlks,
  367. })
  368. }
  369. // 在每一个PubShards创建FileHash引用,并且根据反馈的不存在的FileHash列表筛选导入列表
  370. for pub, refs := range pubShardsRefs {
  371. us := uss[pub]
  372. pubType := us.Storage.(*jcstypes.PubShardsType)
  373. hubCli := stgglb.HubRPCPool.GetByID(pubType.MasterHub)
  374. resp, cerr := hubCli.PubShardsCreateRefs(ctx.Request.Context(), &hubrpc.PubShardsCreateRefs{
  375. PubShardsID: pubType.PubShardsID,
  376. Password: pubType.Password,
  377. FileHashes: refs,
  378. })
  379. if cerr != nil {
  380. hubCli.Release()
  381. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "create pub shards refs at %v: %v", pubType.PubShardsID, cerr))
  382. return
  383. }
  384. invalidHashes := make(map[jcstypes.FileHash]bool)
  385. for _, h := range resp.InvalidFileHashes {
  386. invalidHashes[h] = true
  387. }
  388. for i := range willAdds {
  389. willAdds[i].Blocks = lo.Reject(willAdds[i].Blocks, func(blk jcstypes.ObjectBlock, idx int) bool {
  390. return blk.UserSpaceID == us.UserSpaceID && invalidHashes[blk.FileHash]
  391. })
  392. }
  393. hubCli.Release()
  394. }
  395. // 再次检查每个对象是否拥有完整块
  396. willAdds = lo.Filter(willAdds, func(obj jcstypes.ObjectDetail, idx int) bool {
  397. blkIdx := make(map[int]bool)
  398. for _, blk := range obj.Blocks {
  399. blkIdx[blk.Index] = true
  400. }
  401. switch red := obj.Object.Redundancy.(type) {
  402. case *jcstypes.NoneRedundancy:
  403. if len(blkIdx) < 1 {
  404. invalidObjs = append(invalidObjs, obj.Object)
  405. return false
  406. }
  407. case *jcstypes.RepRedundancy:
  408. if len(blkIdx) < 1 {
  409. invalidObjs = append(invalidObjs, obj.Object)
  410. return false
  411. }
  412. case *jcstypes.ECRedundancy:
  413. if len(blkIdx) < red.K {
  414. invalidObjs = append(invalidObjs, obj.Object)
  415. return false
  416. }
  417. }
  418. return true
  419. })
  420. _, err = db.DoTx21(s.svc.DB, s.svc.DB.Object().BatchCreateByDetails, info.PackageID, willAdds)
  421. if err != nil {
  422. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "create objects: %v", err))
  423. return
  424. }
  425. ctx.JSON(http.StatusOK, types.OK(cliapi.PubShardsImportPackageResp{
  426. InvalidObjects: invalidObjs,
  427. }))
  428. }

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