| @@ -358,6 +358,26 @@ func (s *ObjectService) DeleteByPath(ctx *gin.Context) { | |||||
| ctx.JSON(http.StatusOK, OK(nil)) | ctx.JSON(http.StatusOK, OK(nil)) | ||||
| } | } | ||||
| func (s *ObjectService) Clone(ctx *gin.Context) { | |||||
| log := logger.WithField("HTTP", "Object.Clone") | |||||
| var req cdsapi.ObjectClone | |||||
| if err := ctx.ShouldBindJSON(&req); err != nil { | |||||
| log.Warnf("binding body: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) | |||||
| return | |||||
| } | |||||
| objs, err := s.svc.ObjectSvc().Clone(req.UserID, req.Clonings) | |||||
| if err != nil { | |||||
| log.Warnf("cloning object: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, "clone object failed")) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, OK(cdsapi.ObjectCloneResp{Objects: objs})) | |||||
| } | |||||
| func (s *ObjectService) GetPackageObjects(ctx *gin.Context) { | func (s *ObjectService) GetPackageObjects(ctx *gin.Context) { | ||||
| log := logger.WithField("HTTP", "Object.GetPackageObjects") | log := logger.WithField("HTTP", "Object.GetPackageObjects") | ||||
| @@ -54,6 +54,7 @@ func (s *Server) initRouters() { | |||||
| rt.POST(cdsapi.ObjectMovePath, s.Object().Move) | rt.POST(cdsapi.ObjectMovePath, s.Object().Move) | ||||
| rt.POST(cdsapi.ObjectDeletePath, s.Object().Delete) | rt.POST(cdsapi.ObjectDeletePath, s.Object().Delete) | ||||
| rt.POST(cdsapi.ObjectDeleteByPathPath, s.Object().DeleteByPath) | rt.POST(cdsapi.ObjectDeleteByPathPath, s.Object().DeleteByPath) | ||||
| rt.POST(cdsapi.ObjectClonePath, s.Object().Clone) | |||||
| rt.GET(cdsapi.PackageGetPath, s.Package().Get) | rt.GET(cdsapi.PackageGetPath, s.Package().Get) | ||||
| rt.GET(cdsapi.PackageGetByNamePath, s.Package().GetByName) | rt.GET(cdsapi.PackageGetByNamePath, s.Package().GetByName) | ||||
| @@ -113,6 +113,21 @@ func (svc *ObjectService) Delete(userID cdssdk.UserID, objectIDs []cdssdk.Object | |||||
| return nil | return nil | ||||
| } | } | ||||
| func (svc *ObjectService) Clone(userID cdssdk.UserID, clonings []cdsapi.CloningObject) ([]*cdssdk.Object, error) { | |||||
| coorCli, err := stgglb.CoordinatorMQPool.Acquire() | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||||
| } | |||||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | |||||
| resp, err := coorCli.CloneObjects(coormq.ReqCloneObjects(userID, clonings)) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("requsting to coodinator: %w", err) | |||||
| } | |||||
| return resp.Objects, nil | |||||
| } | |||||
| // GetPackageObjects 获取包中的对象列表。 | // GetPackageObjects 获取包中的对象列表。 | ||||
| // userID: 用户ID。 | // userID: 用户ID。 | ||||
| // packageID: 包ID。 | // packageID: 包ID。 | ||||
| @@ -85,6 +85,41 @@ func (db *ObjectDB) BatchGetByPackagePath(ctx SQLContext, pkgID cdssdk.PackageID | |||||
| return objs, nil | return objs, nil | ||||
| } | } | ||||
| // 仅返回查询到的对象 | |||||
| func (db *ObjectDB) BatchGetDetails(ctx SQLContext, objectIDs []cdssdk.ObjectID) ([]stgmod.ObjectDetail, error) { | |||||
| var objs []cdssdk.Object | |||||
| err := ctx.Table("Object").Where("ObjectID IN ?", objectIDs).Order("ObjectID ASC").Find(&objs).Error | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| // 获取所有的 ObjectBlock | |||||
| var allBlocks []stgmod.ObjectBlock | |||||
| err = ctx.Table("ObjectBlock").Where("ObjectID IN ?", objectIDs).Order("ObjectID, `Index` ASC").Find(&allBlocks).Error | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| // 获取所有的 PinnedObject | |||||
| var allPinnedObjs []cdssdk.PinnedObject | |||||
| err = ctx.Table("PinnedObject").Where("ObjectID IN ?", objectIDs).Order("ObjectID ASC").Find(&allPinnedObjs).Error | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| details := make([]stgmod.ObjectDetail, len(objs)) | |||||
| for i, obj := range objs { | |||||
| details[i] = stgmod.ObjectDetail{ | |||||
| Object: obj, | |||||
| } | |||||
| } | |||||
| stgmod.DetailsFillObjectBlocks(details, allBlocks) | |||||
| stgmod.DetailsFillPinnedAt(details, allPinnedObjs) | |||||
| return details, nil | |||||
| } | |||||
| func (db *ObjectDB) Create(ctx SQLContext, obj cdssdk.Object) (cdssdk.ObjectID, error) { | func (db *ObjectDB) Create(ctx SQLContext, obj cdssdk.Object) (cdssdk.ObjectID, error) { | ||||
| err := ctx.Table("Object").Create(&obj).Error | err := ctx.Table("Object").Create(&obj).Error | ||||
| if err != nil { | if err != nil { | ||||
| @@ -28,6 +28,8 @@ type ObjectService interface { | |||||
| DeleteObjects(msg *DeleteObjects) (*DeleteObjectsResp, *mq.CodeMessage) | DeleteObjects(msg *DeleteObjects) (*DeleteObjectsResp, *mq.CodeMessage) | ||||
| CloneObjects(msg *CloneObjects) (*CloneObjectsResp, *mq.CodeMessage) | |||||
| GetDatabaseAll(msg *GetDatabaseAll) (*GetDatabaseAllResp, *mq.CodeMessage) | GetDatabaseAll(msg *GetDatabaseAll) (*GetDatabaseAllResp, *mq.CodeMessage) | ||||
| AddAccessStat(msg *AddAccessStat) | AddAccessStat(msg *AddAccessStat) | ||||
| @@ -285,6 +287,34 @@ func (client *Client) DeleteObjects(msg *DeleteObjects) (*DeleteObjectsResp, err | |||||
| return mq.Request(Service.DeleteObjects, client.rabbitCli, msg) | return mq.Request(Service.DeleteObjects, client.rabbitCli, msg) | ||||
| } | } | ||||
| // 克隆Object | |||||
| var _ = Register(Service.CloneObjects) | |||||
| type CloneObjects struct { | |||||
| mq.MessageBodyBase | |||||
| UserID cdssdk.UserID `json:"userID"` | |||||
| Clonings []cdsapi.CloningObject `json:"clonings"` | |||||
| } | |||||
| type CloneObjectsResp struct { | |||||
| mq.MessageBodyBase | |||||
| Objects []*cdssdk.Object `json:"objects"` | |||||
| } | |||||
| func ReqCloneObjects(userID cdssdk.UserID, clonings []cdsapi.CloningObject) *CloneObjects { | |||||
| return &CloneObjects{ | |||||
| UserID: userID, | |||||
| Clonings: clonings, | |||||
| } | |||||
| } | |||||
| func RespCloneObjects(objects []*cdssdk.Object) *CloneObjectsResp { | |||||
| return &CloneObjectsResp{ | |||||
| Objects: objects, | |||||
| } | |||||
| } | |||||
| func (client *Client) CloneObjects(msg *CloneObjects) (*CloneObjectsResp, error) { | |||||
| return mq.Request(Service.CloneObjects, client.rabbitCli, msg) | |||||
| } | |||||
| // 增加访问计数 | // 增加访问计数 | ||||
| var _ = RegisterNoReply(Service.AddAccessStat) | var _ = RegisterNoReply(Service.AddAccessStat) | ||||
| @@ -483,3 +483,130 @@ func (svc *Service) DeleteObjects(msg *coormq.DeleteObjects) (*coormq.DeleteObje | |||||
| return mq.ReplyOK(coormq.RespDeleteObjects()) | return mq.ReplyOK(coormq.RespDeleteObjects()) | ||||
| } | } | ||||
| func (svc *Service) CloneObjects(msg *coormq.CloneObjects) (*coormq.CloneObjectsResp, *mq.CodeMessage) { | |||||
| type CloningObject struct { | |||||
| Cloning cdsapi.CloningObject | |||||
| OrgIndex int | |||||
| } | |||||
| type PackageClonings struct { | |||||
| PackageID cdssdk.PackageID | |||||
| Clonings map[string]CloningObject | |||||
| } | |||||
| // TODO 要检查用户是否有Object、Package的权限 | |||||
| clonings := make(map[cdssdk.PackageID]*PackageClonings) | |||||
| for i, cloning := range msg.Clonings { | |||||
| pkg, ok := clonings[cloning.NewPackageID] | |||||
| if !ok { | |||||
| pkg = &PackageClonings{ | |||||
| PackageID: cloning.NewPackageID, | |||||
| Clonings: make(map[string]CloningObject), | |||||
| } | |||||
| clonings[cloning.NewPackageID] = pkg | |||||
| } | |||||
| pkg.Clonings[cloning.NewPath] = CloningObject{ | |||||
| Cloning: cloning, | |||||
| OrgIndex: i, | |||||
| } | |||||
| } | |||||
| ret := make([]*cdssdk.Object, len(msg.Clonings)) | |||||
| err := svc.db2.DoTx(func(tx db2.SQLContext) error { | |||||
| // 剔除掉新路径已经存在的对象 | |||||
| for _, pkg := range clonings { | |||||
| exists, err := svc.db2.Object().BatchGetByPackagePath(tx, pkg.PackageID, lo.Keys(pkg.Clonings)) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch getting objects by package path: %w", err) | |||||
| } | |||||
| for _, obj := range exists { | |||||
| delete(pkg.Clonings, obj.Path) | |||||
| } | |||||
| } | |||||
| // 删除目的Package不存在的对象 | |||||
| newPkg, err := svc.db2.Package().BatchTestPackageID(tx, lo.Keys(clonings)) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch testing package id: %w", err) | |||||
| } | |||||
| for _, pkg := range clonings { | |||||
| if !newPkg[pkg.PackageID] { | |||||
| delete(clonings, pkg.PackageID) | |||||
| } | |||||
| } | |||||
| var avaiClonings []CloningObject | |||||
| var avaiObjIDs []cdssdk.ObjectID | |||||
| for _, pkg := range clonings { | |||||
| for _, cloning := range pkg.Clonings { | |||||
| avaiClonings = append(avaiClonings, cloning) | |||||
| avaiObjIDs = append(avaiObjIDs, cloning.Cloning.ObjectID) | |||||
| } | |||||
| } | |||||
| avaiDetails, err := svc.db2.Object().BatchGetDetails(tx, avaiObjIDs) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch getting object details: %w", err) | |||||
| } | |||||
| avaiDetailsMap := make(map[cdssdk.ObjectID]stgmod.ObjectDetail) | |||||
| for _, detail := range avaiDetails { | |||||
| avaiDetailsMap[detail.Object.ObjectID] = detail | |||||
| } | |||||
| oldAvaiClonings := avaiClonings | |||||
| avaiClonings = nil | |||||
| var newObjs []cdssdk.Object | |||||
| for _, cloning := range oldAvaiClonings { | |||||
| // 进一步剔除原始对象不存在的情况 | |||||
| detail, ok := avaiDetailsMap[cloning.Cloning.ObjectID] | |||||
| if !ok { | |||||
| continue | |||||
| } | |||||
| avaiClonings = append(avaiClonings, cloning) | |||||
| newObj := detail.Object | |||||
| newObj.ObjectID = 0 | |||||
| newObj.Path = cloning.Cloning.NewPath | |||||
| newObj.PackageID = cloning.Cloning.NewPackageID | |||||
| newObjs = append(newObjs, newObj) | |||||
| } | |||||
| // 先创建出新对象 | |||||
| err = svc.db2.Object().BatchCreate(tx, &newObjs) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch creating objects: %w", err) | |||||
| } | |||||
| // 创建了新对象就能拿到新对象ID,再创建新对象块 | |||||
| var newBlks []stgmod.ObjectBlock | |||||
| for i, cloning := range avaiClonings { | |||||
| oldBlks := avaiDetailsMap[cloning.Cloning.ObjectID].Blocks | |||||
| for _, blk := range oldBlks { | |||||
| newBlk := blk | |||||
| newBlk.ObjectID = newObjs[i].ObjectID | |||||
| newBlks = append(newBlks, newBlk) | |||||
| } | |||||
| } | |||||
| err = svc.db2.ObjectBlock().BatchCreate(tx, newBlks) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch creating object blocks: %w", err) | |||||
| } | |||||
| for i, cloning := range avaiClonings { | |||||
| ret[cloning.OrgIndex] = &newObjs[i] | |||||
| } | |||||
| return nil | |||||
| }) | |||||
| if err != nil { | |||||
| logger.Warnf("cloning objects: %s", err.Error()) | |||||
| return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | |||||
| } | |||||
| return mq.ReplyOK(coormq.RespCloneObjects(ret)) | |||||
| } | |||||