Appearance
博客页面访问日志设计
日期:2026-09-24 适用工作树:codex/blog-backend-admin 关联:2026-09-22 静态博客与提示词管理后台重构设计的边界修订
1. 目标与边界修订
公开博客页面记录访问日志(PV 与按日 UV),数据自持、零新增外部服务。用户已确认三项决策: 自建后端打点入库;不做管理端界面(后续用 SQL 查看);记录加盐哈希以支持 UV。
本设计修订原重构规格中"后端仅服务管理员"的边界:后端额外暴露唯一匿名端点 POST /api/public/visits。除此之外一切不变——公开博客内容仍完全来自 Markdown/JSON 静态构建, 不新增任何读取、导出或管理 API。
2. 采集
- VitePress 主题
Layout.vue挂载PageVisitTracker,watch(route.path, {immediate: true})触发;hash/query 变化不重复计数。 - payload 仅
{path ≤512, referrer ≤1024};客户端不采集 IP、UA、屏幕或任何指纹。 navigator.doNotTrack === '1'时跳过。- 优先
navigator.sendBeacon(Blob JSON),fetch keepalive兜底;失败静默。 - 端点默认同源相对地址
/api/public/visits,可经themeConfig.visitLogEndpoint覆盖或置空禁用。
3. 端点与安全面
POST /api/public/visits:permitAll、CSRF 豁免(sendBeacon 无法携带令牌)、匿名不建会话 (响应无 Set-Cookie)、仅 JSON。- 客户端 IP 与 User-Agent 一律取自请求侧(
forward-headers-strategy: framework还原真实 IP), 不信任请求体。 - 限流:内存滑动窗,单 IP 默认 30 次/分钟(
BLOG_VISIT_RATE_LIMIT_PER_MINUTE),全局兜底 600 次/分钟;超限返回42900/ HTTP 429。 - 跨域:默认不开启;CDN 部署时以
BLOG_VISIT_ALLOWED_ORIGIN指定博客来源,仅对该端点开启 CORS。
4. 存储与隐私
表 page_visit_log(Flyway V2):path(512)、referrer(1024) NULL、visitor_hash(64) NULL、 user_agent(255) NULL、visited_at(6);索引 (visited_at)、(path, visited_at)。
visitor_hash = SHA-256(salt | 当日(Asia/Shanghai) | IP | UA小写截断150),盐由BLOG_VISIT_SALT提供;为空时不生成哈希(UV 降级,WARN 一次),明文 IP 全链路不落库。- 当日轮换:哈希材料含日期,跨日不可关联同一访客。
- 不做自动清理与聚合表;个人站量级下原样保留,需要报表时用 SQL 聚合。
5. 验收
- 后端:MySQL 集成测试(插入回读/可空列/倒序/边界长度)、服务单测(哈希稳定性/跨日变化/无盐降级/ 限流触发与恢复/字段校验)、Controller 测试(匿名可达/无 CSRF/400/429/无会话 Cookie/其他公开路径仍拒绝)。
- 前端守卫:根 Node 测试锁定 Tracker 挂载、sendBeacon+keepalive、仅 path+referrer、DNT、 同源默认端点、客户端截断。
- 编排契约:compose/.env.example 传递三个环境变量。
- 冒烟:
smoke-admin.py增加匿名打点检查(200、code 0、无 Set-Cookie)。
6. 明确不在本轮范围
- 管理端统计界面、图表、导出。
- 停留时长、点击热图、滚动深度等增强指标。
- 自动数据清理、保留策略与聚合表。
- 对管理端页面自身的访问统计。
7. 已接受的权衡(2026-09-24 独立复审记录)
- 打点 INSERT 同步执行:数据库故障期间每个请求会在取连接超时内占用容器线程并按限流上限刷 错误日志,个人站量级(600 次/分钟封顶)可接受;未引入异步队列/超时定制,故障恢复后自愈。
- 反代可信度:限流与访客身份依赖
X-Forwarded-For还原的客户端 IP,要求 Nginx 覆盖式设置 该头且后端端口仅绑定回环(见 DEPLOYMENT.md 生产要点第 5 条)。 - 前端行为守卫用根 Node 源码测试锁定(含 SSR
typeof document判定),运行时行为依赖构建 与联调验证。