Skip to content
bitzorcas
中EN

Guide

Website 分析摄取、匿名身份与隐私

源码级解释 Visitor Cookie、旅程、遥测净化、内存队列、持久化、DLQ、Mart、Consent 缺口与隐私接口风险。

Last updated

Website Analytics 以服务端生成 VisitorId 关联匿名行为,用有界 Channel 异步落库,再按租户重建 Mart。它有数据最小化和自助查询/擦除接口,但当前没有 Consent 状态,也没有匿名 Visitor 所有权证明。

1. 摄取路径

公开请求

Visitor middleware

Redis journey

Capture context

Schema + PII checks

in-process Channel

Batch writer

AnalyticsEvent facts

Mart aggregation

in-process DLQ

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。

Terminal window
# ① 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'

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、采集与擦除版本如何向客户端公开。

返回 Website 总览

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%