纸上春秋 · 博客产品迭代文档
111
纸上春秋 · 博客产品迭代文档
版本:v2.5(2026-08-15) 形态:沉浸式叙事个人/多人写作博客 · Flask + JSON + 原生前端
1. 产品概述
1.1 定位
「纸上春秋」是一套纸墨质感 / 沉浸式叙事的写作博客。它不追求功能堆砌,而是围绕"把日子写成故事"这一核心,提供从写作 → 投稿 → 审核 → 发布 → 阅读的完整内容闭环,同时保持与东方美学一致的视觉体验。
1.2 目标用户
| 角色 | 诉求 |
|---|---|
| 读者 | 沉浸式阅读体验(纸墨排版、目录、进度条、天气信息条) |
| 作者 | 注册后自由投稿,被驳回可修改重投,全程站内信通知 |
| 管理员 | 审核投稿、直接发布、管理用户与文章、追溯历史版本 |
1.3 设计语言
宣纸米白 #f5f0e8 / 墨色 #2c2a28 / 朱红点缀 #c0392b;标题手写体(ZCOOL KuaiLe)、正文衬线(Klee One)、引用书法体(Ma Shan Zheng);所有字体与图标本地化托管,不依赖外网 CDN。
2. 版本迭代历史
v1.0 · 设计系统与组件原型(2026-08-13)
| 交付物 | 内容 |
|---|---|
story-blog.html |
首页沉浸式设计:宣纸纹理、打字机英雄区、朱红印章、时间线文章列表 |
book-flip.html |
CSS 3D 翻页书组件(rotateY 双面翻转、折痕光影) |
articles-grid.html |
非对称 Grid 文章列表(4 列 × 3 行手动定位) |
typography-system.html |
排版系统规范:标题晕染、毛笔竖线引用、页边墨点 |
验收:四个组件全部纯 HTML/CSS/JS 可独立运行,确立设计令牌与视觉语言。
v2.0 · 博客系统上线(2026-08-13)
- 后端:Flask 单文件应用 + JSON 文件存储(零数据库),Markdown 渲染 + bleach 白名单防 XSS
- 前台:沉浸式非对称网格首页、日记本风格文章页、自动目录(TOC)+ 滚动高亮、阅读进度条、明暗主题切换、滚动渐入动画
- 管理端:
/admin文章发布/编辑/删除,Markdown 实时预览编辑器 - 部署:Gunicorn + Nginx + 宝塔面板(Ubuntu 22.04 / 2C2G)
迭代要点:管理后台从"无登录"演进为 session 登录(ADMIN_PASSWORD 环境变量)。
v2.1 · 用户系统(2026-08-13)
- 注册
/register(用户名唯一、密码哈希存储、注册即登录)、登录/login(15 分钟失败 5 次锁定) - 投稿
/submit(Markdown + 实时预览,每日上限 5 篇,状态 pending) - 审核流:管理员通过(发布)/ 驳回(附理由),作者个人中心
/profile查看状态 - 用户管理
/admin/users:搜索/排序/分页、角色升降(最后一名管理员不可降级)、启用禁用、重置密码(临时密码展示一次)、删除(文章保留并标记"已注销用户")、操作审计日志data/audit.log
v2.2 · 站内通知(2026-08-13)
- 右上角铃铛 + 未读红点(18px 朱红圆形),30 秒轮询静默刷新
- 下拉面板:相对时间、未读标记(背景加深 + 朱红竖条)、全部已读、单条删除
- 审核结果实时通知作者;作者重投后通知管理员;通知上限 200 条(自动清理最旧已读)
- 迭代细节:类型图标(✅❌📌)按需求改为纯文字展示
v2.3 · 文章修改与版本管理(2026-08-14)
- 被驳回文章可由作者修改并重新提交(显示上次驳回理由、可填修改说明)→ 状态回到 pending
- 每次修改自动记录历史版本(
history数组,上限 20 条,版本号单调递增) - 管理员审核页展示历史列表;任意版本可查看(
/post/<id>/version/<n>)并与当前版本行级 diff 对比(difflib,加绿删红 + 增删统计) - 管理员直接编辑文章同样自动入历史(内容无变化不记录)
v2.4 · 文章信息条与天气(2026-08-15)
- 文章标题下方信息条:约 X 分钟读完 · 城市 · 天气图标 + 温度
- IP 定位三级链路:ip-api.com → ipwho.is → pconline(国内)
- 天气三级链路:心知天气(服务端代理,密钥不落浏览器)→ wttr.in → Open-Meteo
- 服务端 10 分钟城市缓存 + 1.1s 节流(尊重心知 1 次/秒限频),全节点失败静默降级
- 和风天气图标库本地化(
static/vendor/qweather-icons/),天气码/文本双映射 - 图标映射迭代:心知 code 表与 API 实际描述不符 → 改用权威
text字段映射
v2.4.1 · 天气链路完善(2026-08-15)
- 接入心知天气 v3(服务端代理
/api/weather,密钥仅存服务器环境变量,浏览器永不接触) - 三级节点兜底(服务端):心知天气 → wttr.in → Open-Meteo,任一成功即返回
- 10 分钟城市缓存 + 1.1s 全局节流(适配心知 1 次/秒限频、10 万次/月额度)
- 图标映射迭代:心知 code 表与 API 实际描述不符(code=14 实为"中雨")→ 改用权威
text字段映射 - 排障记录:
UnboundLocalError(模块级节流变量在函数内赋值未声明global)→ 一行修复
v2.5 · 安全加固(2026-08-15)
- 全站安全响应头上线:
X-Content-Type-Options/X-Frame-Options/Referrer-Policy/Permissions-Policy(首页/文章页/API/静态资源全覆盖) - 外部安全测试:敏感文件、路径穿越、越权访问、HTTP 方法、错误页泄露共 9 项全部通过;SSH/8888/25 端口暴露面待收敛
- 排障记录(安全头三层根因):① server 级头被 location 级
add_header X-Cache阻断继承 → ② BT 反代location ^~ /写法未匹配正则 → ③ if 块内 add_header 遮蔽(经典坑:条件成立时替换 location 级全部头)→ 最终在 location 与 if 两条分支同时写入
运维横切迭代(v2.0 → v2.5)
| 问题 | 修复 |
|---|---|
| Google Fonts 国内不可达 → 全站无手写字体 | 字体全部下载本地化(static/fonts/,525 个 woff2) |
| jsdelivr 图标 CDN 不稳 | 图标字体本地化(static/vendor/qweather-icons/) |
| 解压后目录缺执行权限 → 静态资源 404 | 部署规范:chmod -R a+rX |
| 英雄区副标题与"向下翻阅"重叠 | flex 布局重构 + --hero-gap 间距令牌 |
| 无目录文章残留 220px 空列 | :has() 网格折叠 + 侧栏隐藏 |
| 移动端适配不足 | 表格/代码横向滚动、安全区、iOS 输入框防缩放、超窄屏收紧 |
| 500 UnboundLocalError | 模块级节流变量在函数内赋值 → global 声明 |
| 安全响应头不生效 | 三层根因(继承阻断 → ^~ 写法 → if 块遮蔽),最终双分支写入 |
3. 当前功能清单(v2.4)
3.1 前台
- [x] 沉浸式英雄区(打字机标题 / 朱红印章 / 装饰大字)
- [x] 非对称文章网格 + 分类筛选 + 滚动渐入
- [x] 日记本风格文章页(首字下沉 / 毛笔竖线引用 / 书法体代码块)
- [x] 自动目录(≥2 标题显示,滚动高亮,移动端折叠)
- [x] 阅读进度条 / 明暗主题(localStorage + 跟随系统)
- [x] 文章信息条(阅读时长 / IP 城市 / 三级天气链路:心知→wttr.in→Open-Meteo,10 分钟缓存)
- [x] 上/下一篇导航、历史版本查看(作者)
3.2 用户端
- [x] 注册(唯一用户名 / 哈希密码 / 自动登录)/ 登录 / 登出
- [x] 投稿(Markdown 实时预览 / 每日 5 篇上限)
- [x] 个人中心(三态统计 / 投稿列表 / 驳回理由 / 改邮箱改密码)
- [x] 被驳回文章修改并重新提交(记录历史版本)
- [x] 站内通知(铃铛 / 轮询 / 已读管理)
3.3 管理端
- [x] 文章管理(直接发布 / 编辑 / 删除,内容变更自动入历史)
- [x] 投稿审核(待审列表 / 详情预览 / 通过 / 驳回 + 理由)
- [x] 用户管理(搜索 / 排序 / 分页 / 角色 / 禁用 / 重置密码 / 删除 / 审计日志)
- [x] 历史版本追溯与行级 diff
4. 技术架构
4.1 架构
浏览器(原生 HTML/CSS/JS)
│ HTTP
▼
Nginx(静态资源 / 反向代理 / Gzip / 缓存 / 限速)
│ proxy_pass 127.0.0.1:8000
▼
Gunicorn(2 worker + 2 thread)
│
▼
Flask 应用(app.py,单文件 ~1500 行)
│
├─ data/users.json 用户(id/username/email/password_hash/role/is_active/notifications)
├─ data/posts.json 文章(含 history 版本数组、status、author)
└─ data/audit.log 管理员操作审计(JSON Lines)
4.2 技术栈
| 层 | 选型 | 说明 |
|---|---|---|
| 后端 | Flask 3.x | 路由 / Jinja2 / JSON API |
| WSGI | Gunicorn | 2 worker + 2 threads(2C2G 黄金配置) |
| Web 服务器 | Nginx(宝塔) | 静态文件 / 反代 / Gzip / 缓存 |
| 存储 | JSON 文件 ×2 | 零数据库进程,单人/低并发场景 |
| Markdown | python-markdown + bleach | fenced_code / tables / toc 扩展 + 白名单清洗 |
| 前端 | 原生 JS(零依赖) | main / admin / users / notification / post-meta / md-preview |
| 字体/图标 | 本地托管 | Google Fonts + QWeather Icons 全部下载自托管 |
4.3 数据模型
User:id, username, email, password_hash, role('admin'|'user'), is_active, notifications[], created_at
Post:id, title, category, tags[], markdown, excerpt, created_at, updated_at, author_id, author, status('pending'|'published'|'rejected'), submitted_at, reviewer_id, review_comment, history[]
Notification:id, type('approved'|'rejected'|'system'), title, content, post_id, is_read, created_at
History version:version, title, content, category, tags[], modified_at, change_reason
5. 设计系统
5.1 设计令牌(tokens.css)
| 令牌 | 值 | 用途 |
|---|---|---|
--color-paper |
#f5f0e8 |
宣纸底色(暗色主题切换为墨夜 #211f1c) |
--color-ink |
#2c2a28 |
墨色文字 |
--color-accent |
#c0392b |
朱红点缀 |
--font-serif |
'Klee One', serif |
正文 |
--font-handwriting |
'ZCOOL KuaiLe', cursive |
标题 |
--shadow-paper |
0 4px 20px rgba(0,0,0,.08) |
纸感投影 |
--hero-gap |
2rem |
英雄区间距 |
| 扩展 | ink-soft / ink-faint / accent-deep / line / calligraphy / deco / lift | 全站唯一数据源 |
5.2 核心组件规范
- 卡片:半透明纸色 + 泛黄墨线 + 悬停上浮
- 徽章:待审核(赭黄)/ 已发布(墨绿)/ 已驳回(朱红灰)
- 横幅:成功墨绿 / 错误朱红
- 模态框:毛玻璃遮罩 + 宣纸面板
- Toast:右上角堆叠,2.6s 自动消失
- 空状态:篆刻印章风格(「暂无新消息」)
6. 安全设计
| 威胁 | 对策 |
|---|---|
| 密码泄露 | werkzeug 哈希存储(不存明文);重置密码生成随机临时密码 |
| 暴力破解 | 登录 15 分钟失败 5 次锁定 IP(内存记录) |
| XSS | 所有 Markdown 输出经 bleach 白名单清洗;会话 cookie HttpOnly + SameSite=Lax |
| 越权 | login_required / admin_required 双层装饰器;通知/文章仅本人或管理员可见 |
| CSRF | SameSite=Lax 基础缓解(TODO:正式 CSRF token,见技术债) |
| 投稿滥用 | 每日 5 篇上限(可配置) |
| 密钥泄露 | 天气/管理员密钥仅存服务器环境变量,浏览器永不接触 |
| 审计 | 管理操作全部写入 data/audit.log |
| 安全响应头 | 全站 4 项响应头上线(nginx 双分支写入,含 if 块遮蔽处理) |
| 暴露面 | SSH/8888/25 端口收敛与密钥登录加固列为待办(见 §9) |
7. 性能与容量
- 静态资源:7 天强缓存 + 本地字体/图标(国内零外部依赖)
- 天气接口:10 分钟城市缓存(上限 200 城)+ 1.1s 全局节流
- 通知:单用户上限 200 条;历史版本:单文章上限 20 条
- 2C2G 预算:系统 350MB + 宝塔 300MB + Gunicorn 200MB + Nginx 30MB ≈ 900MB,建议开启 2G swap
8. 部署与运维
8.1 环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
HOST / PORT |
127.0.0.1 / 5000 | 监听地址与端口 |
ADMIN_USERNAME / ADMIN_PASSWORD |
admin / paperink | 首次创建管理员(务必修改) |
SECRET_KEY |
随机 | 会话签名(生产固定) |
SENIVERSE_API_KEY |
未设置 | 心知天气 v3 密钥(未配置自动降级) |
8.2 部署要点(宝塔 + systemd)
unzip -o paperink-blog.zip -d /www/wwwroot
chmod -R a+rX /www/wwwroot/paperink-blog # 必做(Windows zip 无 Unix 权限)
systemctl daemon-reload && systemctl restart paperink # 模板/代码变更必须重启
8.3 备份
# 全部数据 = data/ 目录(users.json + posts.json + audit.log)
0 4 * * * tar -czf /www/backup/blog-$(date +\%F).tar.gz /www/wwwroot/paperink-blog/data
8.4 常见排障
| 症状 | 处理 |
|---|---|
| 502 | journalctl -u paperink -n 30 查 worker 启动失败(常见:装饰器顺序 NameError) |
| 全站无样式 | 检查 chmod -R a+rX 与 Nginx /static/ 别名 |
| 天气接口 500 | 查日志堆栈(曾出现:模块变量函数内赋值未声明 global) |
| 模板改动不生效 | 生产模式 Jinja 缓存 → 必须重启 |
9. 已知问题与技术债
- JSON 并发写:多用户同时提交存在理论上的写竞争(当前单人/低并发可接受;多人运营建议迁 SQLite)
- 节流粒度:
_LAST_SENIVERSE_CALL为进程内变量,Gunicorn 多 worker 下实际上限为 worker 数 ×1 次/秒(缓存使实际触发极低) - 无 CSRF token:当前依赖 SameSite=Lax,正式对外开放前应补
flask-wtf或自实现 token - 无自动化测试:全部依赖人工验收清单(见 §11)
- 域名/HTTPS 未落地:当前 IP+HTTP 访问;
.cc.cd无法备案,待换可备案域名或境外服务器 - 未限制 /admin 访问来源:公网暴露时建议叠加 Nginx IP 白名单或 Basic Auth
- 搜索功能缺失:仅首页分类筛选,无全文搜索
- 图片能力缺失:无本地上传,仅支持外链 URL
10. 未来规划(Roadmap)
v3.0 · 内容深化
- [ ] 全文搜索(标题/正文/标签)
- [ ] 评论系统(站内信体系复用)
- [ ] 图片上传 + WebP 自动转换(cwebp)
- [ ] RSS / Atom 订阅
v3.1 · 工程化
- [ ] SQLite 迁移(解决并发写)
- [ ] CSRF token + 会话安全加固
- [ ] pytest 自动化测试 + CI
- [ ] 日志结构化(访问日志接入审计)
v3.2 · 平台化
- [ ] 域名 + Let's Encrypt HTTPS + 备案页脚
- [ ] CDN 加速(静态资源)
- [ ] PWA / 离线阅读
- [ ] 多语言支持
11. 验收清单(回归测试)
核心链路
- [ ] 注册 → 自动登录 → 投稿 → 待审核状态
- [ ] 管理员驳回(填理由)→ 作者收站内信 → 修改重投 → 管理员通过 → 首页可见
- [ ] 文章页:目录 / 进度条 / 信息条(时长+城市+天气)正常
- [ ] 历史版本:查看任意版本 + 行级 diff 正确
- [ ] 未登录访问受限页 → 跳转登录;普通用户访问后台 → 403
健壮性
- [ ] 断网时页面正常(天气/定位静默降级)
- [ ] 手机端(≤375px)无溢出、无重叠
- [ ] 明暗主题切换全部页面一致
- [ ] 连续登录失败 5 次被锁定
- [ ] 安全响应头:
curl -sI http://IP/ | grep -i "x-content\|x-frame\|referrer"有输出
部署
- [ ] 重启后服务自启、数据不丢
- [ ] 备份恢复演练(data/ 目录还原)11