package repositories import ( "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" "gorm.io/gorm" ) // TrendData 趋势数据 type TrendData struct { Date string `json:"date"` Count int `json:"value"` YoY float64 `json:"yoy"` MoM float64 `json:"mom"` } // GetPosts 获取所有博客文章(支持搜索、分类、标签筛选) func GetPosts(keyword string, categoryID uint, tagID uint, columnID uint) ([]models.Post, error) { var posts []models.Post query := config.DB.Model(&models.Post{}). Preload("Category"). Preload("Column"). Preload("Tags"). Where("is_published = ? AND deleted_at = ?", 1, 0) if tagID > 0 { query = query.Joins("JOIN post_tags pt ON posts.id = pt.post_id"). Where("pt.tag_id = ?", tagID) } if categoryID > 0 { query = query.Where("category_id = ?", categoryID) } if columnID > 0 { query = query.Where("column_id = ?", columnID) } if keyword != "" { likeKeyword := "%" + keyword + "%" query = query.Where("(MATCH(title, content) AGAINST(? IN BOOLEAN MODE) OR title LIKE ? OR content LIKE ?)", keyword, likeKeyword, likeKeyword) } err := query.Order("created_at DESC").Find(&posts).Error if err != nil { log.Printf("Error querying posts: %v", err) return nil, err } return posts, nil } // GetPostByID 根据ID获取博客文章 func GetPostByID(id uint) (*models.Post, error) { var post models.Post // 显式选择所有字段,确保 content 字段被加载 err := config.DB.Model(&models.Post{}). Select("id", "title", "category_id", "column_id", "excerpt", "content", "read_count", "is_published", "created_at", "updated_at", "deleted_at"). Preload("Category"). Preload("Column"). Preload("Tags"). Where("id = ? AND is_published = ? AND deleted_at = ?", id, 1, 0). First(&post).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil, nil } log.Printf("Error getting post by ID: %v", err) return nil, err } // 更新阅读量 config.DB.Model(&models.Post{}). Where("id = ?", id). UpdateColumn("read_count", gorm.Expr("read_count + ?", 1)) return &post, nil } // GetAllPosts 获取所有博客文章(包括未发布的,后台用) func GetAllPosts(page, pageSize int) ([]models.Post, int64, error) { offset := (page - 1) * pageSize var posts []models.Post var total int64 // Count total err := config.DB.Model(&models.Post{}). Where("deleted_at = ?", 0). Count(&total).Error if err != nil { log.Printf("Error counting posts: %v", err) return nil, 0, err } // Get posts err = config.DB.Model(&models.Post{}). Preload("Category"). Preload("Column"). Preload("Tags"). Where("deleted_at = ?", 0). Order("created_at DESC"). Limit(pageSize). Offset(offset). Find(&posts).Error if err != nil { log.Printf("Error querying all posts: %v", err) return nil, 0, err } return posts, total, nil } // CreatePost 创建博客文章 func CreatePost(post *models.Post) error { err := config.DB.Create(post).Error if err != nil { log.Printf("Error creating post: %v", err) return err } // Insert Tags if len(post.Tags) > 0 { err = config.DB.Model(post).Association("Tags").Replace(post.Tags) if err != nil { log.Printf("Error associating tags: %v", err) return err } } // Handle column association if ColumnID is set if post.ColumnID != nil && *post.ColumnID > 0 { // Add to column_posts table err = AddPostToColumn(*post.ColumnID, post.ID, 0) if err != nil { log.Printf("Error adding post to column: %v", err) // Don't fail the whole operation, just log } } return nil } // UpdatePost 更新博客文章 func UpdatePost(post *models.Post) error { updateData := map[string]interface{}{ "title": post.Title, "category_id": post.CategoryID, "excerpt": post.Excerpt, "content": post.Content, "is_published": post.IsPublished, "updated_at": time.Now().Unix(), } // Update column_id if provided if post.ColumnID != nil { updateData["column_id"] = post.ColumnID } else { // Set to NULL if explicitly set to nil updateData["column_id"] = nil } err := config.DB.Model(&models.Post{}). Where("id = ? AND deleted_at = ?", post.ID, 0). Updates(updateData).Error if err != nil { log.Printf("Error updating post: %v", err) return err } // Update Tags if len(post.Tags) > 0 { err = config.DB.Model(&models.Post{ID: post.ID}).Association("Tags").Replace(post.Tags) if err != nil { log.Printf("Error updating tags: %v", err) return err } } else { // Clear all tags err = config.DB.Model(&models.Post{ID: post.ID}).Association("Tags").Clear() if err != nil { log.Printf("Error clearing tags: %v", err) return err } } // Handle column association // Always sync column_posts table with column_id // First, remove from all columns err = config.DB.Where("post_id = ?", post.ID).Delete(&models.ColumnPost{}).Error if err != nil { log.Printf("Error removing post from columns: %v", err) // Don't fail the whole operation } // Then add to new column if specified if post.ColumnID != nil && *post.ColumnID > 0 { err = AddPostToColumn(*post.ColumnID, post.ID, 0) if err != nil { log.Printf("Error adding post to column: %v", err) // Don't fail the whole operation } } return nil } // UpdatePostStatus 更新文章状态 func UpdatePostStatus(id uint, status int) error { err := config.DB.Model(&models.Post{}). Where("id = ? AND deleted_at = ?", id, 0). Updates(map[string]interface{}{ "is_published": status, "updated_at": time.Now().Unix(), }).Error return err } // DeletePost 删除博客文章 (Soft Delete) func DeletePost(id uint) error { err := config.DB.Model(&models.Post{}). Where("id = ?", id). Update("deleted_at", time.Now().Unix()).Error if err != nil { log.Printf("Error deleting post: %v", err) return err } return nil } // GetPostCount 获取文章总数 func GetPostCount() (int, error) { var count int64 err := config.DB.Model(&models.Post{}). Where("deleted_at = ?", 0). Count(&count).Error if err != nil { log.Printf("Error getting post count: %v", err) return 0, err } return int(count), nil } // BuildPostResponse 构建博客文章响应 func BuildPostResponse(post *models.Post, includeContent bool) *models.PostResponse { // Format CreatedAt to Date string dateStr := time.Unix(post.CreatedAt, 0).Format("2006-01-02") catName := "" catSlug := "" if post.Category != nil { catName = post.Category.Name catSlug = post.Category.Slug } colName := "" colSlug := "" if post.Column != nil { colName = post.Column.Name // 如果 Column 有 Slug 字段,可以在这里添加 // colSlug = post.Column.Slug } response := &models.PostResponse{ ID: post.ID, Title: post.Title, CategoryID: post.CategoryID, CategoryName: catName, CategorySlug: catSlug, ColumnID: post.ColumnID, ColumnName: colName, ColumnSlug: colSlug, Date: dateStr, Excerpt: post.Excerpt, Tags: post.Tags, IsPublished: post.IsPublished, ReadCount: post.ReadCount, } if includeContent { // 使用指针类型,确保即使内容为空字符串也会出现在 JSON 中 content := post.Content response.Content = &content } return response } // BuildPostsResponse 构建博客文章列表响应 func BuildPostsResponse(posts []models.Post) []models.PostResponse { var responses []models.PostResponse for _, post := range posts { responses = append(responses, *BuildPostResponse(&post, false)) } return responses } // SavePostHistory 保存文章历史记录 func SavePostHistory(post *models.Post, modifiedBy uint) error { // 获取当前最大版本号 var maxVersion int err := config.DB.Model(&models.PostHistory{}). Where("post_id = ?", post.ID). Select("COALESCE(MAX(version), 0)"). Scan(&maxVersion).Error if err != nil { log.Printf("Error getting max version: %v", err) return err } history := &models.PostHistory{ PostID: post.ID, Version: maxVersion + 1, Title: post.Title, CategoryID: post.CategoryID, Excerpt: post.Excerpt, Content: post.Content, IsPublished: post.IsPublished, ModifiedBy: modifiedBy, } err = config.DB.Create(history).Error if err != nil { log.Printf("Error saving post history: %v", err) return err } return nil } // GetTopPosts 获取热门文章 (按阅读量) func GetTopPosts(limit int) ([]models.Post, error) { var posts []models.Post err := config.DB.Model(&models.Post{}). Select("id, title, read_count"). Where("is_published = ? AND deleted_at = ?", 1, 0). Order("read_count DESC"). Limit(limit). Find(&posts).Error if err != nil { return nil, err } return posts, nil } // GetNewPostsTrend 获取新增文章趋势 func GetNewPostsTrend(startDate, endDate string) ([]TrendData, error) { // 确定日期范围 var startTime, endTime time.Time if startDate != "" { startUnix := parseDateToUnix(startDate, false) startTime = time.Unix(startUnix, 0) } else { startTime = time.Now().AddDate(0, 0, -6) startTime = time.Date(startTime.Year(), startTime.Month(), startTime.Day(), 0, 0, 0, 0, time.Local) } if endDate != "" { endUnix := parseDateToUnix(endDate, true) endTime = time.Unix(endUnix, 0) } else { endTime = time.Now() endTime = time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 23, 59, 59, 0, time.Local) } // 查询数据库 query := config.DB.Model(&models.Post{}). Select("FROM_UNIXTIME(created_at, '%Y-%m-%d') as date, COUNT(*) as count"). Where("deleted_at = ?", 0). Where("created_at >= ?", startTime.Unix()). Where("created_at <= ?", endTime.Unix()) var results []TrendData err := query.Group("date"). Order("date ASC"). Scan(&results).Error if err != nil { return nil, err } // 创建日期到数据的映射 resultMap := make(map[string]TrendData) for _, r := range results { resultMap[r.Date] = r } // 生成完整日期列表 var fullResults []TrendData current := time.Date(startTime.Year(), startTime.Month(), startTime.Day(), 0, 0, 0, 0, time.Local) endDay := time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 0, 0, 0, 0, time.Local) for !current.After(endDay) { dateStr := current.Format("2006-01-02") if data, exists := resultMap[dateStr]; exists { data.YoY = 0 data.MoM = 0 fullResults = append(fullResults, data) } else { // 填充0值 fullResults = append(fullResults, TrendData{ Date: dateStr, Count: 0, YoY: 0, MoM: 0, }) } current = current.AddDate(0, 0, 1) } return fullResults, nil } // GetPostHistory 获取文章修改历史 func GetPostHistory(postID uint) ([]models.PostHistory, error) { var history []models.PostHistory err := config.DB.Model(&models.PostHistory{}). Where("post_id = ?", postID). Order("version DESC"). Find(&history).Error if err != nil { return nil, err } return history, nil } // GetPostHistoryByVersion 获取特定版本的历史记录 func GetPostHistoryByVersion(postID uint, version uint) (*models.PostHistory, error) { var h models.PostHistory err := config.DB.Model(&models.PostHistory{}). Where("post_id = ? AND version = ?", postID, version). First(&h).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil, nil } return nil, err } return &h, nil } // BuildPostHistoryResponse 构建历史记录响应 func BuildPostHistoryResponse(h *models.PostHistory) *models.PostHistoryResponse { return &models.PostHistoryResponse{ ID: h.ID, PostID: h.PostID, Version: h.Version, Title: h.Title, CategoryID: h.CategoryID, Date: time.Unix(h.CreatedAt, 0).Format("2006-01-02"), IsPublished: h.IsPublished, ModifiedBy: h.ModifiedBy, ModifiedAt: time.Unix(h.ModifiedAt, 0).Format("2006-01-02 15:04:05"), CreatedAt: time.Unix(h.CreatedAt, 0).Format("2006-01-02 15:04:05"), } } // BuildPostHistoryResponses 构建历史记录列表响应 func BuildPostHistoryResponses(history []models.PostHistory) []models.PostHistoryResponse { var responses []models.PostHistoryResponse for _, h := range history { responses = append(responses, *BuildPostHistoryResponse(&h)) } return responses }