post_model.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. package model
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "time"
  7. "git.banshen.xyz/huangguangrong/slow_wild_protobuff/slowwild/slowwildserver"
  8. "github.com/go-redis/redis/v8"
  9. "gorm.io/gorm"
  10. )
  11. const (
  12. // 缓存key前缀
  13. prefixPostKey = "post:%d" // 帖子信息
  14. prefixPostContentKey = "post:content:%d" // 帖子内容
  15. prefixUserLikeKey = "user:like:%d:%d" // 用户点赞状态
  16. prefixUserCollectKey = "user:collect:%d:%d" // 用户收藏状态
  17. prefixPostListKey = "post:list:%d:%d:%d:%s" // 帖子列表 (searchType:sortType:page:keyword)
  18. cacheExpiry = 24 * time.Hour // 缓存过期时间
  19. )
  20. // PPost 冒泡/文章
  21. type Post struct {
  22. *Model
  23. UserId int64 `json:"user_id" gorm:"user_id"` // 用户ID
  24. PostType int32 `json:"post_type" gorm:"post_type"` // 帖子类型0-普通图文1-视频
  25. CommentCount int64 `json:"comment_count" gorm:"comment_count"` // 评论数
  26. CollectionCount int64 `json:"collection_count" gorm:"collection_count"` // 收藏数
  27. UpvoteCount int64 `json:"upvote_count" gorm:"upvote_count"` // 点赞数
  28. ShareCount int64 `json:"share_count" gorm:"share_count"` // 分享数
  29. Visibility int8 `json:"visibility" gorm:"visibility"` // 可见性: 0私密 1好友可见 2关注可见 4公开
  30. IsTop int8 `json:"is_top" gorm:"is_top"` // 是否置顶
  31. IsEssence int8 `json:"is_essence" gorm:"is_essence"` // 是否精华
  32. LatestRepliedOn int64 `json:"latest_replied_on" gorm:"latest_replied_on"` // 最新回复时间
  33. Tags string `json:"tags" gorm:"tags"` // 标签
  34. Ip string `json:"ip" gorm:"ip"` // IP地址
  35. IpLoc string `json:"ip_loc" gorm:"ip_loc"` // IP城市地址
  36. WithUserIds string `json:"with_user_ids" gorm:"with_user_ids"` // 被艾特的用户
  37. HotNum int64 `json:"hot_num" gorm:"hot_num"` // 热度值
  38. }
  39. // TableName 表名称
  40. func (*Post) TableName() string {
  41. return "p_post"
  42. }
  43. // PostModel 帖子模型
  44. type PostModel struct {
  45. conn *gorm.DB
  46. redis *redis.Client
  47. }
  48. func NewPostModel(conn *gorm.DB, rdb *redis.Client) *PostModel {
  49. return &PostModel{
  50. conn: conn,
  51. redis: rdb,
  52. }
  53. }
  54. // Create 创建帖子
  55. func (m *PostModel) Create(ctx context.Context, post *Post) error {
  56. return m.conn.WithContext(ctx).Create(post).Error
  57. }
  58. // PostContentModel 帖子内容模型
  59. type PostContentModel struct {
  60. conn *gorm.DB
  61. }
  62. func NewPostContentModel(conn *gorm.DB) *PostContentModel {
  63. return &PostContentModel{
  64. conn: conn,
  65. }
  66. }
  67. // Create 创建帖子内容
  68. func (m *PostContentModel) Create(ctx context.Context, content *PostContent) error {
  69. return m.conn.WithContext(ctx).Create(content).Error
  70. }
  71. // GetPostList 根据条件获取帖子列表
  72. func (m *PostModel) GetPostList(ctx context.Context, req *slowwildserver.GetPostListReq) ([]*Post, error) {
  73. // 构建缓存key
  74. cacheKey := fmt.Sprintf(prefixPostListKey, req.SearchType, req.SortType, req.Page, req.Keyword)
  75. // 尝试从缓存获取
  76. var posts []*Post
  77. data, err := m.redis.Get(ctx, cacheKey).Bytes()
  78. if err == nil {
  79. if err := json.Unmarshal(data, &posts); err == nil {
  80. return posts, nil
  81. }
  82. }
  83. var normalPosts []*Post
  84. // 先查询置顶帖子
  85. topQuery := m.conn.WithContext(ctx).Model(&Post{}).Where("is_top = ?", 1)
  86. normalQuery := m.conn.WithContext(ctx).Model(&Post{}).Where("is_top = ?", 0)
  87. // 根据搜索类型构建查询条件
  88. switch req.SearchType {
  89. case 1: // 搜索内容/标题
  90. subQuery := fmt.Sprintf("%%%s%%", req.Keyword)
  91. topQuery = topQuery.Joins("LEFT JOIN p_post_content ON p_post.id = p_post_content.post_id").
  92. Where("p_post_content.content LIKE ? OR p_post_content.title LIKE ?", subQuery, subQuery)
  93. normalQuery = normalQuery.Joins("LEFT JOIN p_post_content ON p_post.id = p_post_content.post_id").
  94. Where("p_post_content.content LIKE ? OR p_post_content.title LIKE ?", subQuery, subQuery)
  95. case 2: // 话题搜索
  96. topQuery = topQuery.Joins("JOIN p_tag_with_post ON p_tag_with_post.post_id = p_post.id").
  97. Where("p_tag_with_post.tag_id = ?", req.Keyword)
  98. normalQuery = normalQuery.Joins("JOIN p_tag_with_post ON p_tag_with_post.post_id = p_post.id").
  99. Where("p_tag_with_post.tag_id = ?", req.Keyword)
  100. }
  101. // 根据排序类型构建排序条件
  102. switch req.SortType {
  103. case 1: // 热度排序
  104. topQuery = topQuery.Order("hot_num DESC")
  105. normalQuery = normalQuery.Order("hot_num DESC")
  106. default: // 默认创建时间排序
  107. topQuery = topQuery.Order("created_on DESC")
  108. normalQuery = normalQuery.Order("created_on DESC")
  109. }
  110. // 查询置顶帖子
  111. if err := topQuery.Find(&posts).Error; err != nil {
  112. return nil, err
  113. }
  114. // 查询普通帖子
  115. offset := (req.Page - 1) * req.PageSize
  116. limit := int(req.PageSize) - len(posts)
  117. if limit > 0 {
  118. if err := normalQuery.Offset(int(offset)).Limit(limit).Find(&normalPosts).Error; err != nil {
  119. return nil, err
  120. }
  121. posts = append(posts, normalPosts...)
  122. }
  123. // 缓存查询结果
  124. if data, err := json.Marshal(posts); err == nil {
  125. // 设置较短的过期时间,因为列表数据变化较频繁
  126. m.redis.Set(ctx, cacheKey, data, 5*time.Minute)
  127. }
  128. return posts, nil
  129. }
  130. // GetPostContents 批量获取帖子内容(带缓存)
  131. func (m *PostModel) GetPostContents(ctx context.Context, postIds []int64) (map[int64]*PostContent, error) {
  132. contentMap := make(map[int64]*PostContent)
  133. var missedIds []int64
  134. // 先从Redis获取
  135. for _, id := range postIds {
  136. key := fmt.Sprintf(prefixPostContentKey, id)
  137. data, err := m.redis.Get(ctx, key).Bytes()
  138. if err == nil {
  139. var content PostContent
  140. if err := json.Unmarshal(data, &content); err == nil {
  141. contentMap[id] = &content
  142. continue
  143. }
  144. }
  145. missedIds = append(missedIds, id)
  146. }
  147. // 查询未命中的内容
  148. if len(missedIds) > 0 {
  149. var contents []*PostContent
  150. err := m.conn.WithContext(ctx).Where("post_id IN ?", missedIds).Find(&contents).Error
  151. if err != nil {
  152. return nil, err
  153. }
  154. // 写入缓存并添加到结果
  155. for _, content := range contents {
  156. key := fmt.Sprintf(prefixPostContentKey, content.PostId)
  157. if data, err := json.Marshal(content); err == nil {
  158. m.redis.Set(ctx, key, data, cacheExpiry)
  159. }
  160. contentMap[content.PostId] = content
  161. }
  162. }
  163. return contentMap, nil
  164. }
  165. // IsPostLikedByUser 检查用户是否点赞了帖子(带缓存)
  166. func (m *PostModel) IsPostLikedByUser(ctx context.Context, userId, postId int64) (bool, error) {
  167. key := fmt.Sprintf(prefixUserLikeKey, userId, postId)
  168. exists, err := m.redis.Exists(ctx, key).Result()
  169. if err == nil && exists == 1 {
  170. return m.redis.Get(ctx, key).Bool()
  171. }
  172. var count int64
  173. err = m.conn.WithContext(ctx).
  174. Model(&PostAction{}).
  175. Where("user_id = ? AND post_id = ? and type = 0", userId, postId).
  176. Count(&count).Error
  177. if err != nil {
  178. return false, err
  179. }
  180. liked := count > 0
  181. m.redis.Set(ctx, key, liked, cacheExpiry)
  182. return liked, nil
  183. }
  184. // IsPostCollectedByUser 检查用户是否收藏了帖子(带缓存)
  185. func (m *PostModel) IsPostCollectedByUser(ctx context.Context, userId, postId int64) (bool, error) {
  186. key := fmt.Sprintf(prefixUserCollectKey, userId, postId)
  187. exists, err := m.redis.Exists(ctx, key).Result()
  188. if err == nil && exists == 1 {
  189. return m.redis.Get(ctx, key).Bool()
  190. }
  191. var count int64
  192. err = m.conn.WithContext(ctx).
  193. Model(&PostAction{}).
  194. Where("user_id = ? AND post_id = ? and type = 1", userId, postId).
  195. Count(&count).Error
  196. if err != nil {
  197. return false, err
  198. }
  199. collected := count > 0
  200. m.redis.Set(ctx, key, collected, cacheExpiry)
  201. return collected, nil
  202. }
  203. // 在需要更新帖子时,清除相关缓存
  204. func (m *PostModel) ClearListCache(ctx context.Context) error {
  205. // 使用通配符匹配所有列表缓存
  206. pattern := "post:list:*"
  207. keys, err := m.redis.Keys(ctx, pattern).Result()
  208. if err != nil {
  209. return err
  210. }
  211. if len(keys) > 0 {
  212. return m.redis.Del(ctx, keys...).Err()
  213. }
  214. return nil
  215. }
  216. // UpdatePost 更新帖子时清除缓存
  217. func (m *PostModel) UpdatePost(ctx context.Context, post *Post) error {
  218. err := m.conn.WithContext(ctx).Save(post).Error
  219. if err != nil {
  220. return err
  221. }
  222. // 清除帖子相关的所有缓存
  223. m.redis.Del(ctx, fmt.Sprintf(prefixPostKey, post.ID))
  224. m.ClearListCache(ctx)
  225. return nil
  226. }
  227. // GetPostDetail 获取帖子详情(带缓存)
  228. func (m *PostModel) GetPostDetail(ctx context.Context, postId int64) (*Post, error) {
  229. // 尝试从缓存获取
  230. key := fmt.Sprintf(prefixPostKey, postId)
  231. data, err := m.redis.Get(ctx, key).Bytes()
  232. if err == nil {
  233. var post Post
  234. if err := json.Unmarshal(data, &post); err == nil {
  235. return &post, nil
  236. }
  237. }
  238. // 从数据库获取
  239. var post Post
  240. err = m.conn.WithContext(ctx).First(&post, postId).Error
  241. if err != nil {
  242. return nil, err
  243. }
  244. // 写入缓存
  245. if data, err := json.Marshal(post); err == nil {
  246. m.redis.Set(ctx, key, data, cacheExpiry)
  247. }
  248. return &post, nil
  249. }
  250. // GetPostContent 获取帖子内容(带缓存)
  251. func (m *PostModel) GetPostContent(ctx context.Context, postId int64) (*PostContent, error) {
  252. // 尝试从缓存获取
  253. key := fmt.Sprintf(prefixPostContentKey, postId)
  254. data, err := m.redis.Get(ctx, key).Bytes()
  255. if err == nil {
  256. var content PostContent
  257. if err := json.Unmarshal(data, &content); err == nil {
  258. return &content, nil
  259. }
  260. }
  261. // 从数据库获取
  262. var content PostContent
  263. err = m.conn.WithContext(ctx).Where("post_id = ?", postId).First(&content).Error
  264. if err != nil {
  265. return nil, err
  266. }
  267. // 写入缓存
  268. if data, err := json.Marshal(content); err == nil {
  269. m.redis.Set(ctx, key, data, cacheExpiry)
  270. }
  271. return &content, nil
  272. }
  273. // IncrementCollectionCount 增加收藏数
  274. func (m *PostModel) IncrementCollectionCount(ctx context.Context, postId int64, increment int) error {
  275. err := m.conn.WithContext(ctx).Model(&Post{}).
  276. Where("id = ?", postId).
  277. UpdateColumn("collection_count", gorm.Expr("collection_count + ?", increment)).Error
  278. if err != nil {
  279. return err
  280. }
  281. // 删除相关缓存
  282. key := fmt.Sprintf(prefixPostKey, postId)
  283. m.redis.Del(ctx, key)
  284. m.ClearListCache(ctx)
  285. return nil
  286. }
  287. // IncrementCommentCount 增加评论数
  288. func (m *PostModel) IncrementCommentCount(ctx context.Context, postId int64, increment int) error {
  289. err := m.conn.WithContext(ctx).Model(&Post{}).
  290. Where("id = ?", postId).
  291. UpdateColumn("comment_count", gorm.Expr("comment_count + ?", increment)).Error
  292. if err != nil {
  293. return err
  294. }
  295. // 删除相关缓存
  296. key := fmt.Sprintf(prefixPostKey, postId)
  297. m.redis.Del(ctx, key)
  298. m.ClearListCache(ctx)
  299. return nil
  300. }
  301. // IncrementShareCount 增加分享数
  302. func (m *PostModel) IncrementShareCount(ctx context.Context, postId int64) error {
  303. err := m.conn.WithContext(ctx).Model(&Post{}).
  304. Where("id = ?", postId).
  305. UpdateColumn("share_count", gorm.Expr("share_count + ?", 1)).Error
  306. if err != nil {
  307. return err
  308. }
  309. // 删除相关缓存
  310. key := fmt.Sprintf(prefixPostKey, postId)
  311. m.redis.Del(ctx, key)
  312. m.ClearListCache(ctx)
  313. return nil
  314. }
  315. // Delete 删除帖子
  316. func (m *PostModel) Delete(ctx context.Context, postId int64) error {
  317. return m.conn.WithContext(ctx).Model(&Post{}).
  318. Where("id = ?", postId).
  319. Update("is_del", 1).Error
  320. }
  321. // ClearCache 清除帖子相关的所有缓存
  322. func (m *PostModel) ClearCache(ctx context.Context, postId int64) {
  323. // 删除帖子详情缓存
  324. key := fmt.Sprintf(prefixPostKey, postId)
  325. m.redis.Del(ctx, key)
  326. // 删除帖子内容缓存
  327. key = fmt.Sprintf(prefixPostContentKey, postId)
  328. m.redis.Del(ctx, key)
  329. // 删除帖子列表缓存
  330. m.ClearListCache(ctx)
  331. }
  332. // IncrementUpvoteCount 增加点赞数
  333. func (m *PostModel) IncrementUpvoteCount(ctx context.Context, postId int64, increment int) error {
  334. err := m.conn.WithContext(ctx).Model(&Post{}).
  335. Where("id = ?", postId).
  336. UpdateColumn("upvote_count", gorm.Expr("upvote_count + ?", increment)).Error
  337. if err != nil {
  338. return err
  339. }
  340. // 删除相关缓存
  341. key := fmt.Sprintf(prefixPostKey, postId)
  342. m.redis.Del(ctx, key)
  343. m.ClearListCache(ctx)
  344. return nil
  345. }
  346. // GetUserCollectionPosts 获取用户收藏的帖子列表(带缓存)
  347. func (m *PostModel) GetUserCollectionPosts(ctx context.Context, userId int64, page, pageSize int) ([]*Post, int64, error) {
  348. // 尝试从缓存获取
  349. key := fmt.Sprintf("user:collection:%d:%d:%d", userId, page, pageSize)
  350. data, err := m.redis.Get(ctx, key).Bytes()
  351. if err == nil {
  352. var cacheData struct {
  353. Posts []*Post
  354. Total int64
  355. }
  356. if err := json.Unmarshal(data, &cacheData); err == nil {
  357. return cacheData.Posts, cacheData.Total, nil
  358. }
  359. }
  360. // 从数据库获取
  361. var posts []*Post
  362. var total int64
  363. // 获取收藏总数
  364. err = m.conn.Model(&PostAction{}).
  365. Where("user_id = ? AND action_type = ? AND is_del = 0", userId, 1). // 1表示收藏动作
  366. Count(&total).Error
  367. if err != nil {
  368. return nil, 0, err
  369. }
  370. // 获取收藏的帖子列表
  371. offset := (page - 1) * pageSize
  372. err = m.conn.Table("p_post_action pa").
  373. Select("p.*").
  374. Joins("LEFT JOIN p_post p ON p.id = pa.post_id").
  375. Where("pa.user_id = ? AND pa.action_type = ? AND pa.is_del = 0 AND p.is_del = 0", userId, 1).
  376. Order("pa.created_on DESC").
  377. Offset(offset).
  378. Limit(pageSize).
  379. Find(&posts).Error
  380. if err != nil {
  381. return nil, 0, err
  382. }
  383. // 写入缓存
  384. cacheData := struct {
  385. Posts []*Post
  386. Total int64
  387. }{
  388. Posts: posts,
  389. Total: total,
  390. }
  391. if data, err := json.Marshal(cacheData); err == nil {
  392. m.redis.Set(ctx, key, data, 5*time.Minute)
  393. }
  394. return posts, total, nil
  395. }
  396. // GetUserLikePosts 获取用户点赞的帖子列表(带缓存)
  397. func (m *PostModel) GetUserLikePosts(ctx context.Context, userId int64, page, pageSize int) ([]*Post, int64, error) {
  398. // 尝试从缓存获取
  399. key := fmt.Sprintf("user:like:%d:%d:%d", userId, page, pageSize)
  400. data, err := m.redis.Get(ctx, key).Bytes()
  401. if err == nil {
  402. var cacheData struct {
  403. Posts []*Post
  404. Total int64
  405. }
  406. if err := json.Unmarshal(data, &cacheData); err == nil {
  407. return cacheData.Posts, cacheData.Total, nil
  408. }
  409. }
  410. // 从数据库获取
  411. var posts []*Post
  412. var total int64
  413. // 获取点赞总数
  414. err = m.conn.Model(&PostAction{}).
  415. Where("user_id = ? AND action_type = ? AND is_del = 0", userId, 0). // 0表示点赞动作
  416. Count(&total).Error
  417. if err != nil {
  418. return nil, 0, err
  419. }
  420. // 获取点赞的帖子列表
  421. offset := (page - 1) * pageSize
  422. err = m.conn.Table("p_post_action pa").
  423. Select("p.*").
  424. Joins("LEFT JOIN p_post p ON p.id = pa.post_id").
  425. Where("pa.user_id = ? AND pa.action_type = ? AND pa.is_del = 0 AND p.is_del = 0", userId, 0).
  426. Order("pa.created_on DESC").
  427. Offset(offset).
  428. Limit(pageSize).
  429. Find(&posts).Error
  430. if err != nil {
  431. return nil, 0, err
  432. }
  433. // 写入缓存
  434. cacheData := struct {
  435. Posts []*Post
  436. Total int64
  437. }{
  438. Posts: posts,
  439. Total: total,
  440. }
  441. if data, err := json.Marshal(cacheData); err == nil {
  442. m.redis.Set(ctx, key, data, 5*time.Minute)
  443. }
  444. return posts, total, nil
  445. }
  446. // GetUserPosts 获取用户发布的帖子列表(带缓存)
  447. func (m *PostModel) GetUserPosts(ctx context.Context, userId int64, page, pageSize int) ([]*Post, int64, error) {
  448. // 尝试从缓存获取
  449. key := fmt.Sprintf("user:posts:%d:%d:%d", userId, page, pageSize)
  450. data, err := m.redis.Get(ctx, key).Bytes()
  451. if err == nil {
  452. var cacheData struct {
  453. Posts []*Post
  454. Total int64
  455. }
  456. if err := json.Unmarshal(data, &cacheData); err == nil {
  457. return cacheData.Posts, cacheData.Total, nil
  458. }
  459. }
  460. // 从数据库获取
  461. var posts []*Post
  462. var total int64
  463. // 获取发布帖子总数
  464. err = m.conn.Model(&Post{}).
  465. Where("user_id = ? AND is_del = 0", userId).
  466. Count(&total).Error
  467. if err != nil {
  468. return nil, 0, err
  469. }
  470. // 获取发布的帖子列表
  471. offset := (page - 1) * pageSize
  472. err = m.conn.Where("user_id = ? AND is_del = 0", userId).
  473. Order("created_on DESC").
  474. Offset(offset).
  475. Limit(pageSize).
  476. Find(&posts).Error
  477. if err != nil {
  478. return nil, 0, err
  479. }
  480. // 写入缓存
  481. cacheData := struct {
  482. Posts []*Post
  483. Total int64
  484. }{
  485. Posts: posts,
  486. Total: total,
  487. }
  488. if data, err := json.Marshal(cacheData); err == nil {
  489. m.redis.Set(ctx, key, data, 5*time.Minute)
  490. }
  491. return posts, total, nil
  492. }