数据结构优化

This commit is contained in:
李琦
2026-01-19 16:14:08 +08:00
parent 6d789c7c2a
commit 68c4c6df1c
36 changed files with 4063 additions and 760 deletions

View File

@@ -0,0 +1,249 @@
package repositories
import (
"log"
"time"
"github.com/niangaodev/art-code/config"
"github.com/niangaodev/art-code/models"
"gorm.io/gorm"
)
// CreateAttachment 创建附件记录
func CreateAttachment(attachment *models.Attachment) error {
err := config.DB.Create(attachment).Error
if err != nil {
log.Printf("Error creating attachment: %v", err)
return err
}
return nil
}
// GetAttachments 获取附件列表(分页)
func GetAttachments(page, pageSize int, categoryID *uint, fileType string) ([]models.Attachment, int64, error) {
offset := (page - 1) * pageSize
var attachments []models.Attachment
var total int64
query := config.DB.Model(&models.Attachment{}).
Preload("Category").
Where("deleted_at = ?", 0)
if categoryID != nil && *categoryID > 0 {
query = query.Where("category_id = ?", *categoryID)
}
if fileType != "" {
query = query.Where("file_type = ?", fileType)
}
// Count total
err := query.Count(&total).Error
if err != nil {
log.Printf("Error counting attachments: %v", err)
return nil, 0, err
}
// Get attachments
err = query.Order("created_at DESC").
Limit(pageSize).
Offset(offset).
Find(&attachments).Error
if err != nil {
log.Printf("Error querying attachments: %v", err)
return nil, 0, err
}
return attachments, total, nil
}
// GetAttachmentByID 根据ID获取附件
func GetAttachmentByID(id uint) (*models.Attachment, error) {
var attachment models.Attachment
err := config.DB.Model(&models.Attachment{}).
Preload("Category").
Where("id = ? AND deleted_at = ?", id, 0).
First(&attachment).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
log.Printf("Error getting attachment by ID: %v", err)
return nil, err
}
return &attachment, nil
}
// DeleteAttachment 删除附件(软删除)
func DeleteAttachment(id uint) error {
err := config.DB.Model(&models.Attachment{}).
Where("id = ?", id).
Update("deleted_at", time.Now().Unix()).Error
if err != nil {
log.Printf("Error deleting attachment: %v", err)
return err
}
return nil
}
// CreateAttachmentCategory 创建附件分类
func CreateAttachmentCategory(category *models.AttachmentCategory) error {
err := config.DB.Create(category).Error
if err != nil {
log.Printf("Error creating attachment category: %v", err)
return err
}
return nil
}
// GetAttachmentCategories 获取所有附件分类
func GetAttachmentCategories() ([]models.AttachmentCategory, error) {
var categories []models.AttachmentCategory
err := config.DB.Model(&models.AttachmentCategory{}).
Where("deleted_at = ?", 0).
Order("sort_order ASC, created_at DESC").
Find(&categories).Error
if err != nil {
log.Printf("Error querying attachment categories: %v", err)
return nil, err
}
return categories, nil
}
// UpdateAttachmentCategory 更新附件分类
func UpdateAttachmentCategory(category *models.AttachmentCategory) error {
err := config.DB.Model(&models.AttachmentCategory{}).
Where("id = ? AND deleted_at = ?", category.ID, 0).
Updates(map[string]interface{}{
"name": category.Name,
"description": category.Description,
"sort_order": category.SortOrder,
"updated_at": time.Now().Unix(),
}).Error
if err != nil {
log.Printf("Error updating attachment category: %v", err)
return err
}
return nil
}
// DeleteAttachmentCategory 删除附件分类(软删除)
func DeleteAttachmentCategory(id uint) error {
err := config.DB.Model(&models.AttachmentCategory{}).
Where("id = ?", id).
Update("deleted_at", time.Now().Unix()).Error
if err != nil {
log.Printf("Error deleting attachment category: %v", err)
return err
}
return nil
}
// CreateOSSConfig 创建OSS配置
func CreateOSSConfig(ossConfig *models.OSSConfig) error {
err := config.DB.Create(ossConfig).Error
if err != nil {
log.Printf("Error creating OSS config: %v", err)
return err
}
return nil
}
// GetOSSConfigs 获取所有OSS配置
func GetOSSConfigs() ([]models.OSSConfig, error) {
var configs []models.OSSConfig
err := config.DB.Model(&models.OSSConfig{}).
Where("deleted_at = ?", 0).
Order("created_at DESC").
Find(&configs).Error
if err != nil {
log.Printf("Error querying OSS configs: %v", err)
return nil, err
}
return configs, nil
}
// GetOSSConfigByID 根据ID获取OSS配置
func GetOSSConfigByID(id uint) (*models.OSSConfig, error) {
var ossConfig models.OSSConfig
err := config.DB.Model(&models.OSSConfig{}).
Where("id = ? AND deleted_at = ?", id, 0).
First(&ossConfig).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
log.Printf("Error getting OSS config by ID: %v", err)
return nil, err
}
return &ossConfig, nil
}
// GetActiveOSSConfig 获取启用的OSS配置
func GetActiveOSSConfig(storageType string) (*models.OSSConfig, error) {
var ossConfig models.OSSConfig
query := config.DB.Model(&models.OSSConfig{}).
Where("deleted_at = ? AND is_active = ?", 0, 1)
if storageType != "" {
query = query.Where("storage_type = ?", storageType)
}
err := query.First(&ossConfig).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
log.Printf("Error getting active OSS config: %v", err)
return nil, err
}
return &ossConfig, nil
}
// UpdateOSSConfig 更新OSS配置
func UpdateOSSConfig(ossConfig *models.OSSConfig) error {
err := config.DB.Model(&models.OSSConfig{}).
Where("id = ? AND deleted_at = ?", ossConfig.ID, 0).
Updates(map[string]interface{}{
"name": ossConfig.Name,
"storage_type": ossConfig.StorageType,
"access_key": ossConfig.AccessKey,
"secret_key": ossConfig.SecretKey,
"bucket": ossConfig.Bucket,
"region": ossConfig.Region,
"domain": ossConfig.Domain,
"is_active": ossConfig.IsActive,
"updated_at": time.Now().Unix(),
}).Error
if err != nil {
log.Printf("Error updating OSS config: %v", err)
return err
}
return nil
}
// DeleteOSSConfig 删除OSS配置(软删除)
func DeleteOSSConfig(id uint) error {
err := config.DB.Model(&models.OSSConfig{}).
Where("id = ?", id).
Update("deleted_at", time.Now().Unix()).Error
if err != nil {
log.Printf("Error deleting OSS config: %v", err)
return err
}
return nil
}

