Files
fangke/docs/联调记录-m1-task16.md
T
2026-09-15 16:51:50 +08:00

109 lines
7.0 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.
# Task 16 联调记录:SDK ↔ 后端本地集成验证
- 日期:2026-09-15
- 分支:m1
- 验证路径:**Playwright 真实浏览器**(主链路)+ **curl**(信封/Origin/幂等)+ **manage.py shell**(DB 断言)
- 环境:Windows Git Bash;server 为 Django runserver(sqlite 开发模式),demo 静态站为 `python -m http.server`
## 0. 环境与命令实录
### 后端(server/)
```bash
DB_ENGINE=sqlite .venv/Scripts/python manage.py migrate # 含 tracker.0002 迁移,OK
DJANGO_SUPERUSER_PASSWORD='Abcd1234!' DB_ENGINE=sqlite .venv/Scripts/python manage.py createsuperuser --noinput --username admin --email admin@example.com
# site_x 在本机 dev 库已存在(早期 'probe' 站占用 site_id),任务原文的 get_or_create(name='demo', defaults=dict(site_id='site_x',...))
# 会撞 UNIQUE(site.site_id)——改为原地更新既有行(等效最终状态):
DB_ENGINE=sqlite .venv/Scripts/python manage.py shell -c "..."
# Site.objects.get(site_id='site_x') -> name='demo', domains=['127.0.0.1'], allow_no_origin=True
DJANGO_DEBUG=1 DB_ENGINE=sqlite .venv/Scripts/python manage.py runserver 127.0.0.1:8000 --noreload # 后台
```
### SDK 与 demo 静态站(sdk/)
```bash
pnpm build # dist/v1.js 4648 字节(≤15KB 红线 ✓)
python -m http.server 18080 --bind 127.0.0.1 # 注意:根目录必须是 sdk/(见发现 3)
```
**访问 URL**:`http://127.0.0.1:18080/demo/index.html?utm_source=google&utm_medium=test`
(计划原文写 `--directory demo` + `/index.html`,但 demo 页引用 `../dist/v1.js`,`--directory demo` 时该路径逃逸出 web 根 → 404,SDK 根本加载不到。须以 sdk 为根、走 `/demo/index.html`。)
### 浏览器操作序列(Playwright MCP)
1. 打开 index.html(带 utm_source=google&utm_medium=test),停留约 20s
2. 点击「去第 2 页」链接(同站内导航 → page2.html),停留约 8s
3. evaluate 合成 `visibilitychange`(hidden→visible 各一次)触发 page_stay + flush(true)
4. 再停留数秒,reload page2(同 visitor 再访问)
## 1. 断言清单结果(全部通过)
### ① Event 表三类事件、is_bot=False —— PASS
```
sdk_loaded | /demo/index.html?utm_source=google&utm_medium=test | {sdk_version: 0.1.0}
page_view | /demo/index.html?utm_source=google&utm_medium=test | {utm:{utm_source:google,utm_medium:test}, referrer_domain:""}
page_stay | /demo/index.html?utm_source=google&utm_medium=test | {active_ms: 29000}
sdk_loaded | /demo/page2.html
page_view | /demo/page2.html | {referrer_domain:"127.0.0.1", referrer:"http://127.0.0.1:18080/demo/index.html?..."}
page_stay | /demo/page2.html | {active_ms: 18000}
page_stay | /demo/page2.html | {active_ms: 11000} ← reload 时 pagehide 触发
sdk_loaded/page_view ×2(reload 后)
```
所有事件 `is_bot=False`、`late=False`。
### ② Visitor 归因 —— PASS
```
visit_count=1(≥1) active_days=1(==1) is_bot=False
first_utm={utm_source: google, utm_medium: test}
first_landing_path=/demo/index.html?utm_source=google&utm_medium=test # 含 utm 参数 ✓
device=PC / os=Windows / browser=Chrome / language=zh-CN / screen=1920x1080
last_ip=127.0.0.1
```
### ③ VisitSession —— PASS
```
event_count=9 stay_ms=58000(=29000+18000+11000,>0 ✓)
utm={utm_source: google, utm_medium: test} ✓
referrer_domain="" —— 首个落地页为地址栏直开(无 referrer),按设计首触留空;
page2 的页级来源 referrer_domain=127.0.0.1 已记在事件 properties 中 ✓
duration_ms=1 —— 批内跨度累加(计划 §Task18 明示"批内跨度累加,仅内部字段不展示"),符合设计,非 bug
```
### ④ 刷新后幂等 —— PASS
- 同一 visitor_id `b507cb61-…`(get_or_create 幂等,Visitor 全程仅 1 行)
- reload 复用 sessionStorage 的 session_id(30 分钟会话窗口内,属预期),未虚增 visit_count
- `Event 总数 == 去重 event_id 数`(9 == 9;含 curl 批后 11 == 11)
- curl 重放同批次:`{"accepted":0,"duplicated":1}` —— 幂等去重生效
### ⑤ 响应信封(curl)—— PASS
| 请求 | 结果 |
|---|---|
| 无 Origin(allow_no_origin=True) | `200 {"code":200,"data":{"accepted":1,"duplicated":0},"message":"ok"}` |
| `Origin: http://evil.example.com` | `403 {"code":403,"data":null,"message":"not_allowed"}`(且带 ACAO 回显,跨域 SDK 可读错误体) |
| `Origin: http://127.0.0.1:18080`(白名单) | `200` + `Access-Control-Allow-Origin: http://127.0.0.1:18080` 回显 |
| 同批重放 | `200 {"accepted":0,"duplicated":1}` |
### ⑥ SDK 异步加载 —— PASS
- demo 页 `<script async src="../dist/v1.js">`,Network 面板可见 `GET /dist/v1.js => 304`
- 内联队列 `ctk.q` 模式生效:async 脚本加载完成前排队的 `init` 被正确消费(批次 site_id=site_x、endpoint 指向 8000 可证)
- 浏览器实际发出 2 次 fetch POST(各 200),sendBeacon 3 次(见发现 4)
### 附加:管理 API 对拍(真实数据,为 Task 17/18 铺路)
- `POST /api/admin/auth/token`(admin)→ JWT
- `GET /api/admin/dashboard/overview` → `today{pv:11,uv:2,sessions:2,new_visitors:2,active_now:2}`,top_pages/sources 正确
- `GET /api/admin/visitors/{id}` → `total_stay_ms=58000, first_source=google, online=true`
## 2. 发现的问题与处置
| # | 发现 | 定性 | 处置 |
|---|---|---|---|
| 1 | dev 库已有 site_x('probe')时,任务原文 `get_or_create(name='demo', defaults=dict(site_id='site_x',…))` 撞 UNIQUE(site.site_id) 抛 IntegrityError | 环境态问题(命令对全新库才成立) | 原地更新既有行了事;后续复用 dev 库时同样处理 |
| 2 | 本机 8080 端口被占(WinError 10013,PID 38864 非本任务进程) | 环境问题 | 改用 18080;demo 页 endpoint 为绝对地址,端口无关 |
| 3 | `python -m http.server --directory demo` 时 demo 页的 `../dist/v1.js` 逃逸 web 根 → 404,SDK 不加载 | **使用方式陷阱**(demo 页相对路径要求以 sdk/ 为服务根) | 以 sdk/ 为根、URL 用 `/demo/index.html`;计划文档该步的 `--directory demo` 写法不可用,已在本记录纠正 |
| 4 | sendBeacon 发出的 page_stay 批次不出现在 Playwright network 列表 | 工具观测局限 | 以服务端 DB 断言为准(events/stay_ms 均完整入库) |
| 5 | SDK context 携带 `timezone:"Asia/Shanghai"`,但 Visitor.timezone_name 落库为空 | 计划内缺口(计划 `_FIELD_LEN` 即未含 timezone_name,实现与计划一致) | 非 M1 bug;建议 M2 在 `_FIELD_LEN` 增补 `timezone_name: 64` |
| 6 | duration_ms=1 而会话真实跨度约 178s | **非 bug**:计划明示"批内跨度累加,仅内部字段不展示",展示口径用 stay_ms | 无需修复(联调中曾疑为 bug,核对计划与 test_visitors 注释后排除) |
**结论:未发现产品代码 bug,无需 fix 提交。** 88(server)/46(sdk)测试基线不受影响(本次未改产品代码)。
## 3. 验证后状态
- db.sqlite3 已在 .gitignore,不入库(保留供 Task 17/18 复用真实数据)
- runserver / http.server 后台进程已终止