纸上春秋
小站运维相关 作者:admin 更新于 2026-08-15

纸上春秋 · 博客产品迭代文档

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 数据模型

Userid, username, email, password_hash, role('admin'|'user'), is_active, notifications[], created_at

Postid, title, category, tags[], markdown, excerpt, created_at, updated_at, author_id, author, status('pending'|'published'|'rejected'), submitted_at, reviewer_id, review_comment, history[]

Notificationid, type('approved'|'rejected'|'system'), title, content, post_id, is_read, created_at

History versionversion, 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. 已知问题与技术债

  1. JSON 并发写:多用户同时提交存在理论上的写竞争(当前单人/低并发可接受;多人运营建议迁 SQLite)
  2. 节流粒度_LAST_SENIVERSE_CALL 为进程内变量,Gunicorn 多 worker 下实际上限为 worker 数 ×1 次/秒(缓存使实际触发极低)
  3. 无 CSRF token:当前依赖 SameSite=Lax,正式对外开放前应补 flask-wtf 或自实现 token
  4. 无自动化测试:全部依赖人工验收清单(见 §11)
  5. 域名/HTTPS 未落地:当前 IP+HTTP 访问;.cc.cd 无法备案,待换可备案域名或境外服务器
  6. 未限制 /admin 访问来源:公网暴露时建议叠加 Nginx IP 白名单或 Basic Auth
  7. 搜索功能缺失:仅首页分类筛选,无全文搜索
  8. 图片能力缺失:无本地上传,仅支持外链 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