7.0 KiB
7.0 KiB
Personal Blog - Markdown + HTML 双格式文章改造计划
目标:支持 Markdown 和 HTML 两种格式录入文章,访客端正确渲染,图片路径兼容现有规则。
1. 整体策略
┌─────────────────────────────────────────────────────────────┐
│ 后台编辑器 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ @uiw/react- │ │ react-quill │ │
│ │ md-editor │ │ -new (HTML) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ content_type='markdown' content_type='html' │
│ │ │ │
│ └────────┬────────┘ │
│ ▼ │
│ 后端 API (posts.py) │
│ 保存到数据库 (content_type 字段) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 访客端渲染 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ content_type │ │ content_type │ │
│ │ == 'markdown' │ │ == 'html' │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ renderMarkdown() DOMPurify.sanitize() │
│ (现有轻量渲染) (XSS 过滤后渲染) │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ dangerouslySetInnerHTML │
└─────────────────────────────────────────────────────────────┘
2. 改造步骤清单
| 步骤 | 模块 | 状态 | 说明 |
|---|---|---|---|
| 2.1 | 后端 models.py | ✅ 已完成 | Post 表新增 content_type 字段,默认 'markdown' |
| 2.2 | 后端 schemas.py | ✅ 已完成 | PostCreate, PostUpdate, PostOut 均添加 content_type |
| 2.3 | 后端 posts.py | ✅ 已完成 | 创建/更新接口读写 content_type,列表/详情接口返回 |
| 2.4 | 前端依赖 | ✅ 已完成 | 安装 @uiw/react-md-editor、react-quill-new、dompurify |
| 2.5 | 前端 PostEdit.jsx | ✅ 已完成 | 重写编辑器,支持 Markdown/HTML 切换,图片上传兼容现有规则 |
| 2.6 | 前端 PostDetail.jsx | ✅ 已完成 | 根据 content_type 选择渲染方式,HTML 需 DOMPurify 过滤 |
| 2.7 | 前端 Posts.jsx | ✅ 已完成 | 文章列表显示 content_type 标签 |
| 2.8 | 数据库兼容 | ✅ 已完成 | SQLite 添加字段,现有 111 篇文章默认设为 markdown |
| 2.9 | 构建验证 | ✅ 已完成 | npm run build 通过(7.21s),有 CSS 警告但非阻塞 |
3. 后端变更详情
3.1 models.py
class Post(Base):
# ... 现有字段 ...
content_type = Column(String(10), default='markdown') # 'markdown' | 'html'
3.2 schemas.py
class PostCreate(BaseModel):
# ...
content_type: str = Field(default='markdown', pattern='^(markdown|html)$')
class PostUpdate(BaseModel):
# ...
content_type: Optional[str] = None
class PostOut(PostBase):
# ...
content_type: str
3.3 posts.py
create_post: 自动写入content_typeupdate_post: 自动透传content_type- 列表/详情接口返回
content_type
4. 前端变更详情
4.1 PostEdit.jsx(已完成)
- 引入
MDEditor(Markdown)和ReactQuill(HTML) - 增加格式切换按钮(Markdown / 富文本 HTML)
- 图片上传复用现有
/api/upload接口 - Markdown 模式插入
 - HTML 模式插入
<img src="/uploads/xxx.jpg" /> - 缩略图路径格式:
/uploads/thumbnails/xxx.jpg
4.2 PostDetail.jsx(待完成)
- 引入
DOMPurify - 判断
post.content_type:'markdown'→ 调用现有renderMarkdown()'html'→DOMPurify.sanitize(post.content, { FORBID_TAGS: ['script', 'iframe'] })
4.3 Posts.jsx(待完成)
- 在文章列表项中增加
content_type标签显示
5. 图片路径规则
| 项目 | 路径格式 | 说明 |
|---|---|---|
| 原图 | /uploads/{filename} |
上传接口返回的 file_path |
| 缩略图 | /uploads/thumbnails/{filename} |
上传接口返回的 thumb_path |
| 完整 URL | {API_BASE_URL.replace('/api', '')}{path} |
前端拼接 |
编辑器插入的图片路径使用相对路径 /uploads/xxx.jpg,访客端渲染时自动补全为完整 URL。
6. XSS 防护策略
- 输入侧:Quill 编辑器本身会过滤部分危险标签;后端不二次过滤 HTML 内容(保留用户原始输入)
- 输出侧:访客端使用
DOMPurify.sanitize()过滤,禁止:<script>、<iframe>、<object>、<embed>- 事件处理器属性:
onerror、onload、onclick等
7. 数据库迁移记录
-- 2026-06-12 执行
ALTER TABLE posts ADD COLUMN content_type VARCHAR(10) DEFAULT 'markdown';
UPDATE posts SET content_type = 'markdown' WHERE content_type IS NULL;
影响:111 篇现有文章全部标记为 markdown。
8. 测试清单
- 新建 Markdown 文章,保存成功,访客端正确渲染
- 新建 HTML 文章,保存成功,访客端正确渲染
- 编辑现有 Markdown 文章,content_type 保持为 markdown
- HTML 文章中插入
<script>,访客端被 DOMPurify 过滤 - 图片上传路径符合
/uploads/xxx.jpg规则 npm run build无报错