qoder生成项目

This commit is contained in:
2026-02-13 10:53:54 +08:00
commit 4cab051f9c
24 changed files with 3627 additions and 0 deletions

274
handlers/post.go Normal file
View File

@@ -0,0 +1,274 @@
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"
}