- 链接格式 slug → /posts/{id}
- 状态重定义: draft/pending/approved/rejected/locked
- 新增 rejected 退回状态(可重提)+ locked 锁定状态(禁止编辑)
- 支持退回+锁定(严重违规/侵权场景)
- 新增解除锁定、提交审核等 API
291 lines
9.0 KiB
Markdown
291 lines
9.0 KiB
Markdown
# 内容系统设计文档
|
||
|
||
> **状态**: 计划中 | **优先级**: MVP
|
||
|
||
---
|
||
|
||
## 1. 内容模型
|
||
|
||
### Post(帖子/文章)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | uint (PK) | 自增主键 |
|
||
| `title` | varchar(200) | 标题,非空 |
|
||
| `body` | text | Markdown 原文 |
|
||
| `body_html` | text | 渲染后 HTML(写时缓存) |
|
||
| `user_id` | uint (FK → users) | 作者 |
|
||
| `status` | varchar(20) | `draft` / `pending` / `approved` / `rejected` / `locked` |
|
||
| `reject_reason` | varchar(500) | 退回理由(管理员填写) |
|
||
| `allow_comment` | bool | 是否允许评论(MVP 预留) |
|
||
| `deleted_at` | gorm.DeletedAt | 软删除 |
|
||
| `created_at` / `updated_at` | timestamp | |
|
||
|
||
**链接格式**: `/posts/{id}`(如 `/posts/42`)
|
||
|
||
**状态说明**:
|
||
|
||
| 状态 | 含义 | 公开可见 | 可编辑 |
|
||
|------|------|---------|--------|
|
||
| `draft` | 草稿,未被审核或审核已关闭 | 否(作者本人可见) | 是 |
|
||
| `pending` | 待审,已提交等待管理员操作 | 否 | 否 |
|
||
| `approved` | 审核通过,公开发布 | 是 | 是 |
|
||
| `rejected` | 退回,审核未通过,可修改后重提 | 否 | 是 |
|
||
| `locked` | 锁定,禁止编辑只能删除 | 否 | 否 |
|
||
|
||
**状态流转**:
|
||
|
||
```
|
||
创建 → draft ──(提交/审核开启)──→ pending
|
||
├──(通过)──→ approved ──(撤回)──→ rejected
|
||
│ │
|
||
├──(退回)──→ rejected │
|
||
│ │ │
|
||
└──(退回+锁定)──→ locked ←──────┘
|
||
←(任意状态锁定)
|
||
|
||
rejected ──(修改后重提)──→ pending
|
||
locked ──(解锁)──→ 回到锁定前状态
|
||
|
||
任何状态均可软删除(→ deleted_at)
|
||
```
|
||
|
||
**锁定场景**:
|
||
- **审核退回+锁定**: 严重违规/侵权内容,无修改空间,禁止再次提交
|
||
- **审核通过后退回+锁定**: 已发布内容被撤回并禁止再次编辑/提交
|
||
- **直接锁定**: 任何时候管理员可锁定任意帖子
|
||
|
||
---
|
||
|
||
## 2. 分层结构
|
||
|
||
对齐现有四层架构:
|
||
|
||
```
|
||
internal/
|
||
├── model/post.go # Post + 状态常量
|
||
├── repository/post_repo.go # DB CRUD
|
||
├── service/post_service.go # 业务逻辑 (依赖 postRepository 接口)
|
||
├── controller/
|
||
│ ├── post_controller.go # SSR 页面 + API
|
||
│ └── interfaces.go # 新增 postUseCase 接口
|
||
├── router/ # 注册路由
|
||
```
|
||
|
||
**依赖方向**: router → controller → service → repository → model
|
||
**接口定义**: service 层定义 repository 接口,controller 层定义 service 接口(ISP)
|
||
**控制器位置**: 放在 controller 根包,与 auth/settings 并列
|
||
|
||
---
|
||
|
||
## 3. 接口定义
|
||
|
||
### Repository 接口(service/repository.go 定义)
|
||
|
||
```go
|
||
type postRepository interface {
|
||
Create(post *model.Post) error
|
||
FindByID(id uint) (*model.Post, error)
|
||
FindPageable(keyword string, status string, page, pageSize int) ([]model.Post, int64, error)
|
||
Update(post *model.Post) error
|
||
SoftDelete(id uint) error
|
||
}
|
||
```
|
||
|
||
### Service 接口(controller/interfaces.go 定义)
|
||
|
||
```go
|
||
type postUseCase interface {
|
||
Create(userID uint, title, body string) (*model.Post, error)
|
||
GetByID(id uint) (*model.Post, error)
|
||
List(keyword string, page, pageSize int) ([]model.Post, int64, error)
|
||
Update(userID uint, postID uint, title, body string) error
|
||
Delete(userID uint, postID uint) error
|
||
SubmitForAudit(postID uint) error // 草稿 → 待审
|
||
Approve(postID uint) error // 审核通过
|
||
Reject(postID uint, reason string) error // 退回
|
||
Lock(postID uint) error // 锁定(禁止编辑)
|
||
Unlock(postID uint) error // 解锁
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Service 业务逻辑
|
||
|
||
### 发帖流程
|
||
|
||
```
|
||
用户提交 →
|
||
1. 鉴权(中间件 authMdw.Required)
|
||
2. 校验标题/正文非空
|
||
3. body → body_html(Goldmark 渲染)
|
||
4. 检查审核开关:
|
||
- 审核关闭 → status = approved,直接可见
|
||
- 审核开启 → status = pending,创建 AuditSubmission
|
||
5. Save → 返回
|
||
```
|
||
|
||
### 列表
|
||
|
||
- 仅返回 `status = approved`
|
||
- keyword 标题模糊搜索
|
||
- 分页默认 page_size=20
|
||
- 排序: created_at DESC
|
||
- 每条附带作者用户名
|
||
|
||
### 详情
|
||
|
||
- 通过 ID 查找
|
||
- 公开: 仅 `approved`
|
||
- 作者本人 + admin+: 可预览 `draft` / `pending` / `rejected` / `locked`
|
||
- 已渲染的 body_html 直接输出(带 XSS 防护)
|
||
|
||
### 编辑 / 删除
|
||
|
||
- 权限: 作者本人 或 admin+
|
||
- 状态约束: `pending` / `locked` 禁止编辑
|
||
- `rejected` 状态编辑后自动重置为 `draft`
|
||
- 删除: 软删除,任何状态均可
|
||
|
||
---
|
||
|
||
## 5. 路由设计
|
||
|
||
### SSR 页面(frontend)
|
||
|
||
```
|
||
GET /posts 列表页
|
||
GET /posts/:id 详情页
|
||
GET /posts/new 编辑器(需登录)
|
||
GET /posts/:id/edit 编辑页(作者/admin,locked 状态禁止)
|
||
```
|
||
|
||
### API(api)
|
||
|
||
```
|
||
POST /api/posts 发帖
|
||
PUT /api/posts/:id 编辑
|
||
DELETE /api/posts/:id 删除
|
||
POST /api/posts/:id/submit 草稿提交审核
|
||
GET /api/posts 列表 JSON
|
||
GET /api/posts/:id 详情 JSON
|
||
POST /api/posts/preview Markdown 预览
|
||
```
|
||
|
||
### 管理后台(admin)
|
||
|
||
```
|
||
GET /admin/posts 所有帖子(全状态)
|
||
POST /admin/posts/:id/approve 审核通过
|
||
POST /admin/posts/:id/reject 退回(需填写理由)
|
||
POST /admin/posts/:id/lock 锁定(禁止编辑)
|
||
POST /admin/posts/:id/unlock 解锁
|
||
POST /admin/posts/:id/restore 恢复软删除
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 模板设计
|
||
|
||
```
|
||
templates/MetaLab-2026/html/posts/
|
||
├── index.html 列表(卡片式: 标题+作者+时间+摘要)
|
||
├── show.html 详情(Markdown 渲染 → 主题 CSS)
|
||
├── new.html 编辑器(EasyMDE + 实时预览)
|
||
└── edit.html 复用 new.html,注入现有内容
|
||
|
||
templates/admin/html/posts/
|
||
└── index.html 管理列表(全量状态 + 操作按钮)
|
||
```
|
||
|
||
- **列表摘要**: body 前 200 字符纯文本剥离
|
||
- **详情样式**: 代码高亮、响应式图片、表格自适应
|
||
- **编辑器**: EasyMDE + marked.js + DOMPurify
|
||
|
||
---
|
||
|
||
## 7. Markdown 安全策略
|
||
|
||
| 层级 | 措施 |
|
||
|------|------|
|
||
| 服务端渲染 | Goldmark (Go) + html.EscapeString |
|
||
| 客户端预览 | marked.js + DOMPurify |
|
||
| 外部链接 | `rel="nofollow noopener noreferrer"` |
|
||
| 图片 | 限制域名白名单 + CSS max-width: 100% |
|
||
|
||
---
|
||
|
||
## 8. 审核集成
|
||
|
||
复用现有 `AuditSubmission` 表,新增 `type = "post"`:
|
||
|
||
| 用户角色 | 发帖后状态 | 如何可见 |
|
||
|----------|-----------|---------|
|
||
| user(审核开启) | `pending` → AuditSubmission | 审核通过 → approved |
|
||
| moderator+ | `approved` | 直接可见 |
|
||
| user(审核关闭) | `approved` | 直接可见 |
|
||
|
||
**审核操作对应状态变更**:
|
||
|
||
| 操作 | 状态变更 | 条件 |
|
||
|------|---------|------|
|
||
| 提交审核 | `draft` → `pending` | 仅作者本人 |
|
||
| 审核通过 | `pending` → `approved` | admin+ |
|
||
| 退回 | `pending`/`approved` → `rejected` | admin+,需填写理由 |
|
||
| 退回+锁定 | `pending`/`approved` → `locked` | admin+,严重违规/侵权 |
|
||
|
||
---
|
||
|
||
## 9. 管理后台扩展
|
||
|
||
侧边栏新增 "内容管理" 入口(admin+):
|
||
|
||
- 全状态列表: 标题 / 作者 / 状态 / 时间 / 退回理由
|
||
- 操作: 通过 / 退回 / 锁定 / 解锁 / 恢复
|
||
- 状态标签: draft(灰)/ pending(黄)/ approved(绿)/ rejected(橙)/ locked(红)
|
||
|
||
---
|
||
|
||
## 10. MVP vs 后续
|
||
|
||
| 维度 | MVP | 后续 |
|
||
|------|-----|------|
|
||
| 内容 | 帖子 CRUD | 评论系统 |
|
||
| 编辑器 | EasyMDE + 实时预览 | 图片拖拽上传 |
|
||
| 列表 | 卡片 + 分页 | 分类 / 标签 / 置顶 |
|
||
| 审核 | 复用 Audit 表 | 编辑也走审核 |
|
||
| 管理 | 列表 + 基本操作 | 批量操作 / 搜索 |
|
||
| 通知 | — | 审核通过 / 新评论通知 |
|
||
|
||
---
|
||
|
||
## 11. 预计新增/修改文件
|
||
|
||
**新增(~12)**:
|
||
```
|
||
internal/model/post.go
|
||
internal/repository/post_repo.go
|
||
internal/service/post_service.go
|
||
internal/controller/post_controller.go
|
||
templates/MetaLab-2026/html/posts/index.html
|
||
templates/MetaLab-2026/html/posts/show.html
|
||
templates/MetaLab-2026/html/posts/new.html
|
||
templates/admin/html/posts/index.html
|
||
templates/MetaLab-2026/static/css/posts.css
|
||
templates/admin/static/css/posts.css
|
||
templates/MetaLab-2026/static/js/editor.js
|
||
templates/admin/static/js/posts.js
|
||
```
|
||
|
||
**修改(~6)**:
|
||
```
|
||
internal/controller/interfaces.go + postUseCase
|
||
internal/service/repository.go + postRepository (接口)
|
||
internal/router/frontend.go + post SSR 路由
|
||
internal/router/api.go + post API 路由
|
||
internal/router/admin.go + admin post 路由
|
||
templates/admin/html/layout/base.html + 侧边栏
|
||
cmd/server/main.go + AutoMigrate Post
|
||
```
|