131 lines
4.4 KiB
Python
131 lines
4.4 KiB
Python
"""斗鱼活动商品的唯一解析与快照写入实现。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from datetime import UTC, datetime
|
|
from typing import Any
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..models import DouyuGoodsSnapshot
|
|
|
|
|
|
def _to_int(value: Any) -> int | None:
|
|
if value is None or value == "":
|
|
return None
|
|
try:
|
|
return int(float(value))
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _text(value: Any, *, max_length: int | None = None) -> str:
|
|
text = str(value or "").strip()
|
|
return text[:max_length] if max_length else text
|
|
|
|
|
|
def _json_safe(value: Any) -> Any:
|
|
if isinstance(value, dict):
|
|
return {str(key): _json_safe(item) for key, item in value.items()}
|
|
if isinstance(value, list):
|
|
return [_json_safe(item) for item in value]
|
|
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
return value
|
|
return str(value)
|
|
|
|
|
|
def _first_value(data: Mapping[str, Any], *keys: str) -> Any:
|
|
for key in keys:
|
|
if key in data and data.get(key) is not None:
|
|
return data.get(key)
|
|
return None
|
|
|
|
|
|
def serialize_goods(raw: Mapping[str, Any]) -> dict[str, Any]:
|
|
"""把 storehome 原始商品映射成前端稳定字段。"""
|
|
commodity_id = _text(
|
|
_first_value(raw, "commodityId", "commodity_id"), max_length=64
|
|
)
|
|
name = _text(_first_value(raw, "commodityName", "name"), max_length=256)
|
|
open_status = _to_int(_first_value(raw, "openStatus", "open_status"))
|
|
mark = _text(_first_value(raw, "mark"), max_length=32)
|
|
status_value = _first_value(raw, "status")
|
|
return {
|
|
"commodity_id": commodity_id,
|
|
"name": name,
|
|
"score": _to_int(_first_value(raw, "score")),
|
|
"status": str(status_value) if status_value is not None else "",
|
|
"open_status": open_status,
|
|
"start_time": _to_int(_first_value(raw, "startTime", "start_time")),
|
|
"end_time": _to_int(_first_value(raw, "endTime", "end_time")),
|
|
"mark": mark,
|
|
"count": _to_int(_first_value(raw, "count")),
|
|
"elite_limited": _to_int(_first_value(raw, "eliteLimited")),
|
|
"is_limited": bool(mark) or open_status == 0,
|
|
"raw": _json_safe(dict(raw)),
|
|
}
|
|
|
|
|
|
def serialize_live_goods(goods: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
return [serialize_goods(raw) for raw in goods if isinstance(raw, dict)]
|
|
|
|
|
|
def serialize_snapshot(row: DouyuGoodsSnapshot) -> dict[str, Any]:
|
|
raw = row.raw if isinstance(row.raw, dict) else {}
|
|
open_status = _to_int(_first_value(raw, "openStatus", "open_status"))
|
|
mark = _text(_first_value(raw, "mark"), max_length=32)
|
|
return {
|
|
"commodity_id": row.commodity_id,
|
|
"name": row.name,
|
|
"score": row.score,
|
|
"status": row.status,
|
|
"open_status": open_status,
|
|
"start_time": _to_int(_first_value(raw, "startTime", "start_time")),
|
|
"end_time": _to_int(_first_value(raw, "endTime", "end_time")),
|
|
"mark": mark,
|
|
"count": _to_int(_first_value(raw, "count")),
|
|
"elite_limited": _to_int(_first_value(raw, "eliteLimited")),
|
|
"is_limited": bool(mark) or open_status == 0,
|
|
"raw": raw,
|
|
}
|
|
|
|
|
|
def serialized_snapshots(db: Session) -> list[dict[str, Any]]:
|
|
rows = db.query(DouyuGoodsSnapshot).order_by(DouyuGoodsSnapshot.id.asc()).all()
|
|
return [serialize_snapshot(row) for row in rows]
|
|
|
|
|
|
def upsert_goods_snapshot(
|
|
db: Session,
|
|
goods: list[dict[str, Any]],
|
|
*,
|
|
commit: bool = True,
|
|
) -> None:
|
|
"""统一写入活动商品快照;零值字段必须保留。"""
|
|
now = datetime.now(UTC)
|
|
for raw in goods:
|
|
if not isinstance(raw, dict):
|
|
continue
|
|
commodity_id = _text(
|
|
_first_value(raw, "commodityId", "commodity_id"), max_length=64
|
|
)
|
|
if not commodity_id:
|
|
continue
|
|
row = (
|
|
db.query(DouyuGoodsSnapshot)
|
|
.filter(DouyuGoodsSnapshot.commodity_id == commodity_id)
|
|
.first()
|
|
)
|
|
if row is None:
|
|
row = DouyuGoodsSnapshot(commodity_id=commodity_id)
|
|
db.add(row)
|
|
row.name = _text(_first_value(raw, "commodityName", "name"), max_length=256)
|
|
row.score = _to_int(_first_value(raw, "score"))
|
|
status_value = _first_value(raw, "status")
|
|
row.status = str(status_value) if status_value is not None else ""
|
|
row.raw = _json_safe(raw)
|
|
row.updated_at = now
|
|
if commit:
|
|
db.commit()
|