package model import ( "context" "encoding/json" "fmt" "time" "git.banshen.xyz/huangguangrong/slow_wild_protobuff/slowwild/slowwildserver" "github.com/go-redis/redis/v8" "gorm.io/gorm" ) const ( // 缓存key前缀 prefixPostKey = "post:%d" // 帖子信息 prefixPostContentKey = "post:content:%d" // 帖子内容 prefixUserLikeKey = "user:like:%d:%d" // 用户点赞状态 prefixUserCollectKey = "user:collect:%d:%d" // 用户收藏状态 prefixPostListKey = "post:list:%d:%d:%d:%s" // 帖子列表 (searchType:sortType:page:keyword) cacheExpiry = 24 * time.Hour // 缓存过期时间 ) // PPost 冒泡/文章 type Post struct { *Model UserId int64 `json:"user_id" gorm:"user_id"` // 用户ID PostType int32 `json:"post_type" gorm:"post_type"` // 帖子类型0-普通图文1-视频 CommentCount int64 `json:"comment_count" gorm:"comment_count"` // 评论数 CollectionCount int64 `json:"collection_count" gorm:"collection_count"` // 收藏数 UpvoteCount int64 `json:"upvote_count" gorm:"upvote_count"` // 点赞数 ShareCount int64 `json:"share_count" gorm:"share_count"` // 分享数 Visibility int8 `json:"visibility" gorm:"visibility"` // 可见性: 0私密 1好友可见 2关注可见 4公开 IsTop int8 `json:"is_top" gorm:"is_top"` // 是否置顶 IsEssence int8 `json:"is_essence" gorm:"is_essence"` // 是否精华 LatestRepliedOn int64 `json:"latest_replied_on" gorm:"latest_replied_on"` // 最新回复时间 Tags string `json:"tags" gorm:"tags"` // 标签 Ip string `json:"ip" gorm:"ip"` // IP地址 IpLoc string `json:"ip_loc" gorm:"ip_loc"` // IP城市地址 WithUserIds string `json:"with_user_ids" gorm:"with_user_ids"` // 被艾特的用户 HotNum int64 `json:"hot_num" gorm:"hot_num"` // 热度值 } // TableName 表名称 func (*Post) TableName() string { return "p_post" } // PostModel 帖子模型 type PostModel struct { conn *gorm.DB redis *redis.Client } func NewPostModel(conn *gorm.DB, rdb *redis.Client) *PostModel { return &PostModel{ conn: conn, redis: rdb, } } // Create 创建帖子 func (m *PostModel) Create(ctx context.Context, post *Post) error { return m.conn.WithContext(ctx).Create(post).Error } // PostContentModel 帖子内容模型 type PostContentModel struct { conn *gorm.DB } func NewPostContentModel(conn *gorm.DB) *PostContentModel { return &PostContentModel{ conn: conn, } } // Create 创建帖子内容 func (m *PostContentModel) Create(ctx context.Context, content *PostContent) error { return m.conn.WithContext(ctx).Create(content).Error } // GetPostList 根据条件获取帖子列表 func (m *PostModel) GetPostList(ctx context.Context, req *slowwildserver.GetPostListReq) ([]*Post, error) { // 构建缓存key cacheKey := fmt.Sprintf(prefixPostListKey, req.SearchType, req.SortType, req.Page, req.Keyword) // 尝试从缓存获取 var posts []*Post data, err := m.redis.Get(ctx, cacheKey).Bytes() if err == nil { if err := json.Unmarshal(data, &posts); err == nil { return posts, nil } } var normalPosts []*Post // 先查询置顶帖子 topQuery := m.conn.WithContext(ctx).Model(&Post{}).Where("is_top = ?", 1) normalQuery := m.conn.WithContext(ctx).Model(&Post{}).Where("is_top = ?", 0) // 根据搜索类型构建查询条件 switch req.SearchType { case 1: // 搜索内容/标题 subQuery := fmt.Sprintf("%%%s%%", req.Keyword) topQuery = topQuery.Joins("LEFT JOIN p_post_content ON p_post.id = p_post_content.post_id"). Where("p_post_content.content LIKE ? OR p_post_content.title LIKE ?", subQuery, subQuery) normalQuery = normalQuery.Joins("LEFT JOIN p_post_content ON p_post.id = p_post_content.post_id"). Where("p_post_content.content LIKE ? OR p_post_content.title LIKE ?", subQuery, subQuery) case 2: // 话题搜索 topQuery = topQuery.Joins("JOIN p_tag_with_post ON p_tag_with_post.post_id = p_post.id"). Where("p_tag_with_post.tag_id = ?", req.Keyword) normalQuery = normalQuery.Joins("JOIN p_tag_with_post ON p_tag_with_post.post_id = p_post.id"). Where("p_tag_with_post.tag_id = ?", req.Keyword) } // 根据排序类型构建排序条件 switch req.SortType { case 1: // 热度排序 topQuery = topQuery.Order("hot_num DESC") normalQuery = normalQuery.Order("hot_num DESC") default: // 默认创建时间排序 topQuery = topQuery.Order("created_on DESC") normalQuery = normalQuery.Order("created_on DESC") } // 查询置顶帖子 if err := topQuery.Find(&posts).Error; err != nil { return nil, err } // 查询普通帖子 offset := (req.Page - 1) * req.PageSize limit := int(req.PageSize) - len(posts) if limit > 0 { if err := normalQuery.Offset(int(offset)).Limit(limit).Find(&normalPosts).Error; err != nil { return nil, err } posts = append(posts, normalPosts...) } // 缓存查询结果 if data, err := json.Marshal(posts); err == nil { // 设置较短的过期时间,因为列表数据变化较频繁 m.redis.Set(ctx, cacheKey, data, 5*time.Minute) } return posts, nil } // GetPostContents 批量获取帖子内容(带缓存) func (m *PostModel) GetPostContents(ctx context.Context, postIds []int64) (map[int64]*PostContent, error) { contentMap := make(map[int64]*PostContent) var missedIds []int64 // 先从Redis获取 for _, id := range postIds { key := fmt.Sprintf(prefixPostContentKey, id) data, err := m.redis.Get(ctx, key).Bytes() if err == nil { var content PostContent if err := json.Unmarshal(data, &content); err == nil { contentMap[id] = &content continue } } missedIds = append(missedIds, id) } // 查询未命中的内容 if len(missedIds) > 0 { var contents []*PostContent err := m.conn.WithContext(ctx).Where("post_id IN ?", missedIds).Find(&contents).Error if err != nil { return nil, err } // 写入缓存并添加到结果 for _, content := range contents { key := fmt.Sprintf(prefixPostContentKey, content.PostId) if data, err := json.Marshal(content); err == nil { m.redis.Set(ctx, key, data, cacheExpiry) } contentMap[content.PostId] = content } } return contentMap, nil } // IsPostLikedByUser 检查用户是否点赞了帖子(带缓存) func (m *PostModel) IsPostLikedByUser(ctx context.Context, userId, postId int64) (bool, error) { key := fmt.Sprintf(prefixUserLikeKey, userId, postId) exists, err := m.redis.Exists(ctx, key).Result() if err == nil && exists == 1 { return m.redis.Get(ctx, key).Bool() } var count int64 err = m.conn.WithContext(ctx). Model(&PostAction{}). Where("user_id = ? AND post_id = ? and action_type = 0", userId, postId). Count(&count).Error if err != nil { return false, err } liked := count > 0 m.redis.Set(ctx, key, liked, cacheExpiry) return liked, nil } // IsPostCollectedByUser 检查用户是否收藏了帖子(带缓存) func (m *PostModel) IsPostCollectedByUser(ctx context.Context, userId, postId int64) (bool, error) { key := fmt.Sprintf(prefixUserCollectKey, userId, postId) exists, err := m.redis.Exists(ctx, key).Result() if err == nil && exists == 1 { return m.redis.Get(ctx, key).Bool() } var count int64 err = m.conn.WithContext(ctx). Model(&PostAction{}). Where("user_id = ? AND post_id = ? and action_type = 1", userId, postId). Count(&count).Error if err != nil { return false, err } collected := count > 0 m.redis.Set(ctx, key, collected, cacheExpiry) return collected, nil } // 在需要更新帖子时,清除相关缓存 func (m *PostModel) ClearListCache(ctx context.Context) error { // 使用通配符匹配所有列表缓存 pattern := "post:list:*" keys, err := m.redis.Keys(ctx, pattern).Result() if err != nil { return err } if len(keys) > 0 { return m.redis.Del(ctx, keys...).Err() } return nil } // UpdatePost 更新帖子时清除缓存 func (m *PostModel) UpdatePost(ctx context.Context, post *Post) error { err := m.conn.WithContext(ctx).Save(post).Error if err != nil { return err } // 清除帖子相关的所有缓存 m.redis.Del(ctx, fmt.Sprintf(prefixPostKey, post.ID)) m.ClearListCache(ctx) return nil } // GetPostDetail 获取帖子详情(带缓存) func (m *PostModel) GetPostDetail(ctx context.Context, postId int64) (*Post, error) { // 尝试从缓存获取 key := fmt.Sprintf(prefixPostKey, postId) data, err := m.redis.Get(ctx, key).Bytes() if err == nil { var post Post if err := json.Unmarshal(data, &post); err == nil { return &post, nil } } // 从数据库获取 var post Post err = m.conn.WithContext(ctx).First(&post, postId).Error if err != nil { return nil, err } // 写入缓存 if data, err := json.Marshal(post); err == nil { m.redis.Set(ctx, key, data, cacheExpiry) } return &post, nil } // GetPostContent 获取帖子内容(带缓存) func (m *PostModel) GetPostContent(ctx context.Context, postId int64) (*PostContent, error) { // 尝试从缓存获取 key := fmt.Sprintf(prefixPostContentKey, postId) data, err := m.redis.Get(ctx, key).Bytes() if err == nil { var content PostContent if err := json.Unmarshal(data, &content); err == nil { return &content, nil } } // 从数据库获取 var content PostContent err = m.conn.WithContext(ctx).Where("post_id = ?", postId).First(&content).Error if err != nil { return nil, err } // 写入缓存 if data, err := json.Marshal(content); err == nil { m.redis.Set(ctx, key, data, cacheExpiry) } return &content, nil } // IncrementCollectionCount 增加收藏数 func (m *PostModel) IncrementCollectionCount(ctx context.Context, postId int64, increment int) error { err := m.conn.WithContext(ctx).Model(&Post{}). Where("id = ?", postId). UpdateColumn("collection_count", gorm.Expr("collection_count + ?", increment)).Error if err != nil { return err } // 删除相关缓存 key := fmt.Sprintf(prefixPostKey, postId) m.redis.Del(ctx, key) m.ClearListCache(ctx) return nil } // IncrementCommentCount 增加评论数 func (m *PostModel) IncrementCommentCount(ctx context.Context, postId int64, increment int) error { err := m.conn.WithContext(ctx).Model(&Post{}). Where("id = ?", postId). UpdateColumn("comment_count", gorm.Expr("comment_count + ?", increment)).Error if err != nil { return err } // 删除相关缓存 key := fmt.Sprintf(prefixPostKey, postId) m.redis.Del(ctx, key) m.ClearListCache(ctx) return nil } // IncrementShareCount 增加分享数 func (m *PostModel) IncrementShareCount(ctx context.Context, postId int64) error { err := m.conn.WithContext(ctx).Model(&Post{}). Where("id = ?", postId). UpdateColumn("share_count", gorm.Expr("share_count + ?", 1)).Error if err != nil { return err } // 删除相关缓存 key := fmt.Sprintf(prefixPostKey, postId) m.redis.Del(ctx, key) m.ClearListCache(ctx) return nil } // Delete 删除帖子 func (m *PostModel) Delete(ctx context.Context, postId int64) error { return m.conn.WithContext(ctx).Model(&Post{}). Where("id = ?", postId). Update("is_del", 1).Error } // ClearCache 清除帖子相关的所有缓存 func (m *PostModel) ClearCache(ctx context.Context, postId int64) { // 删除帖子详情缓存 key := fmt.Sprintf(prefixPostKey, postId) m.redis.Del(ctx, key) // 删除帖子内容缓存 key = fmt.Sprintf(prefixPostContentKey, postId) m.redis.Del(ctx, key) // 删除帖子列表缓存 m.ClearListCache(ctx) } // IncrementUpvoteCount 增加点赞数 func (m *PostModel) IncrementUpvoteCount(ctx context.Context, postId int64, increment int) error { err := m.conn.WithContext(ctx).Model(&Post{}). Where("id = ?", postId). UpdateColumn("upvote_count", gorm.Expr("upvote_count + ?", increment)).Error if err != nil { return err } // 删除相关缓存 key := fmt.Sprintf(prefixPostKey, postId) m.redis.Del(ctx, key) m.ClearListCache(ctx) return nil } // GetUserCollectionPosts 获取用户收藏的帖子列表(带缓存) func (m *PostModel) GetUserCollectionPosts(ctx context.Context, userId int64, page, pageSize int) ([]*Post, int64, error) { // 尝试从缓存获取 key := fmt.Sprintf("user:collection:%d:%d:%d", userId, page, pageSize) data, err := m.redis.Get(ctx, key).Bytes() if err == nil { var cacheData struct { Posts []*Post Total int64 } if err := json.Unmarshal(data, &cacheData); err == nil { return cacheData.Posts, cacheData.Total, nil } } // 从数据库获取 var posts []*Post var total int64 // 获取收藏总数 err = m.conn.Model(&PostAction{}). Where("user_id = ? AND action_type = ? AND is_del = 0", userId, 1). // 1表示收藏动作 Count(&total).Error if err != nil { return nil, 0, err } // 获取收藏的帖子列表 offset := (page - 1) * pageSize err = m.conn.Table("p_post_action pa"). Select("p.*"). Joins("LEFT JOIN p_post p ON p.id = pa.post_id"). Where("pa.user_id = ? AND pa.action_type = ? AND pa.is_del = 0 AND p.is_del = 0", userId, 1). Order("pa.created_on DESC"). Offset(offset). Limit(pageSize). Find(&posts).Error if err != nil { return nil, 0, err } // 写入缓存 cacheData := struct { Posts []*Post Total int64 }{ Posts: posts, Total: total, } if data, err := json.Marshal(cacheData); err == nil { m.redis.Set(ctx, key, data, 5*time.Minute) } return posts, total, nil } // GetUserLikePosts 获取用户点赞的帖子列表(带缓存) func (m *PostModel) GetUserLikePosts(ctx context.Context, userId int64, page, pageSize int) ([]*Post, int64, error) { // 尝试从缓存获取 key := fmt.Sprintf("user:like:%d:%d:%d", userId, page, pageSize) data, err := m.redis.Get(ctx, key).Bytes() if err == nil { var cacheData struct { Posts []*Post Total int64 } if err := json.Unmarshal(data, &cacheData); err == nil { return cacheData.Posts, cacheData.Total, nil } } // 从数据库获取 var posts []*Post var total int64 // 获取点赞总数 err = m.conn.Model(&PostAction{}). Where("user_id = ? AND action_type = ? AND is_del = 0", userId, 0). // 0表示点赞动作 Count(&total).Error if err != nil { return nil, 0, err } // 获取点赞的帖子列表 offset := (page - 1) * pageSize err = m.conn.Table("p_post_action pa"). Select("p.*"). Joins("LEFT JOIN p_post p ON p.id = pa.post_id"). Where("pa.user_id = ? AND pa.action_type = ? AND pa.is_del = 0 AND p.is_del = 0", userId, 0). Order("pa.created_on DESC"). Offset(offset). Limit(pageSize). Find(&posts).Error if err != nil { return nil, 0, err } // 写入缓存 cacheData := struct { Posts []*Post Total int64 }{ Posts: posts, Total: total, } if data, err := json.Marshal(cacheData); err == nil { m.redis.Set(ctx, key, data, 5*time.Minute) } return posts, total, nil } // GetUserPosts 获取用户发布的帖子列表(带缓存) func (m *PostModel) GetUserPosts(ctx context.Context, userId int64, page, pageSize int) ([]*Post, int64, error) { // 尝试从缓存获取 key := fmt.Sprintf("user:posts:%d:%d:%d", userId, page, pageSize) data, err := m.redis.Get(ctx, key).Bytes() if err == nil { var cacheData struct { Posts []*Post Total int64 } if err := json.Unmarshal(data, &cacheData); err == nil { return cacheData.Posts, cacheData.Total, nil } } // 从数据库获取 var posts []*Post var total int64 // 获取发布帖子总数 err = m.conn.Model(&Post{}). Where("user_id = ? AND is_del = 0", userId). Count(&total).Error if err != nil { return nil, 0, err } // 获取发布的帖子列表 offset := (page - 1) * pageSize err = m.conn.Where("user_id = ? AND is_del = 0", userId). Order("created_on DESC"). Offset(offset). Limit(pageSize). Find(&posts).Error if err != nil { return nil, 0, err } // 写入缓存 cacheData := struct { Posts []*Post Total int64 }{ Posts: posts, Total: total, } if data, err := json.Marshal(cacheData); err == nil { m.redis.Set(ctx, key, data, 5*time.Minute) } return posts, total, nil }