Files
nl-blogs/server/repositories/post_repository.go
李琦 34fe612aab 2
2026-01-20 08:43:35 +08:00

473 lines
12 KiB
Go

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
}