8bde9b5b4b
Co-Authored-By: Claude Code <noreply@anthropic.com>
11 KiB
11 KiB
M1 验收记录(本地全栈端到端)
- 日期:2026-09-15
- 分支:m1(HEAD
7ed55c8) - 环境:Windows / Django runserver 127.0.0.1:8000(sqlite 开发模式)+
python -m http.server 18080(根目录 sdk/)+ admin Vite dev 127.0.0.1:3006(代理 /api → 8000) - 方法:Playwright 当"真实访客"走真实 SDK 产物(sdk/dist/v1.js,4648B);curl 验证信封/幂等/限流;
manage.py shell断言 DB;admin UI 与 API 对拍 - 端口说明:计划端口 8080 被本机 Docker Desktop 后端占用(Task 16 已知),按预案改用 18080;demo 页 endpoint 为绝对地址
http://127.0.0.1:8000/api/collect/batch,不受影响 - 测试数据基线:验收前库内已有 Task 16 残留访客
b507cb61-…、curl_v1(访客 2 / 事件 11 / 会话 2);本次新增访客e0456de2-…、curl_v2
⚠️ 治理时间线(用户须先读)
M1 采集开始日期 = 真实网站接入日(B-1 执行当日,待用户回填:____年__月__日)。 IP 截断(存储 IP 截短为前缀)任务必须在该日期起 180 天内上线,到期前请跟踪。本地全栈验收日为 2026-09-15(不含真实用户数据,不启动该时钟)。
A. 本地全栈自动化验收(8/8 PASS)
A1 新访客首次访问 — PASS
- 操作:清空 localStorage 后打开
http://127.0.0.1:18080/demo/index.html?utm_source=google&utm_medium=test,点击页面(鼠标活动)停留约 20s(>15s 触发首批 flush) - 网络:
POST http://127.0.0.1:8000/api/collect/batch => 200(Network 面板实录) - 后台访客列表新增
e0456de2-ce7e-4580-be64-a6b29bda1f73(与浏览器localStorage.ctk_vid一致),first_seen 21:44:25 == 页面打开时刻:- 环境:PC / Windows / Chrome / zh-CN / 1920x1080(详情页显示一致)
- 来源:first_source=
google,first_utm={utm_source:google, utm_medium:test},落地页含 utm 参数 - 地理:country/province/city 全空——本地 IP 127.0.0.1 无地理归属,属预期
- visit_count=1、active_days=1、session_count=1、online=true
- 说明:首次打开复用了 Task 16 的 Playwright 浏览器 profile(
ctk_vid=b507cb61…仍在),该访客被识别为回访(visit_count 1→2)——恰证明跨会话身份保持是产品能力;随后清空存储重开才得到"新访客"。副作用:b507cb61…的 visit_count 被 +1(历史测试数据,不影响本次断言)
A2 浏览 3 页各停留 — PASS
- 操作:index(21:44:25 起 ~64s)→ 点链接去 page2(21:45:29 起 ~31s)→ 返回 index(21:46:00 起 ~98s);每页期间点击标题保持鼠标活动;每页 >15s 触发定时 flush,导航时 pagehide→sendBeacon 发 page_stay
- 时间线(API
visitors/{id}/timeline,服务端已把 page_stay 合并进同 path 的 page_view):- page_view ×3:index?utm…(active_ms 45000)/ page2(30000)/ index(34000),均 >10s ✓
- 会话 stay_ms=160000(=45+30+34+51s,含一条独立成条的 page_stay,见"发现 2")
- page_stay 两种触发均验证:真实导航(pagehide)与合成 visibilitychange(hidden)
A3 再次访问同访客(清 ctk_sid/ctk_act,保留 ctk_vid)— PASS
- 操作:evaluate 删除
ctk_sid、ctk_act(ctk_vid保留)→ reload - 结果:visit_count 1→2、session_count 1→2;时间线同一天(2026-09-15)出现第 2 个会话组:21:44(来源 google)与 21:47(来源 直接访问);admin 详情页显示 访问次数 2 / 会话数 2 / 总停留 3分14秒
A4 幂等(curl 重放同批次)— PASS
首发:{"accepted":1,"duplicated":0} 重放:{"accepted":0,"duplicated":1}
DB:event_id aaaaaaaa-bbbb-…-0001 恒为 1 行
- 加压复验:同一批次连发 121 次(幂等批次不产生新行)→ 118×200(均 duplicated=1) + 3×429(限流),DB 该 event_id 仍 1 行、事件总数无增长——重放风暴既不重复入库也不放大写放大
A5 伪造 Origin → 403;超频 → 429 — PASS
Origin: http://evil.example.com+ 合法批次体 →403 {"code":403,"message":"not_allowed"}(ACAO 回显错误体,跨域 SDK 可读)Origin: http://127.0.0.1:18080(白名单)→200+Access-Control-Allow-Origin: http://127.0.0.1:18080- 限流(collect 120/min/匿名 IP):
- 空体突发 122 发 → 117×403(无 site_id)+ 5×429
- 幂等批次突发 121 发 → 118×200 + 3×429
- 捕获 429 响应全貌:
HTTP/1.1 429+Retry-After: 23+ 信封体{"code":429,"data":null,"message":"请求已被限流。 预计 23 秒后可用。"} - 与
tracker/tests/test_collect.py限流用例互证(阈值语义已由测试固化)
- 口径备注:无 Origin 时能否上报由站点
allow_no_origin决定(site_x=True);空 JSON 体({})因解析不出 site_id 也走 403;非法 JSON 报 400 invalid_json——均为 collect.py 既定检查顺序,非缺陷
A6 Dashboard 对拍 — PASS
- 同分钟快照:admin「数据总览」UI 今日 PV 27 / UV 4 / 会话 6 / 新访客 4 / 在线 1 == curl
dashboard/overviewtoday{pv:27,uv:4,sessions:6,new_visitors:4,active_now:1};热门页面两张完全一致(3/3/2/1,路径与排序相同) - 访客"总停留"== 各会话 stay_ms 之和(shell 聚合 vs API 列表 total_stay_ms,4/4 访客全等):
e0456de2… API 194000ms == Σ(160000+34000) b507cb61… API 58000ms == Σ(58000+0) curl_v1 / curl_v2 API 0 == Σ 0 - 口径备注:overview 的 pv 统计全事件(stats.py 明示"pv/uv 均从事件表统计",test_dashboard 固化),top_pages 的 pv 只计 page_view——两者非同一口径,属设计非 bug。截图:
.playwright-mcp/验收-A6-数据总览.png
A7 SDK 性能 — PASS
- 异步非阻塞:demo 页
<script async src="../dist/v1.js">(DOM 实测 async=true);Performance API:DOMContentLoaded 7ms / load 8ms,SDK 加载不阻塞渲染;ctk.q命令队列在 async 脚本就绪后被正确消费(批次 site_id=site_x 到达服务端可证) - 体积:dist/v1.js 原始 4648B,gzip(9) 2167B ≤ 15KB 红线(本地 http.server 无压缩,gzip 为离线实测;生产 nginx gzip 后同量级)
A8 admin 全链路 smoke — PASS
- 路径:清 token → 登录页(admin / 滑块验证拖到底)→ 登录成功 → 数据总览 → 访客管理(列表)→ 访客详情(轨迹 tab:跨天时间线、日期/会话双层分组;会话 tab:会话列表 + 会话详情弹窗)→ 站点管理(site_x「demo」启用中,域名 127.0.0.1)
- Console:admin 全程 0 error / 2 warning(全程唯一 error 为 demo 静态站 favicon.ico 404,系
python -m http.server无 favicon 文件,与 SDK/admin 无关) - 截图:
.playwright-mcp/验收-A8-访客详情时间线.png(含 21:44/21:47 两会话组与停留秒数)
B. 用户手动项清单(待用户执行)
| # | 事项 | 步骤与依据 | 完成打勾 |
|---|---|---|---|
| B-1 | 真实网站接入(WordPress 或任意站) | ① 按 deploy/README-deploy.md §6 后台建站点拿 site_id;② 按 §7 目标站 <head> 贴两行接入代码(endpoint/SDK 地址换正式域名);③ 隐私告知前置:先在目标站挂隐私/Cookie 告知并获得访客同意,再启用采集——治理底线"先上告知再采集",顺序不可颠倒;④ 回填上方"M1 采集开始日期" |
☐ |
| B-2 | 次日回访复验 | 次日用同一浏览器访问目标站(或本地 demo)→ 后台该访客:时间线出现新日期组、visit_count+1、active_days+1。当天无法模拟:服务端对 occurred_at 偏差 >120s 标 late、>24h 直接拒绝(occurred_at_out_of_window,constants.py CLOCK_SKEW_REJECT_SEC=86400)——防篡改设计,伪造时间戳走不通 |
☐ |
| B-3 | 1Panel 生产部署 + HTTPS + 定时任务 | 按 deploy/README-deploy.md:§1 前置构建(admin/sdk dist + ip2region xdb)→ §2 MySQL 准备 → §3 配 .env(fail-fast 护栏)→ §4 compose 部署/建管理员 → §5 HTTPS(推荐 1Panel 反代+Let's Encrypt);§8 定时任务:1Panel 计划任务每日 00:10 docker exec tracker-web python manage.py refresh_stats --days 2(不做则趋势图历史全零);上线后浏览 §9 JWT 应急、§10 CDN/WAF 的 NUM_PROXIES 调整 |
☐ |
| B-4 | 记录 M1 采集开始日期并跟踪 IP 截断 180 天期限 | B-1 完成当日回填本文档顶部日期;IP 截断功能须不晚于该日 +180 天上线(建议即刻立日历提醒) | ☐ |
发现与说明(均非缺陷,留档备查)
- Playwright profile 跨任务持久:
ctk_vid存 localStorage,浏览器复用会以"回访访客"身份进门——测试新访客须先清存储;真实用户场景这正是跨会话身份保持的正确表现。 - 清存储时机与临终 page_stay 归属:先
localStorage.clear()再导航,旧页面 pagehide 发出的 page_stay 会按"flush 时读取的身份"落到新 visitor_id/新 session 下(本例 51s 独立成条)。真实用户无此操作序列(不会中途清存储),SDK 行为符合设计(身份在发送时解析);测试 A3 因此改为"先合成 visibilitychange 发完 page_stay、再清 sid/act reload"。 - GBK 管道乱码:Git Bash 下
curl | python直读中文 JSON 会按 cp936 误解 UTF-8(如"直接访问"显示为乱码);加PYTHONIOENCODING=utf-8后核对无误,服务端返回的始终是正确 UTF-8(已抓包对字节核实)。工具链问题,非产品问题。 - 登录滑块验证码对自动化不友好:Playwright
dragTo默认距离差几像素不触发成功(组件要求拖满宽度-滑块高),需分步 mouse 移动拖到最右端。模板组件行为,不影响人工使用;如需常做 UI 自动化可考虑测试模式下禁用验证码。 - 8080 端口:本机被 Docker Desktop 后端占用(WinError 10013 / PID 38864),非本任务进程,未处置;demo 静态站按预案用 18080。
- favicon.ico 404:
python -m http.server无该文件导致 demo 站 console 一条 404;生产 nginx 配置已含 favicon(deploy/nginx.conf),无需处理。
结论与遗留
- A 部分 8/8 PASS,无需修复项;未发现产品代码缺陷,未改产品代码(server 89 / sdk 46 测试基线不受影响)。
- 遗留问题:
- B 部分四项待用户执行(真实接入 / 次日复验 / 生产部署 / 采集开始日期登记)。
- Task 16 发现 5(timezone_name 恒空)已在后续代码修复并经本次验收实证:ingest.py
_touch_visitor显式映射ctx.timezone → timezone_name,新访客 e0456de2 落库Asia/Shanghai(curl 访客无该字段为空,符合预期)。旧访客 b507cb61 创建于修复前,环境字段只在首建时定格,保持空属既定行为。
- 收尾:打 tag
m1-beta;验收用 runserver / http.server 进程已终止;admin Vite dev(3006,验收前已在本机运行,非本次启动)保留原状。
合规时钟(2026-09-16 记录)
- 正式采集开始日期:2026-09-16(hesucar.com 接入真实流量)
- IP 截止截断期限:2027-03-15(180 天,M2 治理任务必须在此前上线 IP 截断清理)