2026-08-09 10:43:45 +08:00
|
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-09-22 21:15:34 +08:00
|
|
|
|
"errors"
|
2026-08-09 10:43:45 +08:00
|
|
|
|
|
2026-08-09 12:41:08 +08:00
|
|
|
|
"bsm/full/module/social/feed/internal/impl"
|
2026-09-22 21:15:34 +08:00
|
|
|
|
"git.apinb.com/bsm-sdk/core/errcode"
|
2026-08-09 10:43:45 +08:00
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// maxActionCounter 互动计数上限,避免计数被无条件刷高
|
|
|
|
|
|
const maxActionCounter int64 = 1000000
|
|
|
|
|
|
|
2026-08-09 10:43:45 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 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
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 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
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 点赞/踩操作
|
|
|
|
|
|
func LikeAction(action_op, action_type, identity string) (err error) {
|
2026-09-22 21:15:34 +08:00
|
|
|
|
var (
|
|
|
|
|
|
column string
|
|
|
|
|
|
tx = impl.DBService
|
|
|
|
|
|
)
|
|
|
|
|
|
// 显式绑定目标表,避免 action_type 取任意值时目标表不确定
|
|
|
|
|
|
switch action_type {
|
|
|
|
|
|
case "post":
|
2026-08-09 10:43:45 +08:00
|
|
|
|
tx = tx.Model(&FeedPost{})
|
2026-09-22 21:15:34 +08:00
|
|
|
|
case "comment":
|
2026-08-09 10:43:45 +08:00
|
|
|
|
tx = tx.Model(&FeedComment{})
|
2026-09-22 21:15:34 +08:00
|
|
|
|
default:
|
|
|
|
|
|
return errcode.ErrInvalidArgument
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
switch action_op {
|
|
|
|
|
|
case "ilike":
|
2026-08-09 10:43:45 +08:00
|
|
|
|
column = "cnt_like"
|
2026-09-22 21:15:34 +08:00
|
|
|
|
case "unlike":
|
2026-08-09 10:43:45 +08:00
|
|
|
|
column = "cnt_unlike"
|
2026-09-22 21:15:34 +08:00
|
|
|
|
default:
|
|
|
|
|
|
return errcode.ErrInvalidArgument
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 条件更新:目标必须存在且计数未达上限,避免计数被无限刷高
|
|
|
|
|
|
res := tx.Where("identity = ? AND "+column+" < ?", identity, maxActionCounter).
|
|
|
|
|
|
UpdateColumn(column, gorm.Expr(column+" + ?", 1))
|
|
|
|
|
|
if res.Error != nil {
|
|
|
|
|
|
return res.Error
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
if res.RowsAffected == 0 {
|
|
|
|
|
|
return errcode.ErrRecordNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 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)
|
2026-08-09 10:43:45 +08:00
|
|
|
|
if tag != "" {
|
2026-09-22 21:15:34 +08:00
|
|
|
|
tx = tx.Joins("join feed_relate_tags on feed_relate_tags.post_identity = feed_post.identity").
|
|
|
|
|
|
Where("feed_relate_tags.key = ?", tag)
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 使句柄可重复使用,统计与列表查询互不影响
|
|
|
|
|
|
tx = tx.Session(&gorm.Session{})
|
2026-08-09 10:43:45 +08:00
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
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
|
2026-08-09 10:43:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func AddTags(data *[]FeedTags) (err error) {
|
|
|
|
|
|
err = impl.DBService.Create(data).Error
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-09-22 21:15:34 +08:00
|
|
|
|
|
|
|
|
|
|
// ModifyTags 按标签 key 定位并保存,feed_tags 表没有 identity 列
|
2026-08-09 10:43:45 +08:00
|
|
|
|
func ModifyTags(data *FeedTags) (err error) {
|
2026-09-22 21:15:34 +08:00
|
|
|
|
err = impl.DBService.Where("key = ?", data.Key).Save(data).Error
|
2026-08-09 10:43:45 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// DeleteTags 按标签 key 删除,feed_tags 表没有 identity 列
|
2026-08-09 10:43:45 +08:00
|
|
|
|
func DeleteTags(key string) (err error) {
|
2026-09-22 21:15:34 +08:00
|
|
|
|
err = impl.DBService.Where("key = ?", key).Delete(&FeedTags{}).Error
|
2026-08-09 10:43:45 +08:00
|
|
|
|
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 {
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 记录评论作者,供后续归属校验使用
|
|
|
|
|
|
comment.PassportIdentity = authorIdentity
|
2026-08-09 10:43:45 +08:00
|
|
|
|
// 添加评论,更新文章评论量
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|
2026-08-09 10:43:45 +08:00
|
|
|
|
|
2026-09-22 21:15:34 +08:00
|
|
|
|
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
|
|
|
|
|
|
})
|
2026-08-09 10:43:45 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|