Files
fangke/docs/验收记录-m1.md
T

103 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 页 `<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 天上线(建议即刻立日历提醒) | ☐ |
## 发现与说明(均非缺陷,留档备查)
1. **Playwright profile 跨任务持久**:`ctk_vid` 存 localStorage,浏览器复用会以"回访访客"身份进门——测试新访客须先清存储;真实用户场景这正是跨会话身份保持的正确表现。
2. **清存储时机与临终 page_stay 归属**:先 `localStorage.clear()` 再导航,旧页面 pagehide 发出的 page_stay 会按"flush 时读取的身份"落到新 visitor_id/新 session 下(本例 51s 独立成条)。真实用户无此操作序列(不会中途清存储),SDK 行为符合设计(身份在发送时解析);测试 A3 因此改为"先合成 visibilitychange 发完 page_stay、再清 sid/act reload"。
3. **GBK 管道乱码**:Git Bash 下 `curl | python` 直读中文 JSON 会按 cp936 误解 UTF-8(如"直接访问"显示为乱码);加 `PYTHONIOENCODING=utf-8` 后核对无误,**服务端返回的始终是正确 UTF-8**(已抓包对字节核实)。工具链问题,非产品问题。
4. **登录滑块验证码对自动化不友好**:Playwright `dragTo` 默认距离差几像素不触发成功(组件要求拖满 `宽度-滑块高`),需分步 mouse 移动拖到最右端。模板组件行为,不影响人工使用;如需常做 UI 自动化可考虑测试模式下禁用验证码。
5. **8080 端口**:本机被 Docker Desktop 后端占用(WinError 10013 / PID 38864),非本任务进程,未处置;demo 静态站按预案用 18080。
6. **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 截断清理)