数据结构优化
This commit is contained in:
@@ -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的协同过滤)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 权限)
|
||||
|
||||
@@ -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{}).
|
||||
|
||||
Reference in New Issue
Block a user