Files
go_blog/handlers/my_articles.go
T
kevin 5b227b71ef feat: 文章 CRUD 接口 JSON 化——/api/admin|my/articles
- article.go:articleForm 加 json tags,新增 parseArticleFormJSON(绑定+
  去白);ArticleCreate/ArticleUpdate/ArticleDelete 增加 redirectPath 参数,
  校验失败改 APIError 400、DB 错误 500,成功 {ok,redirect}(原 renderForm
  分支移除);id 改用 parseUintParam 数值化
- my_articles.go:MyArticleCreate 复用 ArticleCreate(db, /my/articles);
  MyArticleUpdate/MyArticleDelete 保留 author_id 所有权约束(404
  article_not_found);my 更新不触碰标签(表单无该字段,与旧行为一致)
- main.go:admin/my 文章旧 POST 路由移除,改 POST/PUT/DELETE
  /api/admin/articles[/:id] 与 /api/my/articles[/:id]
- 模板:article_create.html/my_article_form.html 表单改 blogAPI 提交
  (easyMDE.value() 同步正文、editor e.submitter 分流草稿/发布),错误内联
  articleError/myArticleError;article_list.html/my_articles.html 删除改
  blogDelete 委托(base.html 新公共函数,DELETE + reload 保筛选状态)
- main_test 冒烟补文章 CRUD 断言;go build/vet/test 全绿
2026-08-27 19:40:02 +08:00

186 lines
5.0 KiB
Go

package handlers
import (
"net/http"
"strconv"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"go_blog/models"
)
// MyArticlesPage 渲染已登录用户的文章管理页面。
func MyArticlesPage(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
tr := getTr(c)
session := sessions.Default(c)
userID := session.Get("user_id")
var articles []models.Article
db.Where("author_id = ?", userID).Order("created_at DESC").Find(&articles)
data := DefaultData(c)
data["Title"] = tr["my_articles_title"]
data["Articles"] = articles
c.HTML(http.StatusOK, "my_articles", data)
}
}
// MyArticleCreatePage 为普通用户渲染文章创建表单。
func MyArticleCreatePage(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
tr := getTr(c)
token := randomToken()
session := sessions.Default(c)
session.Set("pending_token", token)
session.Save()
renderMyArticleForm(c, db, articleForm{
Action: "/my/articles/new",
TitleText: tr["article_create_title"],
SessionToken: token,
}, "")
}
}
// MyArticleCreate 处理普通用户创建新文章的 POST 请求。
func MyArticleCreate(db *gorm.DB) gin.HandlerFunc {
return ArticleCreate(db, "/my/articles") // 复用创建逻辑,跳转到 my 列表
}
// MyArticleEditPage 为已登录用户自己的文章渲染编辑表单。
func MyArticleEditPage(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
tr := getTr(c)
id := c.Param("id")
session := sessions.Default(c)
userID := session.Get("user_id")
var article models.Article
if err := db.First(&article, "id = ? AND author_id = ?", id, userID).Error; err != nil {
c.Redirect(http.StatusFound, "/my/articles")
return
}
renderMyArticleForm(c, db, articleForm{
Title: article.Title,
Slug: article.Slug,
Summary: article.Summary,
Content: article.Content,
Cover: article.Cover,
StatusStr: strconv.Itoa(article.Status),
IsTop: article.IsTop,
PublishedAt: formatPublishedAt(article.PublishedAt),
Action: "/my/articles/" + id + "/edit",
TitleText: tr["article_edit_title"],
ArticleID: article.ID,
}, "")
}
}
// MyArticleUpdate 处理更新用户自己文章的请求(带 author_id 所有权约束)。
func MyArticleUpdate(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := parseUintParam(c, "id")
if id == 0 {
APIError(c, http.StatusBadRequest, "api_invalid_request")
return
}
session := sessions.Default(c)
userID := session.Get("user_id")
var article models.Article
if err := db.First(&article, "id = ? AND author_id = ?", id, userID).Error; err != nil {
APIError(c, http.StatusNotFound, "article_not_found")
return
}
f, ok := parseArticleFormJSON(c)
if !ok {
return
}
if f.Title == "" {
APIError(c, http.StatusBadRequest, "article_title_required")
return
}
if f.Content == "" {
APIError(c, http.StatusBadRequest, "article_content_required")
return
}
if f.Slug == "" {
f.Slug = generateSlug(f.Title)
if f.Slug == "" {
f.Slug = fallbackSlug(article.ID)
}
}
newStatus := statusFromForm(f.StatusStr)
// 处理 published_at:若提供了表单值则使用,否则在首次发布时自动盖章。
var publishedAt *time.Time
if f.PublishedAt != "" {
// 用户提供了自定义发布时间
publishedAt = parsePublishedAt(f.PublishedAt)
} else {
// 文章首次发布时盖上发布时间。
wasPublished := article.Status == models.ArticlePublished
publishedAt = article.PublishedAt
if newStatus == models.ArticlePublished && !wasPublished && publishedAt == nil {
now := time.Now()
publishedAt = &now
}
}
updates := map[string]interface{}{
"title": f.Title,
"slug": f.Slug,
"summary": f.Summary,
"content": f.Content,
"cover": f.Cover,
"status": newStatus,
"is_top": f.IsTop,
"published_at": publishedAt,
}
if err := db.Model(&article).Updates(updates).Error; err != nil {
APIError(c, http.StatusInternalServerError, "article_error")
return
}
// 注意:my 表单无 tags 字段,此处不触碰标签(与旧行为一致)。
APIOK(c, "/my/articles", nil)
}
}
// MyArticleDelete 软删除用户自己的文章。
func MyArticleDelete(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := parseUintParam(c, "id")
if id == 0 {
APIError(c, http.StatusBadRequest, "api_invalid_request")
return
}
session := sessions.Default(c)
userID := session.Get("user_id")
db.Where("id = ? AND author_id = ?", id, userID).Delete(&models.Article{})
APIOK(c, "/my/articles", nil)
}
}
// renderMyArticleForm 为普通用户渲染文章表单。
func renderMyArticleForm(c *gin.Context, db *gorm.DB, f articleForm, errMsg string) {
data := DefaultData(c)
data["Title"] = f.TitleText
if errMsg != "" {
data["Error"] = errMsg
}
applyFormToData(data, f)
c.HTML(http.StatusOK, "my_article_form", data)
}