fix(server): v2入口加固——page_id孤代理/NaN区间值拒绝,共享ISO解析
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,8 @@ page_id),会话按事件级 session_id 解析(v1 信封不再是唯一依
|
||||
(v2 附注:stay_ms 重算对"并发同会话双批"同样存在同类窗口——后提交者的 SELECT 可能
|
||||
看不到前者未提交事件而少算,与 TOCTOU 同级、只影响 stay_ms 单字段。)
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone as dt_tz
|
||||
from datetime import timedelta
|
||||
import math
|
||||
|
||||
from django.db import transaction, IntegrityError
|
||||
from django.db.models import F, Q
|
||||
@@ -32,31 +33,25 @@ from django.utils import timezone
|
||||
|
||||
from tracker import constants as C
|
||||
from tracker.models import SourceClassification, Visitor, VisitSession, Event
|
||||
from tracker.services.validation import validate_event
|
||||
from tracker.services.validation import validate_event, parse_iso_dt
|
||||
from tracker.services.attribution import classify
|
||||
from tracker.services.geo import resolver as _geo # 模块级单例(勿实例化,11MB/份)
|
||||
|
||||
_FIELD_LEN = {"device_type": 16, "os": 64, "browser": 64, "language": 32, "screen": 32}
|
||||
_CLASS_FIELDS = ("channel", "platform", "variant", "search_term",
|
||||
"evidence_kind", "resolution", "detail")
|
||||
|
||||
|
||||
def _occ_dt(v):
|
||||
"""v2 事件 occurred_at 允许 ISO 字符串(SDK 线格式;collect 视图已预转 datetime,
|
||||
直连调用方可能传原文)。解析失败抛 ValueError/TypeError——调用方放行原值,
|
||||
由 validate_event 统一拒 invalid_occurred_at。naive 视为 UTC(与 collect._iso 同口径)。"""
|
||||
if isinstance(v, str):
|
||||
d = datetime.fromisoformat(v.replace("Z", "+00:00"))
|
||||
return d if d.tzinfo else d.replace(tzinfo=dt_tz.utc)
|
||||
return v
|
||||
_BIGINT_MAX = 2 ** 63 - 1 # BigIntegerField 8 字节量纲上限——超范围值炸列,按 None 降级
|
||||
|
||||
|
||||
def _ms(v):
|
||||
"""v2 区间字段宽松 int 化:bool/非数值 → None(脏数据防御——recompute 会过滤 None 区间,
|
||||
该事件不参与并集但不拒批)。"""
|
||||
"""v2 区间字段宽松 int 化,任何脏值一律 None(recompute 过滤 None 区间,不拒批):
|
||||
bool/非数值;NaN/Infinity(json.loads 默认收这两个字面量,int() 抛 ValueError/
|
||||
OverflowError);超 BigIntegerField 量纲的有限值(DB 写入炸列)。"""
|
||||
if isinstance(v, bool) or not isinstance(v, (int, float)):
|
||||
return None
|
||||
return int(v)
|
||||
if not math.isfinite(v):
|
||||
return None
|
||||
return int(v) if -_BIGINT_MAX <= v <= _BIGINT_MAX else None
|
||||
|
||||
|
||||
def _time_quality(now, occ) -> str:
|
||||
@@ -284,7 +279,7 @@ def ingest_batch(site, payload: dict, ip: str | None = None, ua: str = "") -> di
|
||||
for i, ev in enumerate(events):
|
||||
if v2 and isinstance(ev, dict) and isinstance(ev.get("occurred_at"), str):
|
||||
try: # v2 事件 occurred_at 允许 ISO 字符串(SDK 线格式);解析失败放行原值,
|
||||
ev["occurred_at"] = _occ_dt(ev["occurred_at"]) # validate_event 统一拒 invalid_occurred_at
|
||||
ev["occurred_at"] = parse_iso_dt(ev["occurred_at"]) # validate_event 统一拒 invalid_occurred_at
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
reason = validate_event(ev, v2=v2, batch_site_id=site.site_id,
|
||||
|
||||
Reference in New Issue
Block a user