"""斗鱼活动商品的唯一解析与快照写入实现。""" 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()