From fcba697a555838c099ed742c38b59f5b794dd220 Mon Sep 17 00:00:00 2001 From: miaowuawa <2484667023@qq.com> Date: Sun, 1 Mar 2026 10:54:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A8=E6=83=85=E5=8C=85=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20UserEmoji?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auto/api/v1/priv.go | 144 +++++++++++++ go.mod | 4 +- go.sum | 4 - internal/core/core.go | 3 + internal/core/emoji.go | 39 ++++ internal/dao/jinzhu/dbr/emoji.go | 170 +++++++++++++++ internal/dao/jinzhu/emoji.go | 193 ++++++++++++++++++ internal/dao/jinzhu/jinzhu.go | 2 + internal/model/web/core.go | 83 ++++++++ internal/model/web/xerror.go | 5 + internal/servants/web/priv.go | 175 ++++++++++++++++ mirc/web/v1/priv.go | 22 ++ .../mysql/0021_add_emoji_tables.down.sql | 7 + .../mysql/0021_add_emoji_tables.up.sql | 36 ++++ .../postgres/0021_add_emoji_tables.down.sql | 2 + .../postgres/0021_add_emoji_tables.up.sql | 31 +++ .../sqlite3/0021_add_emoji_tables.down.sql | 2 + .../sqlite3/0021_add_emoji_tables.up.sql | 31 +++ 18 files changed, 946 insertions(+), 7 deletions(-) create mode 100644 internal/core/emoji.go create mode 100644 internal/dao/jinzhu/dbr/emoji.go create mode 100644 internal/dao/jinzhu/emoji.go create mode 100644 scripts/migration/mysql/0021_add_emoji_tables.down.sql create mode 100644 scripts/migration/mysql/0021_add_emoji_tables.up.sql create mode 100644 scripts/migration/postgres/0021_add_emoji_tables.down.sql create mode 100644 scripts/migration/postgres/0021_add_emoji_tables.up.sql create mode 100644 scripts/migration/sqlite3/0021_add_emoji_tables.down.sql create mode 100644 scripts/migration/sqlite3/0021_add_emoji_tables.up.sql diff --git a/auto/api/v1/priv.go b/auto/api/v1/priv.go index 6fe84634..8cdb312d 100644 --- a/auto/api/v1/priv.go +++ b/auto/api/v1/priv.go @@ -56,6 +56,15 @@ type Priv interface { DownloadAttachmentPrecheck(*web.DownloadAttachmentPrecheckReq) (*web.DownloadAttachmentPrecheckResp, error) UploadAttachment(*web.UploadAttachmentReq) (*web.UploadAttachmentResp, error) + // 表情包相关API + UploadEmoji(*web.UploadEmojiReq) (*web.UploadEmojiResp, error) + GetEmojiList(*web.GetEmojiListReq) (*web.GetEmojiListResp, error) + GetUserEmojiList(*web.GetUserEmojiListReq) (*web.GetUserEmojiListResp, error) + GetUserCollectedEmojiList(*web.GetUserCollectedEmojiListReq) (*web.GetUserCollectedEmojiListResp, error) + CollectEmoji(*web.CollectEmojiReq) (*web.CollectEmojiResp, error) + UncollectEmoji(*web.UncollectEmojiReq) (*web.UncollectEmojiResp, error) + DeleteEmoji(*web.DeleteEmojiReq) error + mustEmbedUnimplementedPrivServant() } @@ -594,6 +603,112 @@ func RegisterPrivServant(e *gin.Engine, s Priv, m ...PrivChain) { resp, err := s.UploadAttachment(req) s.Render(c, resp, err) }) + + // 表情包相关路由 + router.Handle("POST", "emoji", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.UploadEmojiReq) + var bv _binding_ = req + if err := bv.Bind(c); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.UploadEmoji(req) + s.Render(c, resp, err) + }) + + router.Handle("GET", "emoji/list", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.GetEmojiListReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.GetEmojiList(req) + s.Render(c, resp, err) + }) + + router.Handle("GET", "emoji/user", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.GetUserEmojiListReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.GetUserEmojiList(req) + s.Render(c, resp, err) + }) + + router.Handle("GET", "emoji/collected", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.GetUserCollectedEmojiListReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.GetUserCollectedEmojiList(req) + s.Render(c, resp, err) + }) + + router.Handle("POST", "emoji/collect", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.CollectEmojiReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.CollectEmoji(req) + s.Render(c, resp, err) + }) + + router.Handle("POST", "emoji/uncollect", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.UncollectEmojiReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + resp, err := s.UncollectEmoji(req) + s.Render(c, resp, err) + }) + + router.Handle("DELETE", "emoji", func(c *gin.Context) { + select { + case <-c.Request.Context().Done(): + return + default: + } + req := new(web.DeleteEmojiReq) + if err := s.Bind(c, req); err != nil { + s.Render(c, nil, err) + return + } + s.Render(c, nil, s.DeleteEmoji(req)) + }) } // UnimplementedPrivServant can be embedded to have forward compatible implementations. @@ -751,6 +866,35 @@ func (UnimplementedPrivServant) UploadAttachment(req *web.UploadAttachmentReq) ( return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } +// 表情包相关方法 +func (UnimplementedPrivServant) UploadEmoji(req *web.UploadEmojiReq) (*web.UploadEmojiResp, error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedPrivServant) GetEmojiList(req *web.GetEmojiListReq) (*web.GetEmojiListResp, error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedPrivServant) GetUserEmojiList(req *web.GetUserEmojiListReq) (*web.GetUserEmojiListResp, error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedPrivServant) GetUserCollectedEmojiList(req *web.GetUserCollectedEmojiListReq) (*web.GetUserCollectedEmojiListResp, error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedPrivServant) CollectEmoji(req *web.CollectEmojiReq) (*web.CollectEmojiResp, error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedPrivServant) UncollectEmoji(req *web.UncollectEmojiReq) (*web.UncollectEmojiResp, error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + +func (UnimplementedPrivServant) DeleteEmoji(req *web.DeleteEmojiReq) error { + return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +} + func (UnimplementedPrivServant) mustEmbedUnimplementedPrivServant() {} // UnimplementedPrivChain can be embedded to have forward compatible implementations. diff --git a/go.mod b/go.mod index 5a76d8c2..6610fa3c 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/grafana/pyroscope-go v1.2.2 github.com/huaweicloud/huaweicloud-sdk-go-obs v3.25.4+incompatible github.com/json-iterator/go v1.1.12 - github.com/lionsoul2014/ip2region v1.7.0 + github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 github.com/meilisearch/meilisearch-go v0.27.2 github.com/minio/minio-go/v7 v7.0.84 @@ -120,7 +120,6 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -129,7 +128,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mohong122/ip2region v0.0.0-20190505055455-f4ef24f6b03d // indirect github.com/mozillazg/go-httpheader v0.2.1 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect diff --git a/go.sum b/go.sum index a88ea05c..9a162387 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lionsoul2014/ip2region v1.7.0 h1:LUNcu4rsodhet/6wd3p5QMmtQUxPBUm+N9sI2Tw7BFs= -github.com/lionsoul2014/ip2region v1.7.0/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI= github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e h1:1+rVed4OnYgQCg934wWHms6J7aEbyQ7TaR/fSYhJmLA= github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU= github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5ATTo469PQPkqzdoU7be46ryiCDO3boc= @@ -232,8 +230,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohong122/ip2region v0.0.0-20190505055455-f4ef24f6b03d h1:Fgk2vj/4uaCke01N4Z91X2gFv3bizQC/5Z0jpXVLWWA= -github.com/mohong122/ip2region v0.0.0-20190505055455-f4ef24f6b03d/go.mod h1:nWEvmMDv872jkCVATPoiQKQvB6NYSzra9gCN5OgS/Mg= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= diff --git a/internal/core/core.go b/internal/core/core.go index 4f1e17a1..8165d6e5 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -44,6 +44,9 @@ type DataService interface { // 实用性服务 //StickerService + + // 表情包服务 + EmojiService } // WebDataServantA Web数据服务集成(版本A) diff --git a/internal/core/emoji.go b/internal/core/emoji.go new file mode 100644 index 00000000..8fecb67f --- /dev/null +++ b/internal/core/emoji.go @@ -0,0 +1,39 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package core + +import ( + "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" +) + +// EmojiService 表情包服务接口 +type EmojiService interface { + // 上传表情包 + UploadEmoji(userID int64, name, url string, width, height int, size int64, emojiType string) (*dbr.Emoji, error) + + // 获取表情包列表 + GetEmojiList(offset, limit int) ([]*dbr.Emoji, error) + + // 获取用户上传的表情包列表 + GetUserEmojiList(userID int64, offset, limit int) ([]*dbr.Emoji, error) + + // 获取用户收藏的表情包列表 + GetUserCollectedEmojiList(userID int64, offset, limit int) ([]*dbr.Emoji, error) + + // 收藏表情包 + CollectEmoji(userID, emojiID int64) error + + // 取消收藏表情包 + UncollectEmoji(userID, emojiID int64) error + + // 检查表情包是否被用户收藏 + IsEmojiCollected(userID, emojiID int64) (bool, error) + + // 获取表情包详情 + GetEmojiByID(emojiID int64) (*dbr.Emoji, error) + + // 删除表情包 + DeleteEmoji(userID, emojiID int64) error +} diff --git a/internal/dao/jinzhu/dbr/emoji.go b/internal/dao/jinzhu/dbr/emoji.go new file mode 100644 index 00000000..1374fbb5 --- /dev/null +++ b/internal/dao/jinzhu/dbr/emoji.go @@ -0,0 +1,170 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package dbr + +import ( + "gorm.io/gorm" +) + +// Emoji 表情包模型 +type Emoji struct { + *Model + UserID int64 `json:"user_id"` + Name string `json:"name"` + URL string `json:"url"` + Width int `json:"width"` + Height int `json:"height"` + Size int64 `json:"size"` + Type string `json:"type"` + CollectionCount int64 `json:"collection_count"` +} + +// UserEmoji 用户表情包收藏模型 +type UserEmoji struct { + *Model + UserID int64 `json:"user_id"` + EmojiID int64 `json:"emoji_id"` +} + +// EmojiFormated 格式化的表情包信息 +type EmojiFormated struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + Name string `json:"name"` + URL string `json:"url"` + Width int `json:"width"` + Height int `json:"height"` + Size int64 `json:"size"` + Type string `json:"type"` + CollectionCount int64 `json:"collection_count"` + IsCollected bool `json:"is_collected"` + CreatedOn int64 `json:"created_on"` +} + +// Format 格式化表情包信息 +func (e *Emoji) Format() *EmojiFormated { + if e.Model != nil { + return &EmojiFormated{ + ID: e.ID, + UserID: e.UserID, + Name: e.Name, + URL: e.URL, + Width: e.Width, + Height: e.Height, + Size: e.Size, + Type: e.Type, + CollectionCount: e.CollectionCount, + IsCollected: false, + CreatedOn: e.CreatedOn, + } + } + return nil +} + +// Get 获取单个表情包 +func (e *Emoji) Get(db *gorm.DB) (*Emoji, error) { + var emoji Emoji + if e.Model != nil && e.Model.ID > 0 { + db = db.Where("id = ? AND is_del = ?", e.Model.ID, 0) + } else { + return nil, gorm.ErrRecordNotFound + } + + err := db.First(&emoji).Error + if err != nil { + return nil, err + } + + return &emoji, nil +} + +// List 获取表情包列表 +func (e *Emoji) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Emoji, error) { + var emojis []*Emoji + var err error + + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + + for k, v := range *conditions { + if k == "ORDER" { + db = db.Order(v) + } else { + db = db.Where(k, v) + } + } + + if err = db.Where("is_del = ?", 0).Find(&emojis).Error; err != nil { + return nil, err + } + + return emojis, nil +} + +// Create 创建表情包 +func (e *Emoji) Create(db *gorm.DB) (*Emoji, error) { + err := db.Create(&e).Error + return e, err +} + +// Update 更新表情包 +func (e *Emoji) Update(db *gorm.DB) error { + return db.Model(&Emoji{}).Where("id = ? AND is_del = ?", e.Model.ID, 0).Save(e).Error +} + +// Delete 删除表情包 +func (e *Emoji) Delete(db *gorm.DB) error { + return db.Delete(&e).Error +} + +// IncrementCollectionCount 增加收藏数 +func (e *Emoji) IncrementCollectionCount(db *gorm.DB) error { + return db.Model(&Emoji{}).Where("id = ? AND is_del = ?", e.Model.ID, 0).Update("collection_count", gorm.Expr("collection_count + ?", 1)).Error +} + +// DecrementCollectionCount 减少收藏数 +func (e *Emoji) DecrementCollectionCount(db *gorm.DB) error { + return db.Model(&Emoji{}).Where("id = ? AND is_del = ? AND collection_count > 0", e.Model.ID, 0).Update("collection_count", gorm.Expr("collection_count - ?", 1)).Error +} + +// GetUserEmoji 获取用户的表情包收藏 +func (ue *UserEmoji) GetUserEmoji(db *gorm.DB) (*UserEmoji, error) { + var userEmoji UserEmoji + err := db.Where("user_id = ? AND emoji_id = ? AND is_del = ?", ue.UserID, ue.EmojiID, 0).First(&userEmoji).Error + if err != nil { + return nil, err + } + return &userEmoji, nil +} + +// ListUserEmojis 获取用户的表情包收藏列表 +func (ue *UserEmoji) ListUserEmojis(db *gorm.DB, userID int64, offset, limit int) ([]*Emoji, error) { + var emojis []*Emoji + + query := db.Table("p_emoji").Select("p_emoji.*").Joins("JOIN p_user_emoji ON p_emoji.id = p_user_emoji.emoji_id").Where("p_user_emoji.user_id = ? AND p_user_emoji.is_del = ? AND p_emoji.is_del = ?", userID, 0, 0) + + if offset >= 0 && limit > 0 { + query = query.Offset(offset).Limit(limit) + } + + err := query.Order("p_user_emoji.created_on DESC").Find(&emojis).Error + if err != nil { + return nil, err + } + + return emojis, nil +} + +// Create 创建用户表情包收藏 +func (ue *UserEmoji) Create(db *gorm.DB) (*UserEmoji, error) { + err := db.Create(&ue).Error + return ue, err +} + +// Delete 删除用户表情包收藏 +func (ue *UserEmoji) Delete(db *gorm.DB) error { + return db.Where("user_id = ? AND emoji_id = ? AND is_del = ?", ue.UserID, ue.EmojiID, 0).Delete(&UserEmoji{}).Error +} diff --git a/internal/dao/jinzhu/emoji.go b/internal/dao/jinzhu/emoji.go new file mode 100644 index 00000000..77c27f70 --- /dev/null +++ b/internal/dao/jinzhu/emoji.go @@ -0,0 +1,193 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package jinzhu + +import ( + "errors" + + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" + "gorm.io/gorm" +) + +var ( + _ core.EmojiService = (*emojiSrv)(nil) +) + +const ( + _emoji_ = "p_emoji" + _user_emoji_ = "p_user_emoji" +) + +type emojiSrv struct { + db *gorm.DB +} + +func newEmojiService(db *gorm.DB) core.EmojiService { + return &emojiSrv{ + db: db, + } +} + +// UploadEmoji 上传表情包 +func (s *emojiSrv) UploadEmoji(userID int64, name, url string, width, height int, size int64, emojiType string) (*dbr.Emoji, error) { + emoji := &dbr.Emoji{ + Model: &dbr.Model{}, + UserID: userID, + Name: name, + URL: url, + Width: width, + Height: height, + Size: size, + Type: emojiType, + CollectionCount: 0, + } + + return emoji.Create(s.db) +} + +// GetEmojiList 获取表情包列表 +func (s *emojiSrv) GetEmojiList(offset, limit int) ([]*dbr.Emoji, error) { + emoji := &dbr.Emoji{} + conditions := &dbr.ConditionsT{ + "ORDER": "collection_count DESC, created_on DESC", + } + + return emoji.List(s.db, conditions, offset, limit) +} + +// GetUserEmojiList 获取用户上传的表情包列表 +func (s *emojiSrv) GetUserEmojiList(userID int64, offset, limit int) ([]*dbr.Emoji, error) { + emoji := &dbr.Emoji{} + conditions := &dbr.ConditionsT{ + "user_id = ?": userID, + "ORDER": "created_on DESC", + } + + return emoji.List(s.db, conditions, offset, limit) +} + +// GetUserCollectedEmojiList 获取用户收藏的表情包列表 +func (s *emojiSrv) GetUserCollectedEmojiList(userID int64, offset, limit int) ([]*dbr.Emoji, error) { + userEmoji := &dbr.UserEmoji{} + return userEmoji.ListUserEmojis(s.db, userID, offset, limit) +} + +// CollectEmoji 收藏表情包 +func (s *emojiSrv) CollectEmoji(userID, emojiID int64) error { + // 检查表情包是否存在 + emoji := &dbr.Emoji{ + Model: &dbr.Model{ + ID: emojiID, + }, + } + _, err := emoji.Get(s.db) + if err != nil { + return err + } + + // 检查是否已经收藏 + userEmoji := &dbr.UserEmoji{ + UserID: userID, + EmojiID: emojiID, + } + _, err = userEmoji.GetUserEmoji(s.db) + if err == nil { + return errors.New("emoji already collected") + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + // 创建收藏记录 + userEmoji = &dbr.UserEmoji{ + Model: &dbr.Model{}, + UserID: userID, + EmojiID: emojiID, + } + _, err = userEmoji.Create(s.db) + if err != nil { + return err + } + + // 增加表情包收藏数 + return emoji.IncrementCollectionCount(s.db) +} + +// UncollectEmoji 取消收藏表情包 +func (s *emojiSrv) UncollectEmoji(userID, emojiID int64) error { + // 检查是否已经收藏 + userEmoji := &dbr.UserEmoji{ + UserID: userID, + EmojiID: emojiID, + } + _, err := userEmoji.GetUserEmoji(s.db) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("emoji not collected") + } + return err + } + + // 删除收藏记录 + err = userEmoji.Delete(s.db) + if err != nil { + return err + } + + // 减少表情包收藏数 + emoji := &dbr.Emoji{ + Model: &dbr.Model{ + ID: emojiID, + }, + } + return emoji.DecrementCollectionCount(s.db) +} + +// IsEmojiCollected 检查表情包是否被用户收藏 +func (s *emojiSrv) IsEmojiCollected(userID, emojiID int64) (bool, error) { + userEmoji := &dbr.UserEmoji{ + UserID: userID, + EmojiID: emojiID, + } + _, err := userEmoji.GetUserEmoji(s.db) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return false, nil + } + return false, err + } + return true, nil +} + +// GetEmojiByID 获取表情包详情 +func (s *emojiSrv) GetEmojiByID(emojiID int64) (*dbr.Emoji, error) { + emoji := &dbr.Emoji{ + Model: &dbr.Model{ + ID: emojiID, + }, + } + return emoji.Get(s.db) +} + +// DeleteEmoji 删除表情包 +func (s *emojiSrv) DeleteEmoji(userID, emojiID int64) error { + // 检查表情包是否存在且属于该用户 + emoji := &dbr.Emoji{ + Model: &dbr.Model{ + ID: emojiID, + }, + } + emoji, err := emoji.Get(s.db) + if err != nil { + return err + } + + if emoji.UserID != userID { + return errors.New("permission denied") + } + + // 删除表情包 + return emoji.Delete(s.db) +} diff --git a/internal/dao/jinzhu/jinzhu.go b/internal/dao/jinzhu/jinzhu.go index 16b6d64a..4f4c7beb 100644 --- a/internal/dao/jinzhu/jinzhu.go +++ b/internal/dao/jinzhu/jinzhu.go @@ -47,6 +47,7 @@ type dataSrv struct { core.UserRelationService core.SecurityService core.AttachmentCheckService + core.EmojiService } type webDataSrvA struct { @@ -83,6 +84,7 @@ func NewDataService() (core.DataService, core.VersionInfo) { UserRelationService: newUserRelationService(db), SecurityService: newSecurityService(db, pvs), AttachmentCheckService: security.NewAttachmentCheckService(), + EmojiService: newEmojiService(db), } return cache.NewCacheDataService(ds), ds } diff --git a/internal/model/web/core.go b/internal/model/web/core.go index a0b46db1..916c961a 100644 --- a/internal/model/web/core.go +++ b/internal/model/web/core.go @@ -182,3 +182,86 @@ func (r *TweetStarStatusReq) Bind(c *gin.Context) error { type StreamMessagesReq struct { SimpleInfo `json:"-" binding:"-"` } + +// 表情包相关请求和响应 + +// UploadEmojiReq 上传表情包请求 +type UploadEmojiReq struct { + BaseInfo `json:"-" binding:"-"` + Name string `form:"name" binding:"required"` + File interface{} `form:"file" binding:"required"` + FileExt string `form:"file_ext" binding:"required"` + FileSize int64 `form:"file_size" binding:"required"` + ContentType string `form:"content_type" binding:"required"` + Width int `form:"width" binding:"required"` + Height int `form:"height" binding:"required"` +} + +// UploadEmojiResp 上传表情包响应 +type UploadEmojiResp struct { + ID int64 `json:"id"` + Name string `json:"name"` + URL string `json:"url"` +} + +// GetEmojiListReq 获取表情包列表请求 +type GetEmojiListReq struct { + SimpleInfo `json:"-" binding:"-"` + BasePageInfo +} + +// GetEmojiListResp 获取表情包列表响应 +type GetEmojiListResp struct { + base.PageResp +} + +// GetUserEmojiListReq 获取用户上传的表情包列表请求 +type GetUserEmojiListReq struct { + SimpleInfo `json:"-" binding:"-"` + BasePageInfo + UserID int64 `form:"user_id" binding:"required"` +} + +// GetUserEmojiListResp 获取用户上传的表情包列表响应 +type GetUserEmojiListResp struct { + base.PageResp +} + +// GetUserCollectedEmojiListReq 获取用户收藏的表情包列表请求 +type GetUserCollectedEmojiListReq struct { + SimpleInfo `json:"-" binding:"-"` + BasePageInfo +} + +// GetUserCollectedEmojiListResp 获取用户收藏的表情包列表响应 +type GetUserCollectedEmojiListResp struct { + base.PageResp +} + +// CollectEmojiReq 收藏表情包请求 +type CollectEmojiReq struct { + SimpleInfo `json:"-" binding:"-"` + EmojiID int64 `json:"emoji_id" binding:"required"` +} + +// CollectEmojiResp 收藏表情包响应 +type CollectEmojiResp struct { + Status bool `json:"status"` +} + +// UncollectEmojiReq 取消收藏表情包请求 +type UncollectEmojiReq struct { + SimpleInfo `json:"-" binding:"-"` + EmojiID int64 `json:"emoji_id" binding:"required"` +} + +// UncollectEmojiResp 取消收藏表情包响应 +type UncollectEmojiResp struct { + Status bool `json:"status"` +} + +// DeleteEmojiReq 删除表情包请求 +type DeleteEmojiReq struct { + BaseInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} diff --git a/internal/model/web/xerror.go b/internal/model/web/xerror.go index 0bbdd0fc..24f43703 100644 --- a/internal/model/web/xerror.go +++ b/internal/model/web/xerror.go @@ -115,6 +115,11 @@ var ( ErrFileInvalidExt = xerror.NewError(10201, "文件类型不合法") ErrFileInvalidSize = xerror.NewError(10202, "文件大小超限") + // 表情包相关错误 + ErrCollectEmojiFailed = xerror.NewError(12001, "收藏表情包失败") + ErrUncollectEmojiFailed = xerror.NewError(12002, "取消收藏表情包失败") + ErrDeleteEmojiFailed = xerror.NewError(12003, "删除表情包失败") + // 长文章相关错误 ErrCreateArticle = xerror.NewError(11001, "长文章创建失败") ErrUpdateArticle = xerror.NewError(11002, "长文章更新失败") diff --git a/internal/servants/web/priv.go b/internal/servants/web/priv.go index 0c8cc199..613f8ea6 100644 --- a/internal/servants/web/priv.go +++ b/internal/servants/web/priv.go @@ -898,6 +898,181 @@ func (s *privSrv) buyPostAttachment(post *ms.Post, user *ms.User) error { return nil } +// UploadEmoji 上传表情包 +func (s *privSrv) UploadEmoji(req *web.UploadEmojiReq) (*web.UploadEmojiResp, error) { + defer req.File.(interface{ Close() error }).Close() + + // 生成随机路径 + randomPath := uuid.Must(uuid.NewV4()).String() + ossSavePath := "emoji/" + generatePath(randomPath[:8]) + "/" + randomPath[9:] + req.FileExt + data := io.NopCloser(req.File.(io.Reader)) + objectUrl, err := s.oss.PutObject(ossSavePath, data, req.FileSize, req.ContentType, false) + if err != nil { + logrus.Errorf("oss.putObject err: %s", err) + return nil, web.ErrFileUploadFailed + } + + // 上传到数据库 + emoji, err := s.Ds.EmojiService.UploadEmoji(req.User.ID, req.Name, objectUrl, req.Width, req.Height, req.FileSize, req.ContentType) + if err != nil { + logrus.Errorf("Ds.EmojiService.UploadEmoji err: %s", err) + return nil, web.ErrFileUploadFailed + } + + return &web.UploadEmojiResp{ + ID: emoji.ID, + Name: emoji.Name, + URL: emoji.URL, + }, nil +} + +// GetEmojiList 获取表情包列表 +func (s *privSrv) GetEmojiList(req *web.GetEmojiListReq) (*web.GetEmojiListResp, error) { + emojis, err := s.Ds.EmojiService.GetEmojiList(req.Offset, req.Limit) + if err != nil { + logrus.Errorf("Ds.EmojiService.GetEmojiList err: %s", err) + return nil, xerror.ServerError + } + + // 检查是否被当前用户收藏 + var emojiList []map[string]interface{} + for _, emoji := range emojis { + isCollected, _ := s.Ds.EmojiService.IsEmojiCollected(req.Uid, emoji.ID) + emojiFormated := emoji.Format() + emojiFormated.IsCollected = isCollected + emojiList = append(emojiList, map[string]interface{}{ + "id": emojiFormated.ID, + "user_id": emojiFormated.UserID, + "name": emojiFormated.Name, + "url": emojiFormated.URL, + "width": emojiFormated.Width, + "height": emojiFormated.Height, + "size": emojiFormated.Size, + "type": emojiFormated.Type, + "collection_count": emojiFormated.CollectionCount, + "is_collected": emojiFormated.IsCollected, + "created_on": emojiFormated.CreatedOn, + }) + } + + return &web.GetEmojiListResp{ + PageResp: base.PageResp{ + Items: emojiList, + Total: int64(len(emojiList)), + }, + }, nil +} + +// GetUserEmojiList 获取用户上传的表情包列表 +func (s *privSrv) GetUserEmojiList(req *web.GetUserEmojiListReq) (*web.GetUserEmojiListResp, error) { + emojis, err := s.Ds.EmojiService.GetUserEmojiList(req.UserID, req.Offset, req.Limit) + if err != nil { + logrus.Errorf("Ds.EmojiService.GetUserEmojiList err: %s", err) + return nil, xerror.ServerError + } + + // 检查是否被当前用户收藏 + var emojiList []map[string]interface{} + for _, emoji := range emojis { + isCollected, _ := s.Ds.EmojiService.IsEmojiCollected(req.Uid, emoji.ID) + emojiFormated := emoji.Format() + emojiFormated.IsCollected = isCollected + emojiList = append(emojiList, map[string]interface{}{ + "id": emojiFormated.ID, + "user_id": emojiFormated.UserID, + "name": emojiFormated.Name, + "url": emojiFormated.URL, + "width": emojiFormated.Width, + "height": emojiFormated.Height, + "size": emojiFormated.Size, + "type": emojiFormated.Type, + "collection_count": emojiFormated.CollectionCount, + "is_collected": emojiFormated.IsCollected, + "created_on": emojiFormated.CreatedOn, + }) + } + + return &web.GetUserEmojiListResp{ + PageResp: base.PageResp{ + Items: emojiList, + Total: int64(len(emojiList)), + }, + }, nil +} + +// GetUserCollectedEmojiList 获取用户收藏的表情包列表 +func (s *privSrv) GetUserCollectedEmojiList(req *web.GetUserCollectedEmojiListReq) (*web.GetUserCollectedEmojiListResp, error) { + emojis, err := s.Ds.EmojiService.GetUserCollectedEmojiList(req.Uid, req.Offset, req.Limit) + if err != nil { + logrus.Errorf("Ds.EmojiService.GetUserCollectedEmojiList err: %s", err) + return nil, xerror.ServerError + } + + // 标记为已收藏 + var emojiList []map[string]interface{} + for _, emoji := range emojis { + emojiFormated := emoji.Format() + emojiFormated.IsCollected = true + emojiList = append(emojiList, map[string]interface{}{ + "id": emojiFormated.ID, + "user_id": emojiFormated.UserID, + "name": emojiFormated.Name, + "url": emojiFormated.URL, + "width": emojiFormated.Width, + "height": emojiFormated.Height, + "size": emojiFormated.Size, + "type": emojiFormated.Type, + "collection_count": emojiFormated.CollectionCount, + "is_collected": emojiFormated.IsCollected, + "created_on": emojiFormated.CreatedOn, + }) + } + + return &web.GetUserCollectedEmojiListResp{ + PageResp: base.PageResp{ + Items: emojiList, + Total: int64(len(emojiList)), + }, + }, nil +} + +// CollectEmoji 收藏表情包 +func (s *privSrv) CollectEmoji(req *web.CollectEmojiReq) (*web.CollectEmojiResp, error) { + err := s.Ds.EmojiService.CollectEmoji(req.Uid, req.EmojiID) + if err != nil { + logrus.Errorf("Ds.EmojiService.CollectEmoji err: %s", err) + return nil, web.ErrCollectEmojiFailed + } + + return &web.CollectEmojiResp{ + Status: true, + }, nil +} + +// UncollectEmoji 取消收藏表情包 +func (s *privSrv) UncollectEmoji(req *web.UncollectEmojiReq) (*web.UncollectEmojiResp, error) { + err := s.Ds.EmojiService.UncollectEmoji(req.Uid, req.EmojiID) + if err != nil { + logrus.Errorf("Ds.EmojiService.UncollectEmoji err: %s", err) + return nil, web.ErrUncollectEmojiFailed + } + + return &web.UncollectEmojiResp{ + Status: true, + }, nil +} + +// DeleteEmoji 删除表情包 +func (s *privSrv) DeleteEmoji(req *web.DeleteEmojiReq) error { + err := s.Ds.EmojiService.DeleteEmoji(req.User.ID, req.ID) + if err != nil { + logrus.Errorf("Ds.EmojiService.DeleteEmoji err: %s", err) + return web.ErrDeleteEmojiFailed + } + + return nil +} + func newPrivSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Priv { return &privSrv{ DaoServant: s, diff --git a/mirc/web/v1/priv.go b/mirc/web/v1/priv.go index eb6d3221..c70a4944 100644 --- a/mirc/web/v1/priv.go +++ b/mirc/web/v1/priv.go @@ -121,4 +121,26 @@ type Priv struct { // AdminDeleteArticle 管理员删除长文章 AdminDeleteArticle func(Delete, web.AdminDeleteArticleReq) `mir:"admin/articles"` + + // 表情包相关API + // UploadEmoji 上传表情包 + UploadEmoji func(Post, web.UploadEmojiReq) web.UploadEmojiResp `mir:"emoji"` + + // GetEmojiList 获取表情包列表 + GetEmojiList func(Get, web.GetEmojiListReq) web.GetEmojiListResp `mir:"emoji/list"` + + // GetUserEmojiList 获取用户上传的表情包列表 + GetUserEmojiList func(Get, web.GetUserEmojiListReq) web.GetUserEmojiListResp `mir:"emoji/user"` + + // GetUserCollectedEmojiList 获取用户收藏的表情包列表 + GetUserCollectedEmojiList func(Get, web.GetUserCollectedEmojiListReq) web.GetUserCollectedEmojiListResp `mir:"emoji/collected"` + + // CollectEmoji 收藏表情包 + CollectEmoji func(Post, web.CollectEmojiReq) web.CollectEmojiResp `mir:"emoji/collect"` + + // UncollectEmoji 取消收藏表情包 + UncollectEmoji func(Post, web.UncollectEmojiReq) web.UncollectEmojiResp `mir:"emoji/uncollect"` + + // DeleteEmoji 删除表情包 + DeleteEmoji func(Delete, web.DeleteEmojiReq) `mir:"emoji"` } diff --git a/scripts/migration/mysql/0021_add_emoji_tables.down.sql b/scripts/migration/mysql/0021_add_emoji_tables.down.sql new file mode 100644 index 00000000..f8a3db3c --- /dev/null +++ b/scripts/migration/mysql/0021_add_emoji_tables.down.sql @@ -0,0 +1,7 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS `p_user_emoji`; +DROP TABLE IF EXISTS `p_emoji`; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/scripts/migration/mysql/0021_add_emoji_tables.up.sql b/scripts/migration/mysql/0021_add_emoji_tables.up.sql new file mode 100644 index 00000000..4b04b181 --- /dev/null +++ b/scripts/migration/mysql/0021_add_emoji_tables.up.sql @@ -0,0 +1,36 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +CREATE TABLE `p_emoji` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '表情包ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '上传用户ID', + `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '表情包名称', + `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '表情包URL', + `width` int unsigned NOT NULL DEFAULT '0' COMMENT '宽度', + `height` int unsigned NOT NULL DEFAULT '0' COMMENT '高度', + `size` bigint unsigned NOT NULL DEFAULT '0' COMMENT '文件大小', + `type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件类型', + `collection_count` bigint unsigned NOT NULL DEFAULT '0' COMMENT '收藏数', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE, + KEY `idx_collection` (`collection_count`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='表情包'; + +CREATE TABLE `p_user_emoji` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `emoji_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '表情包ID', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '收藏时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `idx_user_emoji` (`user_id`,`emoji_id`) USING BTREE, + KEY `idx_emoji` (`emoji_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表情包收藏'; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/scripts/migration/postgres/0021_add_emoji_tables.down.sql b/scripts/migration/postgres/0021_add_emoji_tables.down.sql new file mode 100644 index 00000000..23a7a690 --- /dev/null +++ b/scripts/migration/postgres/0021_add_emoji_tables.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS p_user_emoji; +DROP TABLE IF EXISTS p_emoji; \ No newline at end of file diff --git a/scripts/migration/postgres/0021_add_emoji_tables.up.sql b/scripts/migration/postgres/0021_add_emoji_tables.up.sql new file mode 100644 index 00000000..744309d3 --- /dev/null +++ b/scripts/migration/postgres/0021_add_emoji_tables.up.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS p_emoji ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL DEFAULT 0, + name VARCHAR(64) NOT NULL DEFAULT '', + url VARCHAR(255) NOT NULL DEFAULT '', + width INTEGER NOT NULL DEFAULT 0, + height INTEGER NOT NULL DEFAULT 0, + size BIGINT NOT NULL DEFAULT 0, + type VARCHAR(32) NOT NULL DEFAULT '', + collection_count BIGINT NOT NULL DEFAULT 0, + created_on BIGINT NOT NULL DEFAULT 0, + modified_on BIGINT NOT NULL DEFAULT 0, + deleted_on BIGINT NOT NULL DEFAULT 0, + is_del SMALLINT NOT NULL DEFAULT 0 +); + +CREATE INDEX IF NOT EXISTS idx_emoji_user ON p_emoji(user_id); +CREATE INDEX IF NOT EXISTS idx_emoji_collection ON p_emoji(collection_count); + +CREATE TABLE IF NOT EXISTS p_user_emoji ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL DEFAULT 0, + emoji_id BIGINT NOT NULL DEFAULT 0, + created_on BIGINT NOT NULL DEFAULT 0, + modified_on BIGINT NOT NULL DEFAULT 0, + deleted_on BIGINT NOT NULL DEFAULT 0, + is_del SMALLINT NOT NULL DEFAULT 0 +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_user_emoji ON p_user_emoji(user_id, emoji_id); +CREATE INDEX IF NOT EXISTS idx_user_emoji_emoji ON p_user_emoji(emoji_id); \ No newline at end of file diff --git a/scripts/migration/sqlite3/0021_add_emoji_tables.down.sql b/scripts/migration/sqlite3/0021_add_emoji_tables.down.sql new file mode 100644 index 00000000..23a7a690 --- /dev/null +++ b/scripts/migration/sqlite3/0021_add_emoji_tables.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS p_user_emoji; +DROP TABLE IF EXISTS p_emoji; \ No newline at end of file diff --git a/scripts/migration/sqlite3/0021_add_emoji_tables.up.sql b/scripts/migration/sqlite3/0021_add_emoji_tables.up.sql new file mode 100644 index 00000000..2c0fae69 --- /dev/null +++ b/scripts/migration/sqlite3/0021_add_emoji_tables.up.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS p_emoji ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL DEFAULT 0, + name TEXT NOT NULL DEFAULT '', + url TEXT NOT NULL DEFAULT '', + width INTEGER NOT NULL DEFAULT 0, + height INTEGER NOT NULL DEFAULT 0, + size INTEGER NOT NULL DEFAULT 0, + type TEXT NOT NULL DEFAULT '', + collection_count INTEGER NOT NULL DEFAULT 0, + created_on INTEGER NOT NULL DEFAULT 0, + modified_on INTEGER NOT NULL DEFAULT 0, + deleted_on INTEGER NOT NULL DEFAULT 0, + is_del INTEGER NOT NULL DEFAULT 0 +); + +CREATE INDEX IF NOT EXISTS idx_emoji_user ON p_emoji(user_id); +CREATE INDEX IF NOT EXISTS idx_emoji_collection ON p_emoji(collection_count); + +CREATE TABLE IF NOT EXISTS p_user_emoji ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL DEFAULT 0, + emoji_id INTEGER NOT NULL DEFAULT 0, + created_on INTEGER NOT NULL DEFAULT 0, + modified_on INTEGER NOT NULL DEFAULT 0, + deleted_on INTEGER NOT NULL DEFAULT 0, + is_del INTEGER NOT NULL DEFAULT 0 +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_user_emoji ON p_user_emoji(user_id, emoji_id); +CREATE INDEX IF NOT EXISTS idx_user_emoji_emoji ON p_user_emoji(emoji_id); \ No newline at end of file