From cfbfbc3c5517b6009f19c4c7af09355df191c804 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Thu, 13 Feb 2020 11:53:24 +0800 Subject: [PATCH] Feat: list/search/update/delete shares --- middleware/share.go | 26 +++++++++++++++- models/share.go | 53 +++++++++++++++++++++++++++++++ models/user.go | 2 ++ pkg/serializer/share.go | 60 +++++++++++++++++++++++++++++++++++- routers/controllers/share.go | 44 ++++++++++++++++++++++++++ routers/router.go | 14 +++++++++ service/callback/upload.go | 6 +++- service/share/manage.go | 56 +++++++++++++++++++++++++++++++-- service/share/visit.go | 34 ++++++++++++++++++++ 9 files changed, 290 insertions(+), 5 deletions(-) diff --git a/middleware/share.go b/middleware/share.go index 99868a5..a67a67b 100644 --- a/middleware/share.go +++ b/middleware/share.go @@ -8,6 +8,30 @@ import ( "github.com/gin-gonic/gin" ) +// ShareOwner 检查当前登录用户是否为分享所有者 +func ShareOwner() gin.HandlerFunc { + return func(c *gin.Context) { + var user *model.User + if userCtx, ok := c.Get("user"); ok { + user = userCtx.(*model.User) + } else { + c.JSON(200, serializer.Err(serializer.CodeCheckLogin, "请先登录", nil)) + c.Abort() + return + } + + if share, ok := c.Get("share"); ok { + if share.(*model.Share).Creator().ID != user.ID { + c.JSON(200, serializer.Err(serializer.CodeNotFound, "分享不存在", nil)) + c.Abort() + return + } + } + + c.Next() + } +} + // ShareAvailable 检查分享是否可用 func ShareAvailable() gin.HandlerFunc { return func(c *gin.Context) { @@ -21,7 +45,7 @@ func ShareAvailable() gin.HandlerFunc { share := model.GetShareByHashID(c.Param("id")) if share == nil || !share.IsAvailable() { - c.JSON(200, serializer.Err(serializer.CodeNotFound, "分享不存在或已被取消", nil)) + c.JSON(200, serializer.Err(serializer.CodeNotFound, "分享不存在或已失效", nil)) c.Abort() return } diff --git a/models/share.go b/models/share.go index 9dcde9d..bf9e6a0 100644 --- a/models/share.go +++ b/models/share.go @@ -25,6 +25,7 @@ type Share struct { Expires *time.Time // 过期时间,空值表示无过期时间 Score int // 每人次下载扣除积分 PreviewEnabled bool // 是否允许直接预览 + SourceName string `gorm:"index:source"` // 用于搜索的字段 // 数据库忽略字段 User User `gorm:"PRELOAD:false,association_autoupdate:false"` @@ -79,6 +80,11 @@ func (share *Share) IsAvailable() bool { return false } + // 检查创建者状态 + if share.Creator().Status != Active { + return false + } + return true } @@ -202,3 +208,50 @@ func (share *Share) Downloaded() { "remain_downloads": share.RemainDownloads, }) } + +// Update 更新分享属性 +func (share *Share) Update(props map[string]interface{}) error { + return DB.Model(share).Updates(props).Error +} + +// Delete 删除分享 +func (share *Share) Delete() error { + return DB.Model(share).Delete(share).Error +} + +// ListShares 列出UID下的分享 +func ListShares(uid uint, page, pageSize int, order string, publicOnly bool) ([]Share, int) { + var ( + shares []Share + total int + ) + dbChain := DB + dbChain = dbChain.Where("user_id = ?", uid) + if publicOnly { + dbChain.Where("password = ?", "") + } + + // 计算总数用于分页 + dbChain.Model(&Share{}).Count(&total) + + // 查询记录 + dbChain.Limit(pageSize).Offset((page - 1) * pageSize).Order(order).Find(&shares) + return shares, total +} + +// SearchShares 根据关键字搜索分享 +func SearchShares(page, pageSize int, order, keywords string) ([]Share, int) { + var ( + shares []Share + total int + ) + dbChain := DB + dbChain = dbChain.Where("password = ? and remain_downloads <> 0 and (expires is NULL or expires > ?) and source_name like ?", "", time.Now(), "%"+keywords+"%") + + // 计算总数用于分页 + dbChain.Model(&Share{}).Count(&total) + + // 查询记录 + dbChain.Limit(pageSize).Offset((page - 1) * pageSize).Order(order).Find(&shares) + return shares, total +} diff --git a/models/user.go b/models/user.go index aef7d08..8583711 100644 --- a/models/user.go +++ b/models/user.go @@ -18,6 +18,8 @@ const ( NotActivicated // Baned 被封禁 Baned + // OveruseBaned 超额使用被封禁 + OveruseBaned ) // User 用户模型 diff --git a/pkg/serializer/share.go b/pkg/serializer/share.go index ddf4621..83542b8 100644 --- a/pkg/serializer/share.go +++ b/pkg/serializer/share.go @@ -6,7 +6,7 @@ import ( "time" ) -// Share 分享序列化 +// Share 分享信息序列化 type Share struct { Key string `json:"key"` Locked bool `json:"locked"` @@ -32,6 +32,64 @@ type shareSource struct { Size uint64 `json:"size"` } +// myShareItem 我的分享列表条目 +type myShareItem struct { + Key string `json:"key"` + IsDir bool `json:"is_dir"` + Score int `json:"score"` + Password string `json:"password"` + CreateDate string `json:"create_date,omitempty"` + Downloads int `json:"downloads"` + RemainDownloads int `json:"remain_downloads"` + Views int `json:"views"` + Expire int64 `json:"expire"` + Preview bool `json:"preview"` + Source *shareSource `json:"source,omitempty"` +} + +// BuildShareList 构建我的分享列表响应 +func BuildShareList(shares []model.Share, total int) Response { + res := make([]myShareItem, 0, total) + now := time.Now().Unix() + for i := 0; i < len(shares); i++ { + item := myShareItem{ + Key: hashid.HashID(shares[i].ID, hashid.ShareID), + IsDir: shares[i].IsDir, + Score: shares[i].Score, + Password: shares[i].Password, + CreateDate: shares[i].CreatedAt.Format("2006-01-02 15:04:05"), + Downloads: shares[i].Downloads, + Views: shares[i].Views, + Preview: shares[i].PreviewEnabled, + Expire: -1, + RemainDownloads: shares[i].RemainDownloads, + } + if shares[i].Expires != nil { + item.Expire = shares[i].Expires.Unix() - now + if item.Expire == 0 { + item.Expire = 0 + } + } + if shares[i].File.ID != 0 { + item.Source = &shareSource{ + Name: shares[i].File.Name, + Size: shares[i].File.Size, + } + } else if shares[i].Folder.ID != 0 { + item.Source = &shareSource{ + Name: shares[i].Folder.Name, + } + } + + res = append(res, item) + } + + return Response{Data: map[string]interface{}{ + "total": total, + "items": res, + }} +} + // BuildShareResponse 构建获取分享信息响应 func BuildShareResponse(share *model.Share, unlocked bool) Share { creator := share.Creator() diff --git a/routers/controllers/share.go b/routers/controllers/share.go index 09612f1..49ad750 100644 --- a/routers/controllers/share.go +++ b/routers/controllers/share.go @@ -33,6 +33,50 @@ func GetShare(c *gin.Context) { } } +// ListShare 列出分享 +func ListShare(c *gin.Context) { + var service share.ShareListService + if err := c.ShouldBindQuery(&service); err == nil { + res := service.List(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + +// SearchShare 搜索分享 +func SearchShare(c *gin.Context) { + var service share.ShareListService + if err := c.ShouldBindQuery(&service); err == nil { + res := service.Search(c) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + +// UpdateShare 更新分享属性 +func UpdateShare(c *gin.Context) { + var service share.ShareUpdateService + if err := c.ShouldBindJSON(&service); err == nil { + res := service.Update(c) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + +// DeleteShare 删除分享 +func DeleteShare(c *gin.Context) { + var service share.Service + if err := c.ShouldBindUri(&service); err == nil { + res := service.Delete(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + // GetShareDownload 创建分享下载会话 func GetShareDownload(c *gin.Context) { var service share.Service diff --git a/routers/router.go b/routers/router.go index 9cd34ef..88ebef5 100644 --- a/routers/router.go +++ b/routers/router.go @@ -320,6 +320,10 @@ func InitMasterRouter() *gin.Engine { { // 创建新分享 share.POST("", controllers.CreateShare) + // 列出我的分享 + share.GET("", controllers.ListShare) + // 搜索公共分享 + share.GET("search", controllers.SearchShare) // 转存他人分享 share.POST("save/:id", middleware.ShareAvailable(), @@ -327,6 +331,16 @@ func InitMasterRouter() *gin.Engine { middleware.BeforeShareDownload(), controllers.SaveShare, ) + // 更新分享属性 + share.PATCH(":id", + middleware.ShareAvailable(), + middleware.ShareOwner(), + controllers.UpdateShare, + ) + // 删除分享 + share.DELETE(":id", + controllers.DeleteShare, + ) } // 用户标签 diff --git a/service/callback/upload.go b/service/callback/upload.go index b261474..9cc6722 100644 --- a/service/callback/upload.go +++ b/service/callback/upload.go @@ -124,7 +124,11 @@ func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer. // 获取父目录 exist, parentFolder := fs.IsPathExist(callbackSession.VirtualPath) if !exist { - return serializer.Err(serializer.CodeParamErr, "指定目录不存在", nil) + newFolder, err := fs.CreateDirectory(context.Background(), callbackSession.VirtualPath) + if err != nil { + return serializer.Err(serializer.CodeParamErr, "指定目录不存在", err) + } + parentFolder = newFolder } // 创建文件头 diff --git a/service/share/manage.go b/service/share/manage.go index 48f4452..bc6b7e6 100644 --- a/service/share/manage.go +++ b/service/share/manage.go @@ -20,6 +20,52 @@ type ShareCreateService struct { Preview bool `json:"preview"` } +// ShareUpdateService 分享更新服务 +type ShareUpdateService struct { + Prop string `json:"prop" binding:"required,eq=password|eq=preview_enabled"` + Value string `json:"value" binding:"max=255"` +} + +// Delete 删除分享 +func (service *Service) Delete(c *gin.Context, user *model.User) serializer.Response { + share := model.GetShareByHashID(c.Param("id")) + if share == nil || share.Creator().ID != user.ID { + return serializer.Err(serializer.CodeNotFound, "分享不存在", nil) + } + + if err := share.Delete(); err != nil { + return serializer.Err(serializer.CodeDBError, "分享删除失败", err) + } + + return serializer.Response{} +} + +// Update 更新分享属性 +func (service *ShareUpdateService) Update(c *gin.Context) serializer.Response { + shareCtx, _ := c.Get("share") + share := shareCtx.(*model.Share) + + switch service.Prop { + case "password": + err := share.Update(map[string]interface{}{"password": service.Value}) + if err != nil { + return serializer.Err(serializer.CodeDBError, "无法更新分享密码", err) + } + case "preview_enabled": + value := service.Value == "true" + err := share.Update(map[string]interface{}{"preview_enabled": value}) + if err != nil { + return serializer.Err(serializer.CodeDBError, "无法更新分享属性", err) + } + return serializer.Response{ + Data: value, + } + } + return serializer.Response{ + Data: service.Value, + } +} + // Create 创建新分享 func (service *ShareCreateService) Create(c *gin.Context) serializer.Response { userCtx, _ := c.Get("user") @@ -32,8 +78,9 @@ func (service *ShareCreateService) Create(c *gin.Context) serializer.Response { // 源对象真实ID var ( - sourceID uint - err error + sourceID uint + sourceName string + err error ) if service.IsDir { sourceID, err = hashid.DecodeHashID(service.SourceID, hashid.FolderID) @@ -50,11 +97,15 @@ func (service *ShareCreateService) Create(c *gin.Context) serializer.Response { folder, err := model.GetFoldersByIDs([]uint{sourceID}, user.ID) if err != nil || len(folder) == 0 { exist = false + } else { + sourceName = folder[0].Name } } else { file, err := model.GetFilesByIDs([]uint{sourceID}, user.ID) if err != nil || len(file) == 0 { exist = false + } else { + sourceName = file[0].Name } } if !exist { @@ -69,6 +120,7 @@ func (service *ShareCreateService) Create(c *gin.Context) serializer.Response { Score: service.Score, RemainDownloads: -1, PreviewEnabled: service.Preview, + SourceName: sourceName, } // 如果开启了自动过期 diff --git a/service/share/visit.go b/service/share/visit.go index 546d8cb..b0077d1 100644 --- a/service/share/visit.go +++ b/service/share/visit.go @@ -33,6 +33,40 @@ type ArchiveService struct { Dirs []string `json:"dirs" binding:"exists"` } +// ShareListService 列出分享 +type ShareListService struct { + Page uint `form:"page" binding:"required,min=1"` + OrderBy string `form:"order_by" binding:"required,eq=created_at|eq=downloads|eq=views"` + Order string `form:"order" binding:"required,eq=DESC|eq=ASC"` + Keywords string `form:"keywords"` +} + +// Search 搜索公共分享 +func (service *ShareListService) Search(c *gin.Context) serializer.Response { + // 列出分享 + shares, total := model.SearchShares(int(service.Page), 18, service.OrderBy+" "+ + service.Order, service.Keywords) + // 列出分享对应的文件 + for i := 0; i < len(shares); i++ { + shares[i].Source() + } + + return serializer.BuildShareList(shares, total) +} + +// List 列出用户分享 +func (service *ShareListService) List(c *gin.Context, user *model.User) serializer.Response { + // 列出分享 + shares, total := model.ListShares(user.ID, int(service.Page), 18, service.OrderBy+" "+ + service.Order, false) + // 列出分享对应的文件 + for i := 0; i < len(shares); i++ { + shares[i].Source() + } + + return serializer.BuildShareList(shares, total) +} + // Get 获取分享内容 func (service *ShareGetService) Get(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share")