feat: 实现内容系统 MVC(Post CRUD + 审核流转)
按 content-system.md 设计文档,实现帖子内容系统的最小可行 MVC。 新增: - Model: Post 模型,含 5 种状态(draft/pending/approved/rejected/locked) - Repository: post_repo.go,分页查询(前台 approved 列表 + 后台全状态筛选) - Service: post_service.go,核心业务逻辑(状态流转、Markdown 渲染 Goldmark) - Controller: post_controller + admin_post_controller,SSR 页面 6 个 + API 13 个 - Template: 列表/详情/编辑器/404 + 管理员列表,各配 CSS/JS - 路由: /posts, /posts/:id, /posts/new, /posts/:id/edit, REST API, Admin API - gomod: + github.com/yuin/goldmark v1.8.2 修改: - Main: AutoMigrate Post 表 - Router: deps 注册 PostService/Controller,路由注册 - Nav: 新增“帖子”导航链接 - Admin Sidebar: 新增“内容管理”入口 设计: - 审核开关复用 audit.enabled,开启时帖子为 draft,关闭后直接 approved - 仅 approved 公开可见,作者/admin 可预览非公开状态帖子 - 严格分层 ISP 接口: Controller → postUseCase, Service → postStore, Repo → *PostRepo - 状态保护: pending/locked 禁止编辑,rejected 编辑后自动重置为 draft
This commit is contained in:
149
internal/controller/admin/admin_post_controller.go
Normal file
149
internal/controller/admin/admin_post_controller.go
Normal file
@ -0,0 +1,149 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"metazone.cc/metalab/internal/common"
|
||||
"metazone.cc/metalab/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminPostController 管理后台帖子控制器
|
||||
type AdminPostController struct {
|
||||
postService adminPostUseCase
|
||||
}
|
||||
|
||||
// NewAdminPostController 构造函数
|
||||
func NewAdminPostController(ps adminPostUseCase) *AdminPostController {
|
||||
return &AdminPostController{postService: ps}
|
||||
}
|
||||
|
||||
// PostsPage 管理后台帖子列表页
|
||||
func (ctrl *AdminPostController) PostsPage(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
status := c.Query("status")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
posts, total, err := ctrl.postService.ListAdmin(keyword, status, page, pageSize)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusOK, "admin/posts/index.html", common.BuildAdminPageData(c, gin.H{
|
||||
"Title": "内容管理",
|
||||
"Error": "加载帖子列表失败",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if posts == nil {
|
||||
posts = []model.Post{}
|
||||
}
|
||||
|
||||
p := common.Pagination{Page: page, PageSize: pageSize}
|
||||
p.DefaultPagination()
|
||||
totalPages := int((total + int64(p.PageSize) - 1) / int64(p.PageSize))
|
||||
prevPage := p.Page - 1
|
||||
if prevPage < 1 { prevPage = 1 }
|
||||
nextPage := p.Page + 1
|
||||
if nextPage > totalPages { nextPage = totalPages }
|
||||
|
||||
c.HTML(http.StatusOK, "admin/posts/index.html", common.BuildAdminPageData(c, gin.H{
|
||||
"Title": "内容管理",
|
||||
"Posts": posts,
|
||||
"Total": total,
|
||||
"Page": p.Page,
|
||||
"TotalPages": totalPages,
|
||||
"PrevPage": prevPage,
|
||||
"NextPage": nextPage,
|
||||
"Keyword": keyword,
|
||||
"Status": status,
|
||||
"ExtraCSS": "/admin/static/css/posts.css",
|
||||
}))
|
||||
}
|
||||
|
||||
// Approve 审核通过
|
||||
func (ctrl *AdminPostController) Approve(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctrl.postService.Approve(uint(id)); err != nil {
|
||||
common.Error(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.OkMessage(c, "审核通过")
|
||||
}
|
||||
|
||||
// Reject 退回
|
||||
func (ctrl *AdminPostController) Reject(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req model.PostRejectRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "请填写退回理由")
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctrl.postService.Reject(uint(id), req.Reason); err != nil {
|
||||
common.Error(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.OkMessage(c, "已退回")
|
||||
}
|
||||
|
||||
// Lock 锁定
|
||||
func (ctrl *AdminPostController) Lock(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctrl.postService.Lock(uint(id)); err != nil {
|
||||
common.Error(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.OkMessage(c, "已锁定")
|
||||
}
|
||||
|
||||
// Unlock 解锁
|
||||
func (ctrl *AdminPostController) Unlock(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctrl.postService.Unlock(uint(id)); err != nil {
|
||||
common.Error(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.OkMessage(c, "已解锁")
|
||||
}
|
||||
|
||||
// Restore 恢复软删除
|
||||
func (ctrl *AdminPostController) Restore(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctrl.postService.Restore(uint(id)); err != nil {
|
||||
common.Error(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.OkMessage(c, "已恢复")
|
||||
}
|
||||
Reference in New Issue
Block a user