暂不支持移动端访问

访客统计与隐私数据处理

1792 字 9 分钟
AI 摘要

拆解 AstroBlog 访客系统的 profile、visit、pageview 模型,以及加密、限流和保留期处理。

访客统计把浏览器身份、一次访问会话和页面浏览事件拆开存储,分别处理重复访问、单页导航、管理员身份、网络信号和数据清理。代码主要位于 src/scripts/visitorTracker.tsworkers/comments/src/visitors.ts

三层数据模型#

visitor_profiles 表示一个经过哈希归并的浏览器设备,visitor_visits 表示一次标签页访问,visitor_visit_pages 表示这次访问中的页面事件。一次 profile 可以拥有多次 visit,一次 visit 可以包含多个 pageview。

text
deviceId + site
↓ hash
visitor_profiles
↓ profile_id
visitor_visits + update_token_hash
↓ visit_id
visitor_visit_pages

第一方随机 deviceId 用于在保留期内归并浏览器,Worker 不把原始设备标识存入数据库。标签页进入站点时生成 visitId 和更新 token,后续页面浏览通过 token 证明属于同一次访问。单次 visit 的 pageview 上限为 256,防止异常客户端无限增长。

客户端加载策略#

visitorTracker.ts 不把访客请求放进首屏关键链路,而是在浏览器空闲时初始化。进入页面先发送 /api/visitors/entry,Swup 发生同站导航后,再批量发送 /api/visitors/visits/:id/pageviews。批量事件包含路径、页面标题、referrer 和事件 ID,Worker 负责去重和容量限制。

脚本必须监听新页面的 page:view,同时清理旧页面的定时器和事件,否则同一篇文章可能被记录多次。网络失败不应阻塞页面渲染,tracker 只记录可恢复的错误状态。

Worker 写入流程#

handleVisitorRequest() 负责 /entry/visits/:id/pageviews 两类请求。进入 recordVisitorEntry() 后,Worker 先校验站点、设备 ID、visit ID 和 entry ID,再读取 Cloudflare 边缘 IP、客户端浏览器信息和可选网络探测结果。之后通过限流器检查 visitor-entry:<hash> 键。

如果 visit 已存在,Worker 必须同时验证 device hash 和 update token,验证通过后只追加 pageview 并更新 profile;如果不存在,则批量插入 profile、visit 和首个 pageview。数据库写入使用 env.DB.batch(),减少只创建 profile 但没有 visit 的中间状态。

pageview 接口会再次读取 visit,使用 token hash 验证请求归属,检查 256 条上限,再按 visitor-pageview:<hash> 进行限流。事件经过路径、标题和 referrer 规范化后逐个插入,返回实际接受的数量,而不是直接相信客户端发送的数量。

管理员身份隔离#

管理员不能混入普通访客指标。后台登录后使用独立的 owner cookie,Worker 判断为 owner 请求时更新 visitor_owner_snapshot,而不是创建普通 profile 和 visit。cookie 使用 HttpOnly、SameSite=Strict 和安全属性,绑定操作需要管理员认证和限流。

管理端 VisitorManager.svelte 通过 /api/visitors/admin/* 读取列表、详情、会话和删除结果。删除 profile 时,Worker 先删除关联 pageview,再删除 visit 和 profile;清理快照和撤销的 owner binding 也有单独生命周期,不能只从前端列表移除一行。

网络信息和加密#

网络字段分为 Cloudflare edge 信号和浏览器侧 probe 信号,来源不同不能混为一个 IP。IP 相关值使用 AES-GCM 加密存储,同时保存用于去重和限流的哈希;管理员视图只在授权后解密需要展示的字段。客户端上报的地址、ISP、经纬度等信息是显示元数据,不用于认证。

IP 加密密钥由环境变量提供,Worker 使用缓存的 CryptoKey 减少重复导入。密钥不可用时,健康检查会报告 ipEncryptionReady: false。不要把密钥、原始 IP 或 owner cookie 写进公开日志。

保留期和健康检查#

VISITOR_RETENTION_DAYS 默认 90 天,并限制在 7 到 365 天之间。清理任务按 profile 的 last_seen_at 删除 pageview、visit、profile 和已撤销的 owner binding。管理员健康接口同时返回 schema 版本、加密状态和保留期,排查 D1 时应先检查这个接口。

新增字段时要明确它属于 profile、visit 还是 pageview,设置清理规则,决定是否需要加密,并同步管理端详情和导出逻辑。把所有字段塞进 profile 会让一次访问和长期身份无法区分。

修改和验证#

修改客户端采集:看 visitorTracker.ts 和 page 初始化;修改写入或查询:看 visitors.ts;修改表结构:先写 workers/comments/migrations/;修改管理员显示:看 VisitorManager.svelte;修改加密或保留期:同时检查健康接口、清理 SQL 和文档。

Terminal windowpowershell
# 运行访客路径、网络信号和限流相关测试。
pnpm verify:comments
# 在本地 Wrangler 环境启动带访客加密测试变量的 Worker。
pnpm comments:dev:visitors

本地用例主要验证路径、信号和纯函数。远程 D1、Cloudflare Rate Limiting、加密密钥和 owner binding 必须在目标 Worker 环境单独验收。

字段如何落表#

profile 记录长期但可过期的浏览器属性,例如 device_hash、网络来源、浏览器、语言、时区、主题和最近路径;visit 记录入口路径、referrer、来源分类、UTM 参数、开始/最后访问时间和 pageview 数量;pageview 只记录事件 ID、路径、标题和 referrer。查询访客详情时,Worker 先取 profile,再按 profile_id 取 visits,最后按 visit_id 取 pageviews。

这样的拆分让删除和聚合都可控:删除一个访客可以级联清理三层数据,统计总访客时只 count profile,统计访问次数时 count visit,热门页面则从 pageview 聚合。把所有字段合成一张表会造成重复网络信息,也无法准确表达一次访问的边界。

去重和异常请求#

entry 使用客户端生成的 entry ID,pageview 事件同样带唯一 ID;Worker 在插入时利用唯一约束或查询避免重复。visit token 只保存 hash,客户端重新提交错误 token 时返回 401,而不是创建新的 visit。设备 ID、路径、页面标题和 UTM 字段都有限制长度,防止把超长内容写入 D1 或日志。

访客 API 的限流键不会使用原始 IP,而是使用边缘 IP 或设备 ID 的哈希。IP 加密、哈希和展示字段是三种不同用途,修改其中一个不能顺手复用另一个值。

页面标题和来源字段#

客户端传入的 pageTitle、path、referrer、utmSource、utmMedium 和 utmCampaign 都经过长度限制和清理。path 会转换为规范化的站内路径,referrer 会移除不合法协议并限制长度,来源分类由 Worker 根据 referrer 和当前站点计算。后台展示这些字段时,应把它们当作访客输入,不能拼接进 HTML 或 SQL。

统计接口返回聚合指标和脱敏后的访客视图,管理端详情才会请求单个 profile 的 visits/pageviews。不要让首页直接读取完整访客列表;公开页面只需要经过权限和字段过滤的统计结果。

删除和导出#

删除访客是不可逆的隐私操作,Worker 要求管理员认证并按 site 校验目标 profile,随后删除 pageview、visit、profile 和绑定快照。导出接口应只返回管理端允许的字段,并在响应中明确数据时间范围,不能把内部 token hash、加密密文或完整 owner 绑定返回给浏览器。

[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

找不到相关结果。
[ 目录 ]
[ 全部文章 ]