View File

@@ -39,6 +39,72 @@ func GetColumnByID(id uint) (*models.Column, error) {
return &col, nil
}
// GetColumnStats 获取专栏统计信息(文章数量和最近更新时间)
func GetColumnStats(columnID uint) (int64, int64, error) {
var postCount int64
// 统计文章数量
err := config.DB.Model(&models.Post{}).
Joins("JOIN column_posts cp ON posts.id = cp.post_id").
Where("cp.column_id = ? AND posts.deleted_at = ? AND posts.is_published = ?", columnID, 0, 1).
Count(&postCount).Error
if err != nil {
log.Printf("Error counting posts for column: %v", err)
return 0, 0, err
}
// 获取最近更新时间
var lastUpdated int64
if postCount > 0 {
err = config.DB.Model(&models.Post{}).
Select("COALESCE(MAX(posts.updated_at), 0)").
Joins("JOIN column_posts cp ON posts.id = cp.post_id").
Where("cp.column_id = ? AND posts.deleted_at = ? AND posts.is_published = ?", columnID, 0, 1).
Scan(&lastUpdated).Error
if err != nil {
log.Printf("Error getting last updated time for column: %v", err)
// 如果查询失败,使用专栏的更新时间
var col models.Column
if err2 := config.DB.Model(&models.Column{}).
Select("updated_at").
Where("id = ?", columnID).
First(&col).Error; err2 == nil {
lastUpdated = col.UpdatedAt
}
}
} else {
// 如果没有文章,使用专栏的更新时间
var col models.Column
if err := config.DB.Model(&models.Column{}).
Select("updated_at").
Where("id = ?", columnID).
First(&col).Error; err == nil {
lastUpdated = col.UpdatedAt
}
}
return postCount, lastUpdated, nil
}
// BuildColumnResponse 构建专栏响应(包含统计信息)
func BuildColumnResponse(col *models.Column) map[string]interface{} {
postCount, lastUpdated, _ := GetColumnStats(col.ID)
return map[string]interface{}{
"id": col.ID,
"name": col.Name,
"description": col.Description,
"cover": col.Cover,
"isActive": col.IsActive,
"sortOrder": col.SortOrder,
"createdAt": col.CreatedAt,
"updatedAt": col.UpdatedAt,
"deletedAt": col.DeletedAt,
"postCount": postCount,
"lastUpdated": lastUpdated,
}
}
// CreateColumn 创建专栏
func CreateColumn(col *models.Column) error {
err := config.DB.Create(col).Error

View File

@@ -18,10 +18,11 @@ type TrendData struct {
}
// GetPosts 获取所有博客文章(支持搜索、分类、标签筛选)
func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error) {
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)
@@ -34,6 +35,10 @@ func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error
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 ?)",
@@ -52,8 +57,11 @@ func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error
// 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
@@ -93,6 +101,8 @@ func GetAllPosts(page, pageSize int) ([]models.Post, int64, error) {
// Get posts
err = config.DB.Model(&models.Post{}).
Preload("Category").
Preload("Column").
Preload("Tags").
Where("deleted_at = ?", 0).
Order("created_at DESC").
Limit(pageSize).
@@ -124,21 +134,41 @@ func CreatePost(post *models.Post) error {
}
}
// 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(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(),
}).Error
Updates(updateData).Error
if err != nil {
log.Printf("Error updating post: %v", err)
@@ -161,6 +191,24 @@ func UpdatePost(post *models.Post) error {
}
}
// 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
}
@@ -212,19 +260,34 @@ func BuildPostResponse(post *models.Post, includeContent bool) *models.PostRespo
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 {
response.Content = post.Content
// 使用指针类型,确保即使内容为空字符串也会出现在 JSON 中
content := post.Content
response.Content = &content
}
return response

View File

@@ -0,0 +1,63 @@
package repositories
import (
"log"
"time"
"github.com/niangaodev/art-code/config"
"github.com/niangaodev/art-code/models"
)
// CreateSearchLog 创建搜索记录
func CreateSearchLog(logEntry *models.SearchLog) error {
err := config.DB.Create(logEntry).Error
if err != nil {
log.Printf("Error creating search log: %v", err)
return err
}
return nil
}
// GetSearchLogs 获取搜索记录列表(分页)
func GetSearchLogs(page, pageSize int) ([]models.SearchLog, int64, error) {
offset := (page - 1) * pageSize
var logs []models.SearchLog
var total int64
// Count total
err := config.DB.Model(&models.SearchLog{}).
Where("deleted_at = ?", 0).
Count(&total).Error
if err != nil {
log.Printf("Error counting search logs: %v", err)
return nil, 0, err
}
// Get logs
err = config.DB.Model(&models.SearchLog{}).
Where("deleted_at = ?", 0).
Order("created_at DESC").
Limit(pageSize).
Offset(offset).
Find(&logs).Error
if err != nil {
log.Printf("Error querying search logs: %v", err)
return nil, 0, err
}
return logs, total, nil
}
// DeleteSearchLog 删除搜索记录(软删除)
func DeleteSearchLog(id uint) error {
err := config.DB.Model(&models.SearchLog{}).
Where("id = ?", id).
Update("deleted_at", time.Now().Unix()).Error
if err != nil {
log.Printf("Error deleting search log: %v", err)
return err
}
return nil
}