feat: 移动端设备信息最大化采集(型号/UA-CH/视口/方向/触点/内存/网络/深浅色)
SDK:buildEnvContext 同步扩展 12 键(dpr/viewport/orientation/maxTouchPoints/ deviceMemory/languages/colorScheme/standalone/network downlink·rtt·saveData/ 微信版本),逐键 try/catch 静默缺则不挂;新增 collectHighEntropyHints(UA-CH model/platformVersion/architecture/bitness),init 后 Promise.race 2s 超时 异步并入 __ctk_ctx——批次 context flush 时读取,不阻塞首屏。 后端:Visitor 加 device_model/env_extra(迁移 0005);ingest 首批快照 EXTENDED_ENV_KEYS 白名单(值截 100),非首批不覆盖、唯 device_model 空且 本批有值时补填一次(晚到 hints);列表/详情 API 随行输出。 前端:详情访问环境卡设备行升级显示型号(手机 · Pixel 7),追加像素比/ 视口/屏幕方向/触点/内存/网络质量/深浅色/微信版本行(env_extra 逐键判空, 旧数据整块不显示);列表设备列主行拼型号(手机·Pixel 7)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -89,13 +89,26 @@ def _classify_and_store(*, site, visitor, session, scope, referrer_url,
|
||||
pass
|
||||
|
||||
|
||||
def _extended_env(ctx):
|
||||
"""扩展环境键快照:白名单键 STR 化截 100,空值跳过(任意键不得经 context 入库渲染)。"""
|
||||
out = {}
|
||||
for k in C.EXTENDED_ENV_KEYS:
|
||||
v = ctx.get(k, "")
|
||||
s = str(v if v is not None else "")
|
||||
if s != "":
|
||||
out[k] = s[:100]
|
||||
return out
|
||||
|
||||
|
||||
def _touch_visitor(site, vid, ctx, ip):
|
||||
env = {k: str(ctx.get(k, "") or "")[:n] for k, n in _FIELD_LEN.items()}
|
||||
# Task16 联调发现:键名映射缺口——SDK context 发 "timezone",模型字段是 timezone_name,
|
||||
# 同名推导的 _FIELD_LEN 取不到值,导致 timezone_name 恒空。
|
||||
env["timezone_name"] = str(ctx.get("timezone", "") or "")[:64]
|
||||
model = str(ctx.get("device_model", "") or "")[:64]
|
||||
v, created = Visitor.objects.get_or_create(
|
||||
site=site, visitor_id=vid, defaults={**env, "last_ip": ip})
|
||||
site=site, visitor_id=vid,
|
||||
defaults={**env, "device_model": model, "env_extra": _extended_env(ctx), "last_ip": ip})
|
||||
now = timezone.now()
|
||||
upd = {"last_seen_at": now} # 聚合字段一律 filter().update(),避免并发下全量 save() 用旧值覆盖赢家
|
||||
if created:
|
||||
@@ -103,8 +116,13 @@ def _touch_visitor(site, vid, ctx, ip):
|
||||
first_utm=_clean_utm(ctx.get("utm")),
|
||||
first_landing_path=(ctx.get("path") or "")[:500],
|
||||
last_active_date=now.date(), active_days=1)
|
||||
elif not Visitor.objects.filter(pk=v.pk, last_active_date=now.date()).exists():
|
||||
upd.update(last_active_date=now.date(), active_days=F("active_days") + 1)
|
||||
else:
|
||||
# 首触快照语义:非首批不覆盖 env_extra 等档案字段——唯一例外是 device_model
|
||||
# 为空且本批有值时补填(UA-CH hints 晚于首批发到的场景,只补写一次)
|
||||
if not v.device_model and model:
|
||||
upd["device_model"] = model
|
||||
if not Visitor.objects.filter(pk=v.pk, last_active_date=now.date()).exists():
|
||||
upd.update(last_active_date=now.date(), active_days=F("active_days") + 1)
|
||||
if ip:
|
||||
upd["last_ip"] = ip
|
||||
g = _geo.resolve(ip)
|
||||
|
||||
Reference in New Issue
Block a user