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

11 KiB
Raw Blame History

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 截断清理)