From 15f4b1819b79a2a8094ddf5d9ac642ab2f52e1ad Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Wed, 12 Feb 2020 11:38:04 +0800 Subject: [PATCH] Feat: add and delete file tags --- models/migration.go | 2 +- models/tag.go | 53 ++++++++++++++++++++++ pkg/hashid/hash.go | 1 + pkg/serializer/setting.go | 3 +- pkg/serializer/user.go | 34 +++++++++++++++ routers/controllers/site.go | 2 +- routers/controllers/tag.go | 39 +++++++++++++++++ routers/router.go | 11 +++++ service/explorer/search.go | 17 ++++++++ service/explorer/tag.go | 87 +++++++++++++++++++++++++++++++++++++ 10 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 models/tag.go create mode 100644 routers/controllers/tag.go create mode 100644 service/explorer/tag.go diff --git a/models/migration.go b/models/migration.go index 4600bdb..94f8a3d 100644 --- a/models/migration.go +++ b/models/migration.go @@ -30,7 +30,7 @@ func migration() { DB = DB.Set("gorm:table_options", "ENGINE=InnoDB") } DB.AutoMigrate(&User{}, &Setting{}, &Group{}, &Policy{}, &Folder{}, &File{}, &StoragePack{}, &Share{}, - &Task{}, &Download{}) + &Task{}, &Download{}, &Tag{}) // 创建初始存储策略 addDefaultPolicy() diff --git a/models/tag.go b/models/tag.go new file mode 100644 index 0000000..92c9a9e --- /dev/null +++ b/models/tag.go @@ -0,0 +1,53 @@ +package model + +import ( + "github.com/HFO4/cloudreve/pkg/util" + "github.com/jinzhu/gorm" +) + +// Tag 用户自定义标签 +type Tag struct { + gorm.Model + Name string // 标签名 + Icon string // 图标标识 + Color string // 图标颜色 + Type int // 标签类型(文件分类/目录直达) + Expression string `gorm:"type:text"` // 搜索表表达式/直达路径 + UserID uint // 创建者ID +} + +const ( + // FileTagType 文件分类标签 + FileTagType = iota + // DirectoryLinkType 目录快捷方式标签 + DirectoryLinkType +) + +// Create 创建标签记录 +func (tag *Tag) Create() (uint, error) { + if err := DB.Create(tag).Error; err != nil { + util.Log().Warning("无法插入离线下载记录, %s", err) + return 0, err + } + return tag.ID, nil +} + +// DeleteTagByID 根据给定ID和用户ID删除标签 +func DeleteTagByID(id, uid uint) error { + result := DB.Where("id = ? and user_id = ?", id, uid).Delete(&Tag{}) + return result.Error +} + +// GetTagsByUID 根据用户ID查找标签 +func GetTagsByUID(uid uint) ([]Tag, error) { + var tag []Tag + result := DB.Where("user_id = ?", uid).Find(&tag) + return tag, result.Error +} + +// GetTagsByID 根据ID查找标签 +func GetTagsByID(id, uid uint) (*Tag, error) { + var tag Tag + result := DB.Where("user_id = ? and id = ?", uid, id).First(&tag) + return &tag, result.Error +} diff --git a/pkg/hashid/hash.go b/pkg/hashid/hash.go index 14b4c10..5becb06 100644 --- a/pkg/hashid/hash.go +++ b/pkg/hashid/hash.go @@ -12,6 +12,7 @@ const ( UserID // 用户 FileID // 文件ID FolderID // 目录ID + TagID // 标签ID ) var ( diff --git a/pkg/serializer/setting.go b/pkg/serializer/setting.go index dcc9a2c..90c9885 100644 --- a/pkg/serializer/setting.go +++ b/pkg/serializer/setting.go @@ -34,7 +34,7 @@ func BuildSiteConfig(settings map[string]string, user *model.User) Response { } else { userRes = BuildUser(*model.NewAnonymousUser()) } - return Response{ + res := Response{ Data: SiteConfig{ SiteName: checkSettingValue(settings, "siteName"), LoginCaptcha: model.IsTrueVal(checkSettingValue(settings, "login_captcha")), @@ -50,4 +50,5 @@ func BuildSiteConfig(settings map[string]string, user *model.User) Response { ShareViewMethod: checkSettingValue(settings, "share_view_method"), User: userRes, }} + return res } diff --git a/pkg/serializer/user.go b/pkg/serializer/user.go index 8c1dee4..e84905e 100644 --- a/pkg/serializer/user.go +++ b/pkg/serializer/user.go @@ -3,6 +3,7 @@ package serializer import ( "fmt" "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/hashid" ) // CheckLogin 检查登录 @@ -25,6 +26,7 @@ type User struct { Score int `json:"score"` Policy policy `json:"policy"` Group group `json:"group"` + Tags []tag `json:"tags"` } type policy struct { @@ -46,6 +48,15 @@ type group struct { CompressEnabled bool `json:"compress"` } +type tag struct { + ID string `json:"id"` + Name string `json:"name"` + Icon string `json:"icon"` + Color string `json:"color"` + Type int `json:"type"` + Expression string `json:"expression"` +} + type storage struct { Used uint64 `json:"used"` Free uint64 `json:"free"` @@ -54,6 +65,7 @@ type storage struct { // BuildUser 序列化用户 func BuildUser(user model.User) User { + tags, _ := model.GetTagsByUID(user.ID) return User{ ID: user.ID, Email: user.Email, @@ -80,6 +92,7 @@ func BuildUser(user model.User) User { ShareDownload: user.Group.OptionsSerialized.ShareDownload, CompressEnabled: user.Group.OptionsSerialized.ArchiveTask, }, + Tags: BuildTagRes(tags), } } @@ -107,3 +120,24 @@ func BuildUserStorageResponse(user model.User) Response { Data: storageResp, } } + +// BuildTagRes 构建标签列表 +func BuildTagRes(tags []model.Tag) []tag { + res := make([]tag, 0, len(tags)) + for i := 0; i < len(tags); i++ { + newTag := tag{ + ID: hashid.HashID(tags[i].ID, hashid.TagID), + Name: tags[i].Name, + Icon: tags[i].Icon, + Color: tags[i].Color, + Type: tags[i].Type, + Expression: tags[i].Expression, + } + if newTag.Type == 0 { + newTag.Expression = "" + } + res = append(res, newTag) + } + + return res +} diff --git a/routers/controllers/site.go b/routers/controllers/site.go index 5f49a04..a3d64b7 100644 --- a/routers/controllers/site.go +++ b/routers/controllers/site.go @@ -27,7 +27,7 @@ func SiteConfig(c *gin.Context) { "share_view_method", ) - // 如果已登录,则同时返回用户信息 + // 如果已登录,则同时返回用户信息和标签 user, _ := c.Get("user") if user, ok := user.(*model.User); ok { c.JSON(200, serializer.BuildSiteConfig(siteConfig, user)) diff --git a/routers/controllers/tag.go b/routers/controllers/tag.go new file mode 100644 index 0000000..5f67f3f --- /dev/null +++ b/routers/controllers/tag.go @@ -0,0 +1,39 @@ +package controllers + +import ( + "github.com/HFO4/cloudreve/service/explorer" + "github.com/gin-gonic/gin" +) + +// CreateFilterTag 创建文件分类标签 +func CreateFilterTag(c *gin.Context) { + var service explorer.FilterTagCreateService + if err := c.ShouldBindJSON(&service); err == nil { + res := service.Create(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + +// CreateLinkTag 创建目录快捷方式标签 +func CreateLinkTag(c *gin.Context) { + var service explorer.LinkTagCreateService + if err := c.ShouldBindJSON(&service); err == nil { + res := service.Create(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + +// DeleteTag 删除标签 +func DeleteTag(c *gin.Context) { + var service explorer.TagService + if err := c.ShouldBindUri(&service); err == nil { + res := service.Delete(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} diff --git a/routers/router.go b/routers/router.go index 9f730b2..9cd34ef 100644 --- a/routers/router.go +++ b/routers/router.go @@ -329,6 +329,17 @@ func InitMasterRouter() *gin.Engine { ) } + // 用户标签 + tag := auth.Group("tag") + { + // 创建文件分类标签 + tag.POST("filter", controllers.CreateFilterTag) + // 创建目录快捷方式标签 + tag.POST("link", controllers.CreateLinkTag) + // 删除标签 + tag.DELETE(":id", middleware.HashID(hashid.TagID), controllers.DeleteTag) + } + } } diff --git a/service/explorer/search.go b/service/explorer/search.go index 52ca586..4cb62fd 100644 --- a/service/explorer/search.go +++ b/service/explorer/search.go @@ -2,9 +2,12 @@ package explorer import ( "context" + model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/gin-gonic/gin" + "strings" ) // ItemSearchService 文件搜索服务 @@ -33,6 +36,20 @@ func (service *ItemSearchService) Search(c *gin.Context) serializer.Response { return service.SearchKeywords(c, fs, "%.mp3", "%.flac", "%.ape", "%.wav", "%.acc", "%.ogg", "%.midi", "%.mid") case "doc": return service.SearchKeywords(c, fs, "%.txt", "%.md", "%.pdf", "%.doc", "%.docx", "%.ppt", "%.pptx", "%.xls", "%.xlsx", "%.pub") + case "tag": + if tid, err := hashid.DecodeHashID(service.Keywords, hashid.TagID); err == nil { + if tag, err := model.GetTagsByID(tid, fs.User.ID); err == nil { + if tag.Type == model.FileTagType { + exp := strings.Split(tag.Expression, "\n") + expInput := make([]interface{}, len(exp)) + for i := 0; i < len(exp); i++ { + expInput[i] = exp[i] + } + return service.SearchKeywords(c, fs, expInput...) + } + } + } + return serializer.Err(serializer.CodeNotFound, "标签不存在", nil) default: return serializer.ParamErr("未知搜索类型", nil) } diff --git a/service/explorer/tag.go b/service/explorer/tag.go new file mode 100644 index 0000000..4accb08 --- /dev/null +++ b/service/explorer/tag.go @@ -0,0 +1,87 @@ +package explorer + +import ( + "fmt" + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/hashid" + "github.com/HFO4/cloudreve/pkg/serializer" + "github.com/gin-gonic/gin" + "strings" +) + +// FilterTagCreateService 文件分类标签创建服务 +type FilterTagCreateService struct { + Expression string `json:"expression" binding:"required,min=1,max=65535"` + Icon string `json:"icon" binding:"required,min=1,max=255"` + Name string `json:"name" binding:"required,min=1,max=255"` + Color string `json:"color" binding:"hexcolor|rgb|rgba|hsl"` +} + +// LinkTagCreateService 目录快捷方式标签创建服务 +type LinkTagCreateService struct { + Path string `json:"path" binding:"required,min=1,max=65535"` + Name string `json:"name" binding:"required,min=1,max=255"` +} + +// TagService 标签服务 +type TagService struct { +} + +// Delete 删除标签 +func (service *TagService) Delete(c *gin.Context, user *model.User) serializer.Response { + id, _ := c.Get("object_id") + if err := model.DeleteTagByID(id.(uint), user.ID); err != nil { + return serializer.Err(serializer.CodeDBError, "删除失败", err) + } + return serializer.Response{} +} + +// Create 创建标签 +func (service *LinkTagCreateService) Create(c *gin.Context, user *model.User) serializer.Response { + // 创建标签 + tag := model.Tag{ + Name: service.Name, + Icon: "FolderHeartOutline", + Type: model.DirectoryLinkType, + Expression: service.Path, + UserID: user.ID, + } + id, err := tag.Create() + if err != nil { + return serializer.Err(serializer.CodeDBError, "标签创建失败", err) + } + + return serializer.Response{ + Data: hashid.HashID(id, hashid.TagID), + } +} + +// Create 创建标签 +func (service *FilterTagCreateService) Create(c *gin.Context, user *model.User) serializer.Response { + // 分割表达式,将通配符转换为SQL内的% + expressions := strings.Split(service.Expression, "\n") + for i := 0; i < len(expressions); i++ { + expressions[i] = strings.ReplaceAll(expressions[i], "*", "%") + if expressions[i] == "" { + return serializer.ParamErr(fmt.Sprintf("第 %d 行包含空的匹配表达式", i+1), nil) + } + } + + // 创建标签 + tag := model.Tag{ + Name: service.Name, + Icon: service.Icon, + Color: service.Color, + Type: model.FileTagType, + Expression: strings.Join(expressions, "\n"), + UserID: user.ID, + } + id, err := tag.Create() + if err != nil { + return serializer.Err(serializer.CodeDBError, "标签创建失败", err) + } + + return serializer.Response{ + Data: hashid.HashID(id, hashid.TagID), + } +}