数据结构优化

This commit is contained in:
李琦
2026-01-23 16:05:23 +08:00
parent 889247b33a
commit 9d39b7c22b
11 changed files with 881 additions and 87 deletions

View File

@@ -40,8 +40,30 @@ func GetPosts(c *gin.Context) {
}
}
// 从数据库获取所有博客文章
posts, err := repositories.GetPosts(keyword, categoryID, tagID, columnID)
// 获取分页参数
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
if page < 1 {
page = 1
}
// 从数据库读取 posts_per_page 配置作为默认值
pageSize := 10 // 默认值
setting, err := repositories.GetSettingByKey("posts_per_page")
if err == nil && setting != nil {
if ps, err := strconv.Atoi(setting.Value); err == nil && ps > 0 {
pageSize = ps
}
}
// 如果请求中指定了 pageSize则使用请求的值
if pageSizeStr := c.Query("pageSize"); pageSizeStr != "" {
if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
pageSize = ps
}
}
// 从数据库获取博客文章(支持分页)
posts, total, err := repositories.GetPosts(keyword, categoryID, tagID, columnID, page, pageSize)
if err != nil {
utils.ServerError(c, err)
return
@@ -64,15 +86,21 @@ func GetPosts(c *gin.Context) {
LogSearch(strconv.FormatUint(uint64(columnID), 10), "column", userIP, userLocation)
}
// 构建响应
// 构建响应(返回分页格式)
responses := repositories.BuildPostsResponse(posts)
// Ensure not nil
if responses == nil {
// We need to return []
utils.Success(c, []interface{}{})
} else {
utils.Success(c, responses)
responses = []models.PostResponse{}
}
// 返回分页格式的响应
res := gin.H{
"list": responses,
"total": total,
"page": page,
"size": pageSize,
}
utils.Success(c, res)
}
func GetPost(c *gin.Context) {
@@ -169,9 +197,22 @@ func GetPost(c *gin.Context) {
// 获取所有文章(包括未发布的,后台用)
func AdminGetPosts(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "1000")) // Default to 1000 to mimic "all" for now
if page < 1 {
page = 1
}
posts, total, err := repositories.GetAllPosts(page, pageSize)
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) // 默认20条
if pageSize < 1 {
pageSize = 20
}
// 获取搜索关键词
keyword := c.Query("keyword")
if keyword == "" {
keyword = c.Query("q") // 兼容 q 参数
}
posts, total, err := repositories.GetAllPosts(page, pageSize, keyword)
if err != nil {
utils.ServerError(c, err)
return
@@ -247,6 +288,34 @@ func AdminUpdatePost(c *gin.Context) {
utils.SuccessWithMsg(c, "Post updated successfully", nil)
}
// 更新文章关联关系(只更新分类、专栏、标签,不更新内容)
func AdminUpdatePostRelations(c *gin.Context) {
postIDStr := c.Param("id")
var postID uint
if _, err := fmt.Sscanf(postIDStr, "%d", &postID); err != nil {
utils.Error(c, 400, "Invalid post ID")
return
}
var req struct {
CategoryID uint `json:"categoryId"`
ColumnID *uint `json:"columnId"`
TagIDs []uint `json:"tagIds"`
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.Error(c, 400, "Invalid request")
return
}
// 更新关联关系
if err := repositories.UpdatePostRelations(postID, req.CategoryID, req.ColumnID, req.TagIDs); err != nil {
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "Post relations updated successfully", nil)
}
// 切换文章发布状态
func AdminTogglePostStatus(c *gin.Context) {
postIDStr := c.Param("id")
@@ -355,18 +424,49 @@ func GetPostsByTagID(c *gin.Context) {
return
}
posts, err := repositories.GetPosts("", 0, tagID, 0)
// 获取分页参数
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
if page < 1 {
page = 1
}
// 从数据库读取 posts_per_page 配置作为默认值
pageSize := 10 // 默认值
setting, err := repositories.GetSettingByKey("posts_per_page")
if err == nil && setting != nil {
if ps, err := strconv.Atoi(setting.Value); err == nil && ps > 0 {
pageSize = ps
}
}
// 如果请求中指定了 pageSize则使用请求的值
if pageSizeStr := c.Query("pageSize"); pageSizeStr != "" {
if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
pageSize = ps
}
}
posts, total, err := repositories.GetPosts("", 0, tagID, 0, page, pageSize)
if err != nil {
utils.ServerError(c, err)
return
}
res := repositories.BuildPostsResponse(posts)
if res == nil {
utils.Success(c, []interface{}{})
} else {
utils.Success(c, res)
// 构建响应(返回分页格式)
responses := repositories.BuildPostsResponse(posts)
if responses == nil {
responses = []models.PostResponse{}
}
// 返回分页格式的响应
res := gin.H{
"list": responses,
"total": total,
"page": page,
"size": pageSize,
}
utils.Success(c, res)
}
// GetRecommendedPosts 获取推荐文章基于IP的协同过滤

View File

@@ -20,8 +20,8 @@ func GetSettings(c *gin.Context) {
// 只返回前端需要的公开配置项
publicSettings := make(map[string]string)
publicKeys := []string{"site_title", "site_description", "site_author", "site_keywords", "visible_menus"}
publicKeys := []string{"site_title", "site_description", "site_author", "site_keywords", "visible_menus", "posts_per_page"}
for _, key := range publicKeys {
if value, exists := settingsMap[key]; exists {
publicSettings[key] = value

View File

@@ -148,7 +148,8 @@ func main() {
authAdmin.GET("/posts", middleware.PermissionMiddleware("posts", "read"), handlers.AdminGetPosts)
authAdmin.POST("/posts", middleware.PermissionMiddleware("posts", "create"), handlers.AdminCreatePost)
authAdmin.PUT("/posts/:id", middleware.PermissionMiddleware("posts", "update"), handlers.AdminUpdatePost)
authAdmin.PATCH("/posts/:id/status", middleware.PermissionMiddleware("posts", "update"), handlers.AdminTogglePostStatus) // 新增状态切换
authAdmin.PATCH("/posts/:id/relations", middleware.PermissionMiddleware("posts", "update"), handlers.AdminUpdatePostRelations) // 更新文章关联关系
authAdmin.PATCH("/posts/:id/status", middleware.PermissionMiddleware("posts", "update"), handlers.AdminTogglePostStatus) // 新增状态切换
authAdmin.DELETE("/posts/:id", middleware.PermissionMiddleware("posts", "delete"), handlers.AdminDeletePost)
// 分类管理 (复用 posts 权限)

View File

@@ -17,13 +17,13 @@ type TrendData struct {
MoM float64 `json:"mom"`
}
// GetPosts 获取所有博客文章(支持搜索、分类、标签筛选)
func GetPosts(keyword string, categoryID uint, tagID uint, columnID uint) ([]models.Post, error) {
// GetPosts 获取所有博客文章(支持搜索、分类、标签筛选,支持分页
func GetPosts(keyword string, categoryID uint, tagID uint, columnID uint, page int, pageSize int) ([]models.Post, int64, error) {
var posts []models.Post
var total int64
// 构建基础查询
query := config.DB.Model(&models.Post{}).
Preload("Category").
Preload("Column").
Preload("Tags").
Where("is_published = ? AND deleted_at = ?", 1, 0)
if tagID > 0 {
@@ -45,13 +45,30 @@ func GetPosts(keyword string, categoryID uint, tagID uint, columnID uint) ([]mod
keyword, likeKeyword, likeKeyword)
}
err := query.Order("created_at DESC").Find(&posts).Error
// 计算总数
countQuery := query
err := countQuery.Count(&total).Error
if err != nil {
log.Printf("Error querying posts: %v", err)
return nil, err
log.Printf("Error counting posts: %v", err)
return nil, 0, err
}
return posts, nil
// 应用分页
offset := (page - 1) * pageSize
err = query.
Preload("Category").
Preload("Column").
Preload("Tags").
Order("created_at DESC").
Limit(pageSize).
Offset(offset).
Find(&posts).Error
if err != nil {
log.Printf("Error querying posts: %v", err)
return nil, 0, err
}
return posts, total, nil
}
// GetPostByID 根据ID获取博客文章
@@ -82,28 +99,36 @@ func GetPostByID(id uint) (*models.Post, error) {
return &post, nil
}
// GetAllPosts 获取所有博客文章(包括未发布的,后台用)
func GetAllPosts(page, pageSize int) ([]models.Post, int64, error) {
// GetAllPosts 获取所有博客文章(包括未发布的,后台用,支持搜索
func GetAllPosts(page, pageSize int, keyword string) ([]models.Post, int64, error) {
offset := (page - 1) * pageSize
var posts []models.Post
var total int64
// 构建查询
query := config.DB.Model(&models.Post{}).
Where("deleted_at = ?", 0)
// 如果有关键词,添加搜索条件
if keyword != "" {
likeKeyword := "%" + keyword + "%"
query = query.Where("(MATCH(title, content) AGAINST(? IN BOOLEAN MODE) OR title LIKE ? OR content LIKE ?)",
keyword, likeKeyword, likeKeyword)
}
// Count total
err := config.DB.Model(&models.Post{}).
Where("deleted_at = ?", 0).
Count(&total).Error
err := query.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{}).
err = query.
Preload("Category").
Preload("Column").
Preload("Tags").
Where("deleted_at = ?", 0).
Order("created_at DESC").
Limit(pageSize).
Offset(offset).
@@ -149,11 +174,12 @@ func CreatePost(post *models.Post) error {
// UpdatePost 更新博客文章
func UpdatePost(post *models.Post) error {
// 使用 map 更新,并明确指定要更新的字段,确保即使字段是空字符串也会被更新
updateData := map[string]interface{}{
"title": post.Title,
"category_id": post.CategoryID,
"excerpt": post.Excerpt,
"content": post.Content,
"content": post.Content, // 明确包含 content即使为空字符串也会更新
"is_published": post.IsPublished,
"updated_at": time.Now().Unix(),
}
@@ -166,8 +192,10 @@ func UpdatePost(post *models.Post) error {
updateData["column_id"] = nil
}
// 使用 Select 明确指定要更新的字段,确保所有字段都被更新
err := config.DB.Model(&models.Post{}).
Where("id = ? AND deleted_at = ?", post.ID, 0).
Select("title", "category_id", "column_id", "excerpt", "content", "is_published", "updated_at").
Updates(updateData).Error
if err != nil {
@@ -212,6 +240,71 @@ func UpdatePost(post *models.Post) error {
return nil
}
// UpdatePostRelations 只更新文章的关联关系(分类、专栏、标签),不更新内容
func UpdatePostRelations(postID uint, categoryID uint, columnID *uint, tagIDs []uint) error {
// 更新分类和专栏
updateData := map[string]interface{}{
"category_id": categoryID,
"updated_at": time.Now().Unix(),
}
if columnID != nil {
updateData["column_id"] = columnID
} else {
updateData["column_id"] = nil
}
err := config.DB.Model(&models.Post{}).
Where("id = ? AND deleted_at = ?", postID, 0).
Updates(updateData).Error
if err != nil {
log.Printf("Error updating post relations: %v", err)
return err
}
// 更新标签关联
post := &models.Post{ID: postID}
if len(tagIDs) > 0 {
// 构建标签对象
tags := make([]models.Tag, 0, len(tagIDs))
for _, tagID := range tagIDs {
tags = append(tags, models.Tag{ID: tagID})
}
err = config.DB.Model(post).Association("Tags").Replace(tags)
if err != nil {
log.Printf("Error updating tags: %v", err)
return err
}
} else {
// 清除所有标签
err = config.DB.Model(post).Association("Tags").Clear()
if err != nil {
log.Printf("Error clearing tags: %v", err)
return err
}
}
// 处理专栏关联
// 先移除所有专栏关联
err = config.DB.Where("post_id = ?", postID).Delete(&models.ColumnPost{}).Error
if err != nil {
log.Printf("Error removing post from columns: %v", err)
// Don't fail the whole operation
}
// 然后添加新的专栏关联
if columnID != nil && *columnID > 0 {
err = AddPostToColumn(*columnID, postID, 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{}).