473 lines
12 KiB
Go
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
|
|
}
|