数据结构优化
This commit is contained in:
@@ -368,3 +368,38 @@ func GetPostsByTagID(c *gin.Context) {
|
||||
utils.Success(c, res)
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecommendedPosts 获取推荐文章(基于IP的协同过滤)
|
||||
func GetRecommendedPosts(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
|
||||
utils.Error(c, 400, "Invalid post ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取客户端IP
|
||||
userIP := c.ClientIP()
|
||||
|
||||
// 获取推荐文章(默认3篇)
|
||||
limit := 3
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 10 {
|
||||
limit = l
|
||||
}
|
||||
}
|
||||
|
||||
posts, err := repositories.GetRecommendedPostsByIP(id, userIP, limit)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
responses := repositories.BuildPostsResponse(posts)
|
||||
if responses == nil {
|
||||
utils.Success(c, []interface{}{})
|
||||
} else {
|
||||
utils.Success(c, responses)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ func main() {
|
||||
// 博客路由
|
||||
api.GET("/posts", handlers.GetPosts)
|
||||
api.GET("/posts/:id", handlers.GetPost)
|
||||
api.GET("/posts/:id/recommendations", handlers.GetRecommendedPosts)
|
||||
|
||||
// 分类路由
|
||||
api.GET("/categories", handlers.GetCategories)
|
||||
|
||||
@@ -339,7 +339,8 @@ func SavePostHistory(post *models.Post, modifiedBy uint) error {
|
||||
func GetTopPosts(limit int) ([]models.Post, error) {
|
||||
var posts []models.Post
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Select("id, title, read_count").
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("is_published = ? AND deleted_at = ?", 1, 0).
|
||||
Order("read_count DESC").
|
||||
Limit(limit).
|
||||
@@ -470,3 +471,95 @@ func BuildPostHistoryResponses(history []models.PostHistory) []models.PostHistor
|
||||
}
|
||||
return responses
|
||||
}
|
||||
|
||||
// GetRecommendedPostsByIP 基于IP的协同过滤推荐算法
|
||||
// 查找相同IP访问过的其他文章,按访问频率排序返回
|
||||
func GetRecommendedPostsByIP(currentPostID uint, userIP string, limit int) ([]models.Post, error) {
|
||||
if userIP == "" {
|
||||
// 如果IP为空,返回热门文章作为备选
|
||||
return GetTopPosts(limit)
|
||||
}
|
||||
|
||||
// 1. 查找相同IP访问过的其他文章ID及其访问次数
|
||||
type PostAccessCount struct {
|
||||
ArticleID uint
|
||||
Count int64
|
||||
}
|
||||
|
||||
var accessCounts []PostAccessCount
|
||||
err := config.DB.Model(&models.UserAccessLog{}).
|
||||
Select("article_id, COUNT(*) as count").
|
||||
Where("user_ip = ? AND article_id != ? AND deleted_at = ?", userIP, currentPostID, 0).
|
||||
Group("article_id").
|
||||
Order("count DESC").
|
||||
Limit(limit * 2). // 多查询一些,因为后面还要过滤未发布的文章
|
||||
Scan(&accessCounts).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error querying recommended posts by IP: %v", err)
|
||||
// 如果查询失败,返回热门文章作为备选
|
||||
return GetTopPosts(limit)
|
||||
}
|
||||
|
||||
if len(accessCounts) == 0 {
|
||||
// 如果没有访问记录,返回热门文章作为备选
|
||||
return GetTopPosts(limit)
|
||||
}
|
||||
|
||||
// 2. 提取文章ID列表
|
||||
articleIDs := make([]uint, 0, len(accessCounts))
|
||||
for _, ac := range accessCounts {
|
||||
articleIDs = append(articleIDs, ac.ArticleID)
|
||||
}
|
||||
|
||||
// 3. 查询这些文章的详细信息(排除未发布的文章)
|
||||
var posts []models.Post
|
||||
err = config.DB.Model(&models.Post{}).
|
||||
Preload("Category").
|
||||
Preload("Tags").
|
||||
Where("id IN ? AND is_published = ? AND deleted_at = ?", articleIDs, 1, 0).
|
||||
Find(&posts).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error fetching recommended posts: %v", err)
|
||||
return GetTopPosts(limit)
|
||||
}
|
||||
|
||||
// 4. 按照访问次数排序(保持与 accessCounts 的顺序一致)
|
||||
postMap := make(map[uint]models.Post)
|
||||
for _, post := range posts {
|
||||
postMap[post.ID] = post
|
||||
}
|
||||
|
||||
var sortedPosts []models.Post
|
||||
for _, ac := range accessCounts {
|
||||
if post, exists := postMap[ac.ArticleID]; exists {
|
||||
sortedPosts = append(sortedPosts, post)
|
||||
if len(sortedPosts) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 如果推荐的文章数量不足,用热门文章补充
|
||||
if len(sortedPosts) < limit {
|
||||
topPosts, err := GetTopPosts(limit - len(sortedPosts))
|
||||
if err == nil {
|
||||
// 排除已经推荐的文章
|
||||
existingIDs := make(map[uint]bool)
|
||||
for _, p := range sortedPosts {
|
||||
existingIDs[p.ID] = true
|
||||
}
|
||||
for _, p := range topPosts {
|
||||
if !existingIDs[p.ID] && p.ID != currentPostID {
|
||||
sortedPosts = append(sortedPosts, p)
|
||||
if len(sortedPosts) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sortedPosts, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user