From dda5465d602cd04f5008e1867b9cfc864569ac5a Mon Sep 17 00:00:00 2001 From: Michael Li Date: Fri, 3 Feb 2023 20:30:03 +0800 Subject: [PATCH] sqlx: implements topic data logics --- internal/core/topics.go | 41 ++++++-- internal/dao/jinzhu/dbr/tag.go | 12 +-- internal/dao/jinzhu/topics.go | 74 +++++++++++--- internal/dao/jinzhu/tweets.go | 20 ++-- internal/dao/jinzhu/user.go | 18 +++- internal/dao/jinzhu/utils.go | 59 +++++++---- internal/dao/sakila/sqlx.go | 15 ++- internal/dao/sakila/topics.go | 143 ++++++++++++++++++++++----- internal/model/web/pub.go | 7 +- internal/servants/web/broker/post.go | 30 +----- internal/servants/web/priv.go | 8 +- internal/servants/web/pub.go | 15 +-- web/src/types/Item.d.ts | 10 +- 13 files changed, 298 insertions(+), 154 deletions(-) diff --git a/internal/core/topics.go b/internal/core/topics.go index 138cc151..0982496c 100644 --- a/internal/core/topics.go +++ b/internal/core/topics.go @@ -4,19 +4,42 @@ package core -import ( - "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" +const ( + TagCategoryHot TagCategory = "hot" + TagCategoryNew TagCategory = "new" ) -type ( - Tag = dbr.Tag - TagFormated = dbr.TagFormated -) +type TagCategory string + +type Tag struct { + ID int64 `json:"id" db:"id"` + UserID int64 `json:"user_id" db:"user_id"` + Tag string `json:"tag"` + QuoteNum int64 `json:"quote_num" db:"quote_num"` +} + +type TagFormated struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + User *UserFormated `json:"user"` + Tag string `json:"tag"` + QuoteNum int64 `json:"quote_num"` +} // TopicService 话题服务 type TopicService interface { - CreateTag(tag *Tag) (*Tag, error) - DeleteTag(tag *Tag) error - GetTags(conditions *ConditionsT, offset, limit int) ([]*Tag, error) + UpsertTags(userId int64, tags []string) ([]*Tag, error) + DecrTagsById(ids []int64) error + GetTags(category TagCategory, offset int, limit int) ([]*Tag, error) GetTagsByKeyword(keyword string) ([]*Tag, error) } + +func (t *Tag) Format() *TagFormated { + return &TagFormated{ + ID: t.ID, + UserID: t.UserID, + User: &UserFormated{}, + Tag: t.Tag, + QuoteNum: t.QuoteNum, + } +} diff --git a/internal/dao/jinzhu/dbr/tag.go b/internal/dao/jinzhu/dbr/tag.go index 8570f574..8c7c9d20 100644 --- a/internal/dao/jinzhu/dbr/tag.go +++ b/internal/dao/jinzhu/dbr/tag.go @@ -71,9 +71,7 @@ func (t *Tag) Delete(db *gorm.DB) error { }).Error } -func (t *Tag) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Tag, error) { - var tags []*Tag - var err error +func (t *Tag) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) (tags []*Tag, err error) { if offset >= 0 && limit > 0 { db = db.Offset(offset).Limit(limit) } @@ -87,12 +85,8 @@ func (t *Tag) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]* db = db.Where(k, v) } } - - if err = db.Where("is_del = 0 and quote_num > 0").Find(&tags).Error; err != nil { - return nil, err - } - - return tags, nil + err = db.Where("is_del = 0 and quote_num > 0").Find(&tags).Error + return } func (t *Tag) TagsFrom(db *gorm.DB, tags []string) (res []*Tag, err error) { diff --git a/internal/dao/jinzhu/topics.go b/internal/dao/jinzhu/topics.go index d5ce70a9..3ab42fa9 100644 --- a/internal/dao/jinzhu/topics.go +++ b/internal/dao/jinzhu/topics.go @@ -26,30 +26,82 @@ func newTopicService(db *gorm.DB) core.TopicService { } } -func (s *topicServant) CreateTag(tag *core.Tag) (*core.Tag, error) { - return createTag(s.db, tag) +func (s *topicServant) UpsertTags(userId int64, tags []string) (_ []*core.Tag, err error) { + db := s.db.Begin() + defer func() { + if err == nil { + db.Commit() + } else { + db.Rollback() + } + }() + return createTags(db, userId, tags) } -func (s *topicServant) DeleteTag(tag *core.Tag) error { - return deleteTag(s.db, tag) +func (s *topicServant) DecrTagsById(ids []int64) (err error) { + db := s.db.Begin() + defer func() { + if err == nil { + db.Commit() + } else { + db.Rollback() + } + }() + return decrTagsByIds(db, ids) } -func (s *topicServant) GetTags(conditions *core.ConditionsT, offset, limit int) ([]*core.Tag, error) { - return (&dbr.Tag{}).List(s.db, conditions, offset, limit) +func (s *topicServant) GetTags(category core.TagCategory, offset, limit int) (res []*core.Tag, err error) { + conditions := &core.ConditionsT{} + switch category { + case core.TagCategoryHot: + // 热门标签 + conditions = &core.ConditionsT{ + "ORDER": "quote_num DESC", + } + case core.TagCategoryNew: + // 最新标签 + conditions = &core.ConditionsT{ + "ORDER": "id DESC", + } + } + // TODO: 优化查询方式,直接返回[]*core.Tag, 目前保持先转换一下 + var tags []*dbr.Tag + if tags, err = (&dbr.Tag{}).List(s.db, conditions, offset, limit); err == nil { + for _, tag := range tags { + res = append(res, &core.Tag{ + ID: tag.ID, + UserID: tag.UserID, + Tag: tag.Tag, + QuoteNum: tag.QuoteNum, + }) + } + } + return } -func (s *topicServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) { - tag := &dbr.Tag{} - +func (s *topicServant) GetTagsByKeyword(keyword string) (res []*core.Tag, err error) { keyword = "%" + strings.Trim(keyword, " ") + "%" + tag := &dbr.Tag{} + var tags []*dbr.Tag if keyword == "%%" { - return tag.List(s.db, &dbr.ConditionsT{ + tags, err = tag.List(s.db, &dbr.ConditionsT{ "ORDER": "quote_num DESC", }, 0, 6) } else { - return tag.List(s.db, &dbr.ConditionsT{ + tags, err = tag.List(s.db, &dbr.ConditionsT{ "tag LIKE ?": keyword, "ORDER": "quote_num DESC", }, 0, 6) } + if err == nil { + for _, tag := range tags { + res = append(res, &core.Tag{ + ID: tag.ID, + UserID: tag.UserID, + Tag: tag.Tag, + QuoteNum: tag.QuoteNum, + }) + } + } + return } diff --git a/internal/dao/jinzhu/tweets.go b/internal/dao/jinzhu/tweets.go index 186b7280..9882a9e8 100644 --- a/internal/dao/jinzhu/tweets.go +++ b/internal/dao/jinzhu/tweets.go @@ -288,21 +288,15 @@ func (s *tweetManageServant) VisiblePost(post *core.Post, visibility core.PostVi db.Rollback() return err } - // tag处理 tags := strings.Split(post.Tags, ",") - for _, t := range tags { - tag := &dbr.Tag{ - Tag: t, - } - // TODO: 暂时宽松不处理错误,这里或许可以有优化,后续完善 - if oldVisibility == dbr.PostVisitPrivate { - // 从私密转为非私密才需要重新创建tag - createTag(db, tag) - } else if visibility == dbr.PostVisitPrivate { - // 从非私密转为私密才需要删除tag - deleteTag(db, tag) - } + // TODO: 暂时宽松不处理错误,这里或许可以有优化,后续完善 + if oldVisibility == dbr.PostVisitPrivate { + // 从私密转为非私密才需要重新创建tag + createTags(db, post.UserID, tags) + } else if visibility == dbr.PostVisitPrivate { + // 从非私密转为私密才需要删除tag + deleteTags(db, tags) } db.Commit() s.cacheIndex.SendAction(core.IdxActVisiblePost, post) diff --git a/internal/dao/jinzhu/user.go b/internal/dao/jinzhu/user.go index 96d09fc1..21e36fe0 100644 --- a/internal/dao/jinzhu/user.go +++ b/internal/dao/jinzhu/user.go @@ -70,19 +70,31 @@ func (s *userManageServant) GetUsersByKeyword(keyword string) ([]*core.User, err } } -func (s *userManageServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) { +func (s *userManageServant) GetTagsByKeyword(keyword string) (res []*core.Tag, err error) { tag := &dbr.Tag{} keyword = "%" + strings.Trim(keyword, " ") + "%" + var tags []*dbr.Tag if keyword == "%%" { - return tag.List(s.db, &dbr.ConditionsT{ + tags, err = tag.List(s.db, &dbr.ConditionsT{ "ORDER": "quote_num DESC", }, 0, 6) } else { - return tag.List(s.db, &dbr.ConditionsT{ + tags, err = tag.List(s.db, &dbr.ConditionsT{ "tag LIKE ?": keyword, "ORDER": "quote_num DESC", }, 0, 6) } + if err == nil { + for _, tag := range tags { + res = append(res, &core.Tag{ + ID: tag.ID, + UserID: tag.UserID, + Tag: tag.Tag, + QuoteNum: tag.QuoteNum, + }) + } + } + return } func (s *userManageServant) CreateUser(user *dbr.User) (*core.User, error) { diff --git a/internal/dao/jinzhu/utils.go b/internal/dao/jinzhu/utils.go index 27766397..53660034 100644 --- a/internal/dao/jinzhu/utils.go +++ b/internal/dao/jinzhu/utils.go @@ -5,35 +5,52 @@ package jinzhu import ( + "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" "gorm.io/gorm" ) -func createTag(db *gorm.DB, tag *dbr.Tag) (*dbr.Tag, error) { - t, err := tag.Get(db) - if err != nil { - tag.QuoteNum = 1 - return tag.Create(db) - } - - // 更新 - t.QuoteNum++ - err = t.Update(db) - - if err != nil { - return nil, err +func createTags(db *gorm.DB, userId int64, tags []string) (res []*core.Tag, err error) { + for _, name := range tags { + tag := &dbr.Tag{Tag: name} + if tag, err = tag.Get(db); err == nil { + // 更新 + tag.QuoteNum++ + if err = tag.Update(db); err != nil { + return + } + } else { + tag.UserID, tag.QuoteNum = userId, 1 + if tag, err = (&dbr.Tag{ + UserID: userId, + QuoteNum: 1, + }).Create(db); err != nil { + return + } + } + res = append(res, &core.Tag{ + ID: tag.ID, + UserID: tag.UserID, + Tag: tag.Tag, + QuoteNum: tag.QuoteNum, + }) } - - return t, nil + return } -func deleteTag(db *gorm.DB, tag *dbr.Tag) error { - tag, err := tag.Get(db) - if err != nil { - return err +func decrTagsByIds(db *gorm.DB, ids []int64) (err error) { + for _, id := range ids { + tag := &dbr.Tag{Model: &dbr.Model{ID: id}} + if tag, err = tag.Get(db); err == nil { + tag.QuoteNum-- + if err = tag.Update(db); err != nil { + return + } + } else { + continue + } } - tag.QuoteNum-- - return tag.Update(db) + return nil } func deleteTags(db *gorm.DB, tags []string) error { diff --git a/internal/dao/sakila/sqlx.go b/internal/dao/sakila/sqlx.go index c38994b2..95275a8f 100644 --- a/internal/dao/sakila/sqlx.go +++ b/internal/dao/sakila/sqlx.go @@ -24,7 +24,7 @@ type sqlxServant struct { db *sqlx.DB } -func (s *sqlxServant) with(handle func(*sqlx.Tx) error) error { +func (s *sqlxServant) with(handle func(tx *sqlx.Tx) error) error { tx, err := s.db.Beginx() if err != nil { return err @@ -60,6 +60,19 @@ func sqlxDB() *sqlx.DB { return _db } +func in(db *sqlx.DB, query string, args ...interface{}) (string, []interface{}, error) { + q, params, err := sqlx.In(query, args...) + if err != nil { + return "", nil, err + } + return db.Rebind(q), params, nil +} + +func r(query string) string { + db := sqlxDB() + return db.Rebind(t(query)) +} + func c(query string) *sqlx.Stmt { db := sqlxDB() stmt, err := db.Preparex(db.Rebind(t(query))) diff --git a/internal/dao/sakila/topics.go b/internal/dao/sakila/topics.go index 1d5e47f1..c0ce3c6e 100644 --- a/internal/dao/sakila/topics.go +++ b/internal/dao/sakila/topics.go @@ -5,9 +5,11 @@ package sakila import ( + "strings" + "time" + "github.com/jmoiron/sqlx" "github.com/rocboss/paopao-ce/internal/core" - "github.com/rocboss/paopao-ce/pkg/debug" ) var ( @@ -16,40 +18,133 @@ var ( type topicServant struct { *sqlxServant - stmtAddTag *sqlx.Stmt - stmtDelTag *sqlx.Stmt - stmtListTag *sqlx.Stmt + stmtNewestTags *sqlx.Stmt + stmtHotTags *sqlx.Stmt + stmtTagsByKeywordA *sqlx.Stmt + stmtTagsByKeywordB *sqlx.Stmt + stmtInsertTag *sqlx.Stmt + stmtTagsByIdA string + stmtTagsByIdB string + stmtDecrTagsById string + stmtTagsByName string + stmtIncrTagsById string } -func (s *topicServant) CreateTag(tag *core.Tag) (*core.Tag, error) { - // TODO - debug.NotImplemented() - return nil, nil +func (s *topicServant) UpsertTags(userId int64, tags []string) (res []*core.Tag, xerr error) { + if len(tags) <= 0 { + return nil, nil + } + xerr = s.with(func(tx *sqlx.Tx) error { + query, args, err := in(s.db, s.stmtTagsByName, tags) + var ts []*core.Tag + if err = tx.Select(&ts, query, args...); err != nil { + return err + } + var upTags []string + if len(ts) > 0 { + var ids []int64 + for _, t := range ts { + ids = append(ids, t.ID) + upTags = append(upTags, t.Tag) + t.QuoteNum++ + // prepare remain tags just delete updated tag + for i, name := range tags { + if name == t.Tag { + size := len(tags) + tags[i] = tags[size-1] + tags = tags[:size-1] + break + } + } + } + if query, args, err = in(s.db, s.stmtIncrTagsById, ids); err != nil { + return err + } + if _, err = tx.Exec(query, args...); err != nil { + return err + } + res = append(res, ts...) + } + // process remain tags + if len(tags) == 0 { + return nil + } + var ids []int64 + now := time.Now().Unix() + for _, tag := range tags { + res, err := s.stmtInsertTag.Exec(userId, tag, now, now) + if err != nil { + return err + } + id, err := res.LastInsertId() + if err != nil { + return err + } + ids = append(ids, id) + } + var newTags []*core.Tag + query, args, err = in(s.db, s.stmtTagsByIdB, ids) + if err != nil { + return err + } + + if err = tx.Select(&newTags, query, args...); err != nil { + return err + } + res = append(res, newTags...) + return nil + }) + return } -func (s *topicServant) DeleteTag(tag *core.Tag) error { - // TODO - debug.NotImplemented() - return nil +func (s *topicServant) DecrTagsById(ids []int64) error { + return s.with(func(tx *sqlx.Tx) error { + query, args, err := in(s.db, s.stmtTagsByIdA, ids) + if err != nil { + return err + } + var ids []int64 + if err = tx.Select(&ids, query, args...); err != nil { + return err + } + query, args, err = in(s.db, s.stmtDecrTagsById, time.Now().Unix(), ids) + _, err = tx.Exec(query, args...) + return err + }) } -func (s *topicServant) GetTags(conditions *core.ConditionsT, offset, limit int) ([]*core.Tag, error) { - // TODO - debug.NotImplemented() - return nil, nil +func (s *topicServant) GetTags(category core.TagCategory, offset int, limit int) (res []*core.Tag, err error) { + switch category { + case core.TagCategoryHot: + err = s.stmtHotTags.Select(&res, offset, limit) + case core.TagCategoryNew: + err = s.stmtHotTags.Select(&res, offset, limit) + } + return } -func (s *topicServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) { - // TODO - debug.NotImplemented() - return nil, nil +func (s *topicServant) GetTagsByKeyword(keyword string) (res []*core.Tag, err error) { + keyword = "%" + strings.Trim(keyword, " ") + "%" + if keyword == "%%" { + err = s.stmtTagsByKeywordA.Select(&res) + } else { + err = s.stmtTagsByKeywordB.Select(&res) + } + return } func newTopicService(db *sqlx.DB) core.TopicService { return &topicServant{ - sqlxServant: newSqlxServant(db), - stmtAddTag: c(`SELECT * FROM @person WHERE first_name=?`), - stmtDelTag: c(`SELECT * FROM @person WHERE first_name=?`), - stmtListTag: c(`SELECT * FROM @person WHERE first_name=?`), + sqlxServant: newSqlxServant(db), + stmtNewestTags: c(`SELECT id, user_id, tag, quote_num FROM @tag WHERE is_del = 0 AND quote_num > 0 ORDER BY id DESC OFFSET ? LIMIT ?`), + stmtHotTags: c(`SELECT id, user_id, tag, quote_num FROM @tag WHERE is_del = 0 AND quote_num > 0 ORDER BY quote_num DESC OFFSET ? LIMIT ?`), + stmtTagsByKeywordA: c(`SELECT id, user_id, tag, quote_num FROM @tag WHERE is_del = 0 ORDER BY quote_num DESC OFFSET 0 LIMIT 6`), + stmtTagsByKeywordB: c(`SELECT id, user_id, tag, quote_num FROM @tag WHERE is_del = 0 AND tag LIKE ? ORDER BY quote_num DESC OFFSET 0 LIMIT 6`), + stmtInsertTag: c(`INSERT INFO @tag (user_id, tag, created_on, modified_on, quote_num) VALUES (?, ?, ?, ?, 1)`), + stmtTagsByIdA: r(`SELECT id FROM @tag WHERE id IN (?) AND is_del = 0 AND quote_num >= 0`), + stmtTagsByIdB: r(`SELECT id, user_id, tag, quote_num FROM @tag WHERE id IN (?)`), + stmtDecrTagsById: r(`UPDATE @tag SET quote_num=quote_num-1, modified_on=? WHERE id IN (?)`), + stmtTagsByName: r(`SELECT id, user_id, tag, quote_num FROM @tag WHERE tag IN (?) AND is_del = 0 AND quote_num >= 0`), + stmtIncrTagsById: r(`UPDATE @tag SET quote_num=quote_num+1 WHERE id IN (?)`), } } diff --git a/internal/model/web/pub.go b/internal/model/web/pub.go index a951b33a..7cb21a2b 100644 --- a/internal/model/web/pub.go +++ b/internal/model/web/pub.go @@ -10,12 +10,7 @@ import ( "github.com/rocboss/paopao-ce/pkg/debug" ) -const ( - TagTypeHot TagType = "hot" - TagTypeNew TagType = "new" -) - -type TagType string +type TagType = core.TagCategory type TweetDetailReq struct { TweetId int64 `form:"id"` diff --git a/internal/servants/web/broker/post.go b/internal/servants/web/broker/post.go index 59d87975..bace3982 100644 --- a/internal/servants/web/broker/post.go +++ b/internal/servants/web/broker/post.go @@ -19,10 +19,7 @@ import ( "github.com/sirupsen/logrus" ) -type TagType string - -const TagTypeHot TagType = "hot" -const TagTypeNew TagType = "new" +type TagType = core.TagCategory type PostListReq struct { Conditions *core.ConditionsT @@ -157,13 +154,7 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (_ *core.Po // 私密推文不创建标签与用户提醒 if post.Visibility != core.PostVisitPrivate { // 创建标签 - for _, t := range tags { - tag := &core.Tag{ - UserID: userID, - Tag: t, - } - ds.CreateTag(tag) - } + ds.UpsertTags(userID, tags) // 创建用户消息提醒 for _, u := range param.Users { @@ -543,22 +534,7 @@ func GetPostTags(param *PostTagsReq) ([]*core.TagFormated, error) { if num > conf.AppSetting.MaxPageSize { num = conf.AppSetting.MaxPageSize } - - conditions := &core.ConditionsT{} - if param.Type == TagTypeHot { - // 热门标签 - conditions = &core.ConditionsT{ - "ORDER": "quote_num DESC", - } - } - if param.Type == TagTypeNew { - // 热门标签 - conditions = &core.ConditionsT{ - "ORDER": "id DESC", - } - } - - tags, err := ds.GetTags(conditions, 0, num) + tags, err := ds.GetTags(core.TagCategory(param.Type), 0, num) if err != nil { return nil, err } diff --git a/internal/servants/web/priv.go b/internal/servants/web/priv.go index 6efdf93c..a7bfcd52 100644 --- a/internal/servants/web/priv.go +++ b/internal/servants/web/priv.go @@ -306,13 +306,7 @@ func (s *privSrv) CreateTweet(req *web.CreateTweetReq) (_ *web.CreateTweetResp, // 私密推文不创建标签与用户提醒 if post.Visibility != core.PostVisitPrivate { // 创建标签 - for _, t := range tags { - tag := &core.Tag{ - UserID: req.User.ID, - Tag: t, - } - s.Ds.CreateTag(tag) - } + s.Ds.UpsertTags(req.User.ID, tags) // 创建用户消息提醒 for _, u := range req.Users { diff --git a/internal/servants/web/pub.go b/internal/servants/web/pub.go index 58d49ee9..01e9e46b 100644 --- a/internal/servants/web/pub.go +++ b/internal/servants/web/pub.go @@ -75,20 +75,7 @@ func (s *pubSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error if num > conf.AppSetting.MaxPageSize { num = conf.AppSetting.MaxPageSize } - - conditions := &core.ConditionsT{} - if req.Type == web.TagTypeHot { - // 热门标签 - conditions = &core.ConditionsT{ - "ORDER": "quote_num DESC", - } - } else if req.Type == web.TagTypeNew { - // 热门标签 - conditions = &core.ConditionsT{ - "ORDER": "id DESC", - } - } - tags, err := s.Ds.GetTags(conditions, 0, num) + tags, err := s.Ds.GetTags(req.Type, 0, num) if err != nil { return nil, _errGetPostTagsFailed } diff --git a/web/src/types/Item.d.ts b/web/src/types/Item.d.ts index 5d254e7f..0f00aa28 100644 --- a/web/src/types/Item.d.ts +++ b/web/src/types/Item.d.ts @@ -282,15 +282,7 @@ declare module Item { /** 标签名 */ tag: string, /** 引用数 */ - quote_num: number, - /** 创建时间 */ - created_on: number, - /** 修改时间 */ - modified_on?: number, - /** 删除时间 */ - deleted_on?: number, - /** 是否删除:0为未删除,1为已删除 */ - is_del?: 0 | 1 + quote_num: number } interface PagerProps {