package models import ( "errors" "bsm/full/module/social/feed/internal/impl" "git.apinb.com/bsm-sdk/core/errcode" "gorm.io/gorm" ) // maxActionCounter 互动计数上限,避免计数被无条件刷高 const maxActionCounter int64 = 1000000 func InitData() error { return nil } func CreatePost(data *FeedPost) error { err := impl.DBService.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&data).Error; err != nil { return err } if err := tx.Create(&data.Attachs).Error; err != nil { return err } if err := tx.Create(&data.Tags).Error; err != nil { return err } return nil }) return err } // ChargePost 修改动态,仅允许作者本人修改;is_open=false 需真正写入,附件变更需真正落库 func ChargePost(data *FeedPost, passportIdentity string) (affected int64, err error) { err = impl.DBService.Transaction(func(tx *gorm.DB) error { // 用 map 更新,避免 GORM 结构体更新忽略 is_open=false 这类零值 res := tx.Model(&FeedPost{}). Where("identity = ? AND passport_identity = ?", data.Identity, passportIdentity). Updates(map[string]any{"content": data.Content, "is_open": data.IsOpen}) if res.Error != nil { return res.Error } affected = res.RowsAffected if affected == 0 { // 动态不存在或非本人发布,直接结束,不再处理附件 return nil } // 附件变更真正落库:请求携带附件时整体替换原附件 if len(data.Attachs) > 0 { if err := tx.Where("post_identity = ?", data.Identity).Delete(&FeedRelateAttach{}).Error; err != nil { return err } if err := tx.Create(&data.Attachs).Error; err != nil { return err } } return nil }) return } // DeletePost 删除动态,仅允许作者本人删除 func DeletePost(identity, passportIdentity string) (affected int64, err error) { res := impl.DBService.Where("identity = ? AND passport_identity = ?", identity, passportIdentity).Delete(&FeedPost{}) return res.RowsAffected, res.Error } // 点赞/踩操作 func LikeAction(action_op, action_type, identity string) (err error) { var ( column string tx = impl.DBService ) // 显式绑定目标表,避免 action_type 取任意值时目标表不确定 switch action_type { case "post": tx = tx.Model(&FeedPost{}) case "comment": tx = tx.Model(&FeedComment{}) default: return errcode.ErrInvalidArgument } switch action_op { case "ilike": column = "cnt_like" case "unlike": column = "cnt_unlike" default: return errcode.ErrInvalidArgument } // 条件更新:目标必须存在且计数未达上限,避免计数被无限刷高 res := tx.Where("identity = ? AND "+column+" < ?", identity, maxActionCounter). UpdateColumn(column, gorm.Expr(column+" + ?", 1)) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return errcode.ErrRecordNotFound } return nil } // PostList 动态列表,仅返回公开动态或调用者本人发布的动态 func PostList(page, size int64, tag, viewerIdentity string) (list []*FeedPost, cnt int64, err error) { list = make([]*FeedPost, 0) tx := impl.DBService.Model(&FeedPost{}). Where("status <> ?", -1). Where("is_open = ? OR passport_identity = ?", true, viewerIdentity) if tag != "" { tx = tx.Joins("join feed_relate_tags on feed_relate_tags.post_identity = feed_post.identity"). Where("feed_relate_tags.key = ?", tag) } // 使句柄可重复使用,统计与列表查询互不影响 tx = tx.Session(&gorm.Session{}) if err = tx.Count(&cnt).Error; err != nil { return nil, 0, err } err = tx.Order("created_at desc"). Limit(int(size)). Offset(int((page - 1) * size)). Find(&list).Error if err != nil { return nil, 0, err } // 标签与附件是 gorm:"-" 字段,GORM 不会加载,需二次查询回填 if err = fillRelates(list); err != nil { return nil, 0, err } return list, cnt, nil } // fillRelates 按 post_identity 批量回填动态的标签与附件 func fillRelates(list []*FeedPost) error { if len(list) == 0 { return nil } identities := make([]string, 0, len(list)) for _, item := range list { identities = append(identities, item.Identity) } var attachs []FeedRelateAttach if err := impl.DBService.Where("post_identity IN ?", identities).Find(&attachs).Error; err != nil { return err } var relates []FeedRelateTags if err := impl.DBService.Where("post_identity IN ?", identities).Find(&relates).Error; err != nil { return err } // 标签名称存于 feed_tags 字典,按 key 批量查出后回填 contents := map[string]string{} if len(relates) > 0 { keys := make([]string, 0, len(relates)) for _, relate := range relates { keys = append(keys, relate.Key) } var dict []FeedTags if err := impl.DBService.Where("key IN ?", keys).Find(&dict).Error; err != nil { return err } for _, tag := range dict { contents[tag.Key] = tag.Content } } attachMap := map[string][]FeedRelateAttach{} for _, attach := range attachs { attachMap[attach.PostIdentity] = append(attachMap[attach.PostIdentity], attach) } tagMap := map[string][]FeedRelateTags{} for _, relate := range relates { relate.Content = contents[relate.Key] tagMap[relate.PostIdentity] = append(tagMap[relate.PostIdentity], relate) } for _, item := range list { item.Attachs = attachMap[item.Identity] item.Tags = tagMap[item.Identity] } return nil } func AddTags(data *[]FeedTags) (err error) { err = impl.DBService.Create(data).Error if err != nil { return err } return err } // ModifyTags 按标签 key 定位并保存,feed_tags 表没有 identity 列 func ModifyTags(data *FeedTags) (err error) { err = impl.DBService.Where("key = ?", data.Key).Save(data).Error if err != nil { return err } return err } // DeleteTags 按标签 key 删除,feed_tags 表没有 identity 列 func DeleteTags(key string) (err error) { err = impl.DBService.Where("key = ?", key).Delete(&FeedTags{}).Error if err != nil { return err } return err } func TagsList() (list []*FeedTags, cnt int64, err error) { tags := make([]*FeedTags, 0) tx := impl.DBService.Order("created_at desc") err = tx.Find(&tags).Count(&cnt).Error return tags, cnt, err } func AddComment(comment *FeedComment, authorIdentity string) (err error) { err = impl.DBService.Transaction(func(tx *gorm.DB) error { // 记录评论作者,供后续归属校验使用 comment.PassportIdentity = authorIdentity // 添加评论,更新文章评论量 err := tx.Create(comment).Table("feed_post").Where("identity = ?", comment.PostIdentity).UpdateColumn("cnt_comment", gorm.Expr("cnt_comment + ?", 1)).Error if err != nil { return err } if comment.ParentIdentity != "" { //更新被评论的评论数 err = tx.Where("identity = ?", comment.ParentIdentity).UpdateColumn("cnt_comment", gorm.Expr("cnt_comment + ?", 1)).Error if err != nil { return err } } return nil }) return } // DeleteComment 删除评论,仅允许评论作者本人删除,并同步递减所属动态的评论数 func DeleteComment(identity, passportIdentity string) (affected int64, err error) { err = impl.DBService.Transaction(func(tx *gorm.DB) error { var comment FeedComment // 只有作者本人的评论才允许删除 if err := tx.Where("identity = ? AND passport_identity = ?", identity, passportIdentity).First(&comment).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err } res := tx.Delete(&FeedComment{}, "identity = ?", identity) if res.Error != nil { return res.Error } affected = res.RowsAffected // 同步递减所属动态的评论数,并做下限保护避免出现负数 if comment.PostIdentity != "" { if err := tx.Model(&FeedPost{}). Where("identity = ? AND cnt_comment > 0", comment.PostIdentity). UpdateColumn("cnt_comment", gorm.Expr("cnt_comment - ?", 1)).Error; err != nil { return err } } return nil }) return }