From 1e39aa122f8faa3c9f9f8ce53d2266ccd68b50a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 15 Sep 2026 22:03:24 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20M1=20=E9=AA=8C=E6=94=B6=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=EF=BC=88=E6=9C=AC=E5=9C=B0=E5=85=A8=E6=A0=88=20A=20?= =?UTF-8?q?=E9=83=A8=E5=88=86=208/8=EF=BC=8C=E7=94=A8=E6=88=B7=E9=A1=B9?= =?UTF-8?q?=E6=B8=85=E5=8D=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- docs/验收记录-m1.md | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 docs/验收记录-m1.md diff --git a/docs/验收记录-m1.md b/docs/验收记录-m1.md new file mode 100644 index 0000000..bedef30 --- /dev/null +++ b/docs/验收记录-m1.md @@ -0,0 +1,97 @@ +# 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/overview` today `{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 页 `