Website Analytics 以服务端生成 VisitorId 关联匿名行为,用有界 Channel 异步落库,再按租户重建 Mart。它有数据最小化和自助查询/擦除接口,但当前没有 Consent 状态,也没有匿名 Visitor 所有权证明。
1. 摄取路径
2. Visitor Cookie
Cookie 是 256 位随机 Base64Url,长度固定 43,HttpOnly、SameSite=Lax、Path=/、MaxAge 365 天;仅在 HTTPS 请求时设置 Secure,并标记 IsEssential=true。
中间件只校验长度和字符集,不签名/加密 Cookie。客户端可以自行设置另一个合法格式值。源码注释明确:VisitorId 只用于限流分区和匿名会话关联,不构成身份凭证。
3. Journey
Redis Journey 以 VisitorId 为全局 Key,分布式锁保护 FirstTouch;30 分钟无活动生成新 Session,缓存 TTL 400 天。首次和当前 UTM 去空白、限长,并拒绝明显邮箱/电话号码。
缓存不可用时中间件只记录 warning,后续使用 SessionId="missing" 的 fallback context。大量降级流量会共享同一 SessionId,影响会话统计和实验分析。
4. 自动 PageView
公开/API telemetry/短链表面经过中间件。成功 GET(非 telemetry)且 Journey 存在时自动采集 pageview;这包括公开内容、隐私 GET、实验分配与短链成功响应。
当前没有读取同意 Cookie、Consent Store、Global Privacy Control 或 Do Not Track。IsEssential 只是 Cookie 配置,不是法规依据;文档不能说“先确认同意再采集”。
5. 自定义事件与 Web Vitals
Telemetry 每批 1..50,Web Vitals 1..20;LCP/INP 0..120,000ms,CLS 0..10。客户端时间与接收时间偏差不得超过 5 分钟。
客户端不能直接提交 experiment-assigned。带实验维度的 conversion 必须让 Experiment service 验证 assignment token。Web Vitals 会合并 lcp/cls/inp/thresholdViolation 维度。
6. 数据最小化
Path 去 query/fragment,Referrer 只保留 scheme/host/path,UA 归为粗设备/浏览器,IP 先截 IPv4 /24 或 IPv6 /64 再交 GeoIP,存储只保留 region。
维度最多 20 个;Key ≤48、Value ≤256;只接受内置/服务端注册 Key,并启发式拒绝 email/phone/contact/name/ip/useragent 或明显标识值。
启发式不是 DLP:自由文本可能绕过,合法 7 位数字也可能误拒绝。注册新维度必须做隐私和基数审查。
7. 接纳语义
// ① Collector 先构造完整批次并分配稳定持久化 ID。Result created = collector.Capture(context, events);
// ② TryWrite 失败时返回 AnalyticsQueueFull,不等待容量。if (created.IsFailure) return Problem(created.Error);
// ③ Success 只代表进程内 Channel 接纳;崩溃前可能尚未落库。return Accepted();Channel 配置 FullMode=Wait,但 Collector 使用 TryWrite,所以队满立即失败。正常 Stop 会 Complete 后等待排空;进程 crash、kill -9 或节点丢失会丢在途批次。
8. 写入与幂等
Batch Writer 为每批新建 scope,最多重试配置次数并指数退避。Store 先查询已有 IDs,只插入 pending,在显式 UnitOfWork 中提交。
稳定 ID 支持同一批重放去重,但“查询后插入”仍可能遇到并发唯一冲突;多实例重放的分类必须由数据库异常合同证明。它不是端到端 exactly-once。
9. DLQ
重试耗尽的批次进入当前 API 实例的 ConcurrentDictionary,包含完整 AnalyticsEvent、异常类型/消息和次数。健康检查有条目时 Degraded,并明确 durability=in-process。
Operations 端点只看到当前实例;重启后条目消失;负载均衡下运维请求也未必落到故障实例。GA 需要持久 DLQ、集群查询、租约重放、审计和保留。
10. 隐私接口
认证主体接口用 ICurrentTenant + ICurrentUser 查询/软删 SubjectId;匿名接口用 PublicTenantId + Visitor Cookie 查询/软删 VisitorId,并删除 Journey。
匿名 Cookie 可替换,知道/窃取 VisitorId 的人可查询或擦除对应分析。这与源码“非身份凭证”矛盾。必须使用签名 ownership token、一次性恢复凭据或经过验证的账号绑定。
擦除是软删除 AnalyticsEvent;Mart 已聚合数据不会被同一 Handler 同步删除/重建,DLQ/队列中的在途事件可能在擦除后重新落库,Journey 删除也可能被当前请求中间件再次 touch。当前不是完整可重放隐私工作流。
11. Mart
Job 按配置 TenantIds 和 LookbackDays 重算日/周/月 Mart。对齐窗口优先读 Mart,否则扫 facts;最近 5 分钟 realtime 从 facts 计算。
TenantIds 是手工可信列表,新租户遗漏就没有物化数据。重建删除/替换窗口行,需验证双 ORM 事务与并发查询一致性。
12. 测试与运维
覆盖 Cookie 伪造/窃取、无 Consent 自动 pageview、Journey 失败 fallback、PII/基数、队满、正常排空、crash 丢失、重试/DLQ/重启、多实例运维、重复 ID、擦除与在途竞态、Mart 迟到事件和双 ORM。
# ① Consent 当前预期无 Website 命中;VisitorId 非授权注释必须保留。rg -n "Consent|VisitorId.*不构成身份凭证|IsEssential" \ src/Platform/Website src/Hosts/BitzOrcas.Api -g '*.cs'# ② 检查进程内队列与 DLQ。rg -n "Channel\.CreateBounded|ConcurrentDictionary|durability.*in-process" \ src/Platform/Website -g '*.cs'13. Consent 与 DSR 目标合同
GA 设计至少要逐项明确:
- 同意目的、版本、来源、时间和证据;
- 必需 Cookie 与分析 Cookie 的严格拆分;
- 未同意时是否完全跳过 Journey 和 GeoIP;
- 撤回后新的 pageview/telemetry 如何立即停止;
- GPC/DNT 与地区规则如何决策;
- Visitor 与登录 Subject 如何合并/解除绑定;
- 匿名 ownership token 如何签发、轮换和撤销;
- 查询/擦除请求如何防 CSRF 与 Cookie 固定;
- 队列、持久 facts、DLQ 和 Mart 的统一 operation ID;
- 擦除期间如何隔离新到事件;
- 软删、物理清理、备份到期各自 SLA;
- Mart 如何删除或重算单一主体贡献;
- 重放是否检查当前 Consent/erasure tombstone;
- 400 天 Journey TTL 的法律与产品依据;
- GeoIP 数据库、网络前缀和地域字段的保留;
- 自定义维度注册的隐私审批;
- 接纳成功、持久成功和丢失估算的 SLO;
- 多实例队列深度与 DLQ 如何聚合;
- crash-safe inbox 的容量、背压和成本;
- 用户如何获得可理解的导出格式。
- 运维重放由谁审批并记录理由;
- 数据处理器和跨境位置如何进入证据包;
- Consent、采集与擦除版本如何向客户端公开。