package handlers import ( "net/http" "strconv" "strings" "time" "goblog/database" "goblog/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // 文章列表请求参数 type PostListQuery struct { Page int `form:"page,default=1"` PageSize int `form:"page_size,default=10"` CategoryID uint `form:"category_id"` TagID uint `form:"tag_id"` Status string `form:"status"` Keyword string `form:"keyword"` } // 创建文章请求 type CreatePostRequest struct { Title string `json:"title" binding:"required"` Content string `json:"content" binding:"required"` Summary string `json:"summary"` Cover string `json:"cover"` CategoryID uint `json:"category_id" binding:"required"` Tags []string `json:"tags"` Status string `json:"status"` IsTop bool `json:"is_top"` } // 更新文章请求 type UpdatePostRequest struct { Title string `json:"title"` Content string `json:"content"` Summary string `json:"summary"` Cover string `json:"cover"` CategoryID uint `json:"category_id"` Tags []string `json:"tags"` Status string `json:"status"` IsTop bool `json:"is_top"` } // 生成 slug func generateSlug(title string) string { slug := strings.ToLower(title) slug = strings.ReplaceAll(slug, " ", "-") slug = strings.ReplaceAll(slug, "_", "-") // 简化处理,实际项目中可能需要更完善的 slug 生成 return slug } // 获取文章列表 func GetPosts(c *gin.Context) { var query PostListQuery if err := c.ShouldBindQuery(&query); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } db := database.DB.Model(&models.Post{}).Preload("Category").Preload("Tags").Preload("Author") // 前端只显示已发布的文章 if !isAdmin(c) { db = db.Where("status = ?", "published") } else if query.Status != "" { db = db.Where("status = ?", query.Status) } if query.CategoryID > 0 { db = db.Where("category_id = ?", query.CategoryID) } if query.TagID > 0 { db = db.Joins("JOIN post_tags ON post_tags.post_id = posts.id"). Where("post_tags.tag_id = ?", query.TagID) } if query.Keyword != "" { db = db.Where("title LIKE ? OR content LIKE ?", "%"+query.Keyword+"%", "%"+query.Keyword+"%") } var total int64 db.Count(&total) var posts []models.Post offset := (query.Page - 1) * query.PageSize db.Order("is_top DESC, published_at DESC, created_at DESC"). Offset(offset).Limit(query.PageSize).Find(&posts) c.JSON(http.StatusOK, gin.H{ "data": posts, "total": total, "page": query.Page, "size": query.PageSize, }) } // 获取单篇文章 func GetPost(c *gin.Context) { id := c.Param("id") var post models.Post query := database.DB.Preload("Category").Preload("Tags").Preload("Author").Preload("Comments", func(db *gorm.DB) *gorm.DB { return db.Where("status = ? AND parent_id IS NULL", "approved").Preload("Children", func(db *gorm.DB) *gorm.DB { return db.Where("status = ?", "approved") }) }) // 尝试按 ID 或 slug 查找 if _, err := strconv.Atoi(id); err == nil { query = query.Where("id = ?", id) } else { query = query.Where("slug = ?", id) } // 非管理员只能查看已发布文章 if !isAdmin(c) { query = query.Where("status = ?", "published") } if err := query.First(&post).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "文章不存在"}) return } // 增加浏览量 database.DB.Model(&post).UpdateColumn("views", gorm.Expr("views + 1")) c.JSON(http.StatusOK, gin.H{"data": post}) } // 创建文章 func CreatePost(c *gin.Context) { var req CreatePostRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } userID, _ := c.Get("userID") post := models.Post{ Title: req.Title, Slug: generateSlug(req.Title), Content: req.Content, Summary: req.Summary, Cover: req.Cover, AuthorID: userID.(uint), CategoryID: req.CategoryID, Status: req.Status, IsTop: req.IsTop, } if req.Status == "published" { now := time.Now() post.PublishedAt = &now } // 处理标签 if len(req.Tags) > 0 { var tags []models.Tag for _, tagName := range req.Tags { var tag models.Tag database.DB.FirstOrCreate(&tag, models.Tag{ Name: tagName, Slug: generateSlug(tagName), }) tags = append(tags, tag) } post.Tags = tags } if err := database.DB.Create(&post).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "创建文章失败"}) return } c.JSON(http.StatusCreated, gin.H{"data": post}) } // 更新文章 func UpdatePost(c *gin.Context) { id := c.Param("id") var post models.Post if err := database.DB.First(&post, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "文章不存在"}) return } var req UpdatePostRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updates := map[string]interface{}{} if req.Title != "" { updates["title"] = req.Title updates["slug"] = generateSlug(req.Title) } if req.Content != "" { updates["content"] = req.Content } if req.Summary != "" { updates["summary"] = req.Summary } if req.Cover != "" { updates["cover"] = req.Cover } if req.CategoryID > 0 { updates["category_id"] = req.CategoryID } if req.Status != "" { updates["status"] = req.Status if req.Status == "published" && post.Status != "published" { now := time.Now() updates["published_at"] = &now } } updates["is_top"] = req.IsTop // 处理标签 if len(req.Tags) > 0 { var tags []models.Tag for _, tagName := range req.Tags { var tag models.Tag database.DB.FirstOrCreate(&tag, models.Tag{ Name: tagName, Slug: generateSlug(tagName), }) tags = append(tags, tag) } database.DB.Model(&post).Association("Tags").Replace(tags) } if err := database.DB.Model(&post).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "更新文章失败"}) return } c.JSON(http.StatusOK, gin.H{"data": post}) } // 删除文章 func DeletePost(c *gin.Context) { id := c.Param("id") var post models.Post if err := database.DB.First(&post, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "文章不存在"}) return } if err := database.DB.Delete(&post).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "删除文章失败"}) return } c.JSON(http.StatusOK, gin.H{"message": "删除成功"}) } // 判断是否为管理员 func isAdmin(c *gin.Context) bool { role, exists := c.Get("role") return exists && role == "admin" }