数据结构优化
This commit is contained in:
249
server/repositories/attachment_repository.go
Normal file
249
server/repositories/attachment_repository.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
63
server/repositories/search_log_repository.go
Normal file
63
server/repositories/search_log_repository.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user