From 309a1cbf4d52fa92c1d3ab7e9e848cff0caf109c Mon Sep 17 00:00:00 2001 From: scholar7r Date: Thu, 12 Mar 2026 16:28:02 +0800 Subject: [PATCH 1/3] feat: implement batch delete service --- routers/controllers/share.go | 10 ++++++++++ routers/router.go | 7 ++++++- service/share/manage.go | 27 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/routers/controllers/share.go b/routers/controllers/share.go index 3aa549ca..9899d98b 100644 --- a/routers/controllers/share.go +++ b/routers/controllers/share.go @@ -72,6 +72,16 @@ func DeleteShare(c *gin.Context) { c.JSON(200, serializer.Response{}) } +func BatchDeleteShare(c *gin.Context) { + service := ParametersFromContext[*share.BatchDeleteShareService](c, share.BatchDeleteParamCtx{}) + err := service.Delete(c) + if err != nil { + c.JSON(200, serializer.Err(c, err)) + } + + c.JSON(200, serializer.Response{}) +} + func ShareRedirect(c *gin.Context) { service := ParametersFromContext[*share.ShortLinkRedirectService](c, share.ShortLinkRedirectParamCtx{}) c.Redirect(http.StatusFound, service.RedirectTo(c)) diff --git a/routers/router.go b/routers/router.go index 8b357d82..70f345ea 100644 --- a/routers/router.go +++ b/routers/router.go @@ -38,7 +38,6 @@ func InitRouter(dep dependency.Dep) *gin.Engine { l.Info("Current running mode: Slave.") return initSlaveRouter(dep) - } func newGinEngine(dep dependency.Dep) *gin.Engine { @@ -842,6 +841,12 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine { middleware.HashID(hashid.ShareID), controllers.DeleteShare, ) + share.DELETE("", + middleware.LoginRequired(), + middleware.RequiredScopes(types.ScopeSharesWrite), + controllers.FromJSON[sharesvc.BatchDeleteShareService](sharesvc.BatchDeleteParamCtx{}), + controllers.BatchDeleteShare, + ) //// 获取README文本文件内容 //share.GET("readme/:id", // middleware.CheckShareUnlocked(), diff --git a/service/share/manage.go b/service/share/manage.go index b8ad4d1d..e9049f1b 100644 --- a/service/share/manage.go +++ b/service/share/manage.go @@ -27,8 +27,35 @@ type ( ShowReadMe bool `json:"show_readme"` } ShareCreateParamCtx struct{} + + BatchDeleteShareService struct { + ShareIDs []string `json:"ids" binding:"required"` + } + BatchDeleteParamCtx struct{} ) +func (s *BatchDeleteShareService) Delete(c *gin.Context) error { + dep := dependency.FromContext(c) + shareClient := dep.ShareClient() + + var shareIDs []int + + for _, v := range s.ShareIDs { + share, err := shareClient.GetByHashID(c, v) + if err != nil { + return err + } + + shareIDs = append(shareIDs, share.ID) + } + + if err := shareClient.DeleteBatch(c, shareIDs); err != nil { + return serializer.NewError(serializer.CodeDBError, "Failed to delete shares", err) + } + + return nil +} + // Upsert 创建或更新分享 func (service *ShareCreateService) Upsert(c *gin.Context, existed int) (string, error) { dep := dependency.FromContext(c) From a2552bbf906d3753c32391ce564ff3bda2d565e9 Mon Sep 17 00:00:00 2001 From: scholar7r Date: Fri, 13 Mar 2026 09:37:27 +0800 Subject: [PATCH 2/3] refactor: implement `GetByHashIDs` to make sure only query database once --- inventory/share.go | 17 +++++++++++++++++ service/share/manage.go | 17 ++++++++--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/inventory/share.go b/inventory/share.go index 642ce21c..52f3b9f2 100644 --- a/inventory/share.go +++ b/inventory/share.go @@ -41,6 +41,8 @@ type ( GetByIDUser(ctx context.Context, id, uid int) (*ent.Share, error) // GetByHashID returns the share with given hash id. GetByHashID(ctx context.Context, idRaw string) (*ent.Share, error) + // GetByHashIDs returns the shares with given hash ids + GetByHashIDs(ctx context.Context, idsRaw []string) ([]*ent.Share, error) // Upsert creates or update a new share record. Upsert(ctx context.Context, params *CreateShareParams) (*ent.Share, error) // Viewed increase the view count of the share. @@ -161,6 +163,21 @@ func (c *shareClient) GetByHashID(ctx context.Context, idRaw string) (*ent.Share return c.GetByID(ctx, id) } +func (c *shareClient) GetByHashIDs(ctx context.Context, idsRaw []string) ([]*ent.Share, error) { + var ids []int + + for _, v := range idsRaw { + id, err := c.hasher.Decode(v, hashid.ShareID) + if err != nil { + return nil, fmt.Errorf("failed to decode hash id %q: %w", v, err) + } + + ids = append(ids, id) + } + + return c.GetByIDs(ctx, ids) +} + func (c *shareClient) GetByID(ctx context.Context, id int) (*ent.Share, error) { s, err := withShareEagerLoading(ctx, c.client.Share.Query().Where(share.ID(id))).First(ctx) if err != nil { diff --git a/service/share/manage.go b/service/share/manage.go index e9049f1b..687e3445 100644 --- a/service/share/manage.go +++ b/service/share/manage.go @@ -38,18 +38,17 @@ func (s *BatchDeleteShareService) Delete(c *gin.Context) error { dep := dependency.FromContext(c) shareClient := dep.ShareClient() - var shareIDs []int - - for _, v := range s.ShareIDs { - share, err := shareClient.GetByHashID(c, v) - if err != nil { - return err - } + shares, err := shareClient.GetByHashIDs(c, s.ShareIDs) + if err != nil { + return err + } - shareIDs = append(shareIDs, share.ID) + var ids []int + for _, v := range shares { + ids = append(ids, v.ID) } - if err := shareClient.DeleteBatch(c, shareIDs); err != nil { + if err := shareClient.DeleteBatch(c, ids); err != nil { return serializer.NewError(serializer.CodeDBError, "Failed to delete shares", err) } From 533b4c3d5dae78543f13c784db7d47b2c6c216d3 Mon Sep 17 00:00:00 2001 From: scholar7r Date: Fri, 13 Mar 2026 11:34:24 +0800 Subject: [PATCH 3/3] refactor: use decode instead of `Get` before `Delete` - fix incorrect `DeleteBatch` method (for admin) calling - implement DeleteBatchByUserID --- inventory/share.go | 28 +++++++++------------------- service/share/manage.go | 22 +++++++++++++--------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/inventory/share.go b/inventory/share.go index 52f3b9f2..0090154b 100644 --- a/inventory/share.go +++ b/inventory/share.go @@ -41,8 +41,6 @@ type ( GetByIDUser(ctx context.Context, id, uid int) (*ent.Share, error) // GetByHashID returns the share with given hash id. GetByHashID(ctx context.Context, idRaw string) (*ent.Share, error) - // GetByHashIDs returns the shares with given hash ids - GetByHashIDs(ctx context.Context, idsRaw []string) ([]*ent.Share, error) // Upsert creates or update a new share record. Upsert(ctx context.Context, params *CreateShareParams) (*ent.Share, error) // Viewed increase the view count of the share. @@ -51,12 +49,14 @@ type ( Downloaded(ctx context.Context, share *ent.Share) error // Delete deletes the share. Delete(ctx context.Context, shareId int) error + // DeleteBatch deletes the shares with the given ids. + DeleteBatch(ctx context.Context, shareIds []int) error + // DeleteBatchByUserID deletes the shares with the given ids and user id + DeleteBatchByUserID(ctx context.Context, uid int, shareIds []int) error // List returns a list of shares with the given args. List(ctx context.Context, args *ListShareArgs) (*ListShareResult, error) // CountByTimeRange counts the number of shares created in the given time range. CountByTimeRange(ctx context.Context, start, end *time.Time) (int, error) - // DeleteBatch deletes the shares with the given ids. - DeleteBatch(ctx context.Context, shareIds []int) error } CreateShareParams struct { @@ -163,21 +163,6 @@ func (c *shareClient) GetByHashID(ctx context.Context, idRaw string) (*ent.Share return c.GetByID(ctx, id) } -func (c *shareClient) GetByHashIDs(ctx context.Context, idsRaw []string) ([]*ent.Share, error) { - var ids []int - - for _, v := range idsRaw { - id, err := c.hasher.Decode(v, hashid.ShareID) - if err != nil { - return nil, fmt.Errorf("failed to decode hash id %q: %w", v, err) - } - - ids = append(ids, id) - } - - return c.GetByIDs(ctx, ids) -} - func (c *shareClient) GetByID(ctx context.Context, id int) (*ent.Share, error) { s, err := withShareEagerLoading(ctx, c.client.Share.Query().Where(share.ID(id))).First(ctx) if err != nil { @@ -212,6 +197,11 @@ func (c *shareClient) DeleteBatch(ctx context.Context, shareIds []int) error { return err } +func (c *shareClient) DeleteBatchByUserID(ctx context.Context, uid int, shareIds []int) error { + _, err := c.client.Share.Delete().Where(share.IDIn(shareIds...)).Where(share.HasUserWith(user.ID(uid))).Exec(ctx) + return err +} + func (c *shareClient) Delete(ctx context.Context, shareId int) error { return c.client.Share.DeleteOneID(shareId).Exec(ctx) } diff --git a/service/share/manage.go b/service/share/manage.go index 687e3445..f1121e7c 100644 --- a/service/share/manage.go +++ b/service/share/manage.go @@ -2,6 +2,7 @@ package share import ( "context" + "fmt" "time" "github.com/cloudreve/Cloudreve/v4/application/dependency" @@ -10,6 +11,7 @@ import ( "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager" + "github.com/cloudreve/Cloudreve/v4/pkg/hashid" "github.com/cloudreve/Cloudreve/v4/pkg/serializer" "github.com/cloudreve/Cloudreve/v4/service/explorer" "github.com/gin-gonic/gin" @@ -34,21 +36,23 @@ type ( BatchDeleteParamCtx struct{} ) -func (s *BatchDeleteShareService) Delete(c *gin.Context) error { +func (service *BatchDeleteShareService) Delete(c *gin.Context) error { dep := dependency.FromContext(c) + uid := inventory.UserIDFromContext(c) shareClient := dep.ShareClient() - shares, err := shareClient.GetByHashIDs(c, s.ShareIDs) - if err != nil { - return err - } - var ids []int - for _, v := range shares { - ids = append(ids, v.ID) + + for _, v := range service.ShareIDs { + id, err := dep.HashIDEncoder().Decode(v, hashid.ShareID) + if err != nil { + return fmt.Errorf("failed to decode hash id %q: %w", v, err) + } + + ids = append(ids, id) } - if err := shareClient.DeleteBatch(c, ids); err != nil { + if err := shareClient.DeleteBatchByUserID(c, uid, ids); err != nil { return serializer.NewError(serializer.CodeDBError, "Failed to delete shares", err) }