From 2c205c68b163d8a04fa17229b9f3c49833c9d24d Mon Sep 17 00:00:00 2001 From: shangfangjian Date: Tue, 15 Sep 2026 01:26:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=A9=E4=BD=99?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ingest.py: 异常不再暴露客户端,改为通用错误消息+日志记录 - bzzoiro.py: 修复 normalize 重复调用,改为一次遍历缓存结果 - understat.py: 使用 Repository 替代原始 SQL --- src/api/routes/ingest.py | 13 ++++++++++--- src/data/bzzoiro.py | 25 ++++++++++++------------- src/data/understat.py | 26 +++++++++++++------------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/api/routes/ingest.py b/src/api/routes/ingest.py index 61f0186..4ea041e 100644 --- a/src/api/routes/ingest.py +++ b/src/api/routes/ingest.py @@ -1,6 +1,8 @@ """采集路由。""" from __future__ import annotations +import logging + from fastapi import APIRouter, HTTPException from src.api.schemas import IngestBzzoiroRequest, IngestResponse, IngestUnderstatRequest, IngestInjuriesRequest, IngestSimpleResponse @@ -8,6 +10,8 @@ from src.data.sources import get_source from src.data.injuries import ingest_injuries from src.db.unit_of_work import get_uow +logger = logging.getLogger(__name__) + router = APIRouter(prefix="/api/v1", tags=["ingest"]) @@ -26,7 +30,8 @@ async def ingest_bzzoiro_route(req: IngestBzzoiroRequest): ) return IngestResponse(**result) except Exception as e: - raise HTTPException(500, str(e)) + logger.exception("bzzoiro ingest failed") + raise HTTPException(500, "数据采集失败,请查看服务器日志") @router.post("/ingest/understat", response_model=IngestSimpleResponse) @@ -38,7 +43,8 @@ async def ingest_understat_route(req: IngestUnderstatRequest): result = await source.ingest(session, league=req.league, season=req.season) return IngestSimpleResponse(**result) except Exception as e: - raise HTTPException(500, str(e)) + logger.exception("understat ingest failed") + raise HTTPException(500, "xG 回填失败,请查看服务器日志") @router.post("/ingest/injuries", response_model=IngestSimpleResponse) @@ -49,4 +55,5 @@ async def ingest_injuries_route(req: IngestInjuriesRequest): result = await ingest_injuries(session, date=req.date) return IngestSimpleResponse(**result) except Exception as e: - raise HTTPException(500, str(e)) + logger.exception("injuries ingest failed") + raise HTTPException(500, "伤停采集失败,请查看服务器日志") diff --git a/src/data/bzzoiro.py b/src/data/bzzoiro.py index 2105563..0f25fe9 100644 --- a/src/data/bzzoiro.py +++ b/src/data/bzzoiro.py @@ -152,12 +152,21 @@ class BzzoiroSource: # === 批量优化: 预加载球队和已有比赛到内存 === team_name_to_id: dict[str, int] = {} existing_match_keys: set[tuple[int, int, str]] = set() + normalized_matches: list = [] # 缓存规范化结果,避免重复调用 + if raw_events: - # 预加载所有涉及的球队名 + # 一次遍历: 收集球队名 + 规范化 all_team_names = set() for raw in raw_events: nm = normalize_bzzoiro(raw, code) - if nm: + if nm is not None: + try: + nm.validate() + except Exception as e: + logger.debug("normalize skip: %s", e) + league_r["errors"].append(f"normalize: {e}") + continue + normalized_matches.append(nm) all_team_names.add(nm.home_team) all_team_names.add(nm.away_team) @@ -176,17 +185,7 @@ class BzzoiroSource: for row in rows: existing_match_keys.add((row.home_team_id, row.away_team_id, str(row.d))) - for raw in raw_events: - try: - nm = normalize_bzzoiro(raw, code) - if nm is None: - continue - nm.validate() - except Exception as e: - logger.debug("normalize skip: %s", e) - league_r["errors"].append(f"normalize: {e}") - continue - + for nm in normalized_matches: # 球队: 内存查找 + 按需创建 home_team_id = team_name_to_id.get(nm.home_team) if home_team_id is None: diff --git a/src/data/understat.py b/src/data/understat.py index 2ca5be1..7d04574 100644 --- a/src/data/understat.py +++ b/src/data/understat.py @@ -86,6 +86,8 @@ class UnderstatSource: 注意: 本方法不控制事务(commit/rollback),由调用方通过 UnitOfWork 控制。 """ + from src.db.repositories import LeagueRepository, MatchRepository, TeamRepository + result = {"updated": 0, "skipped": 0, "unmatched": 0, "errors": []} try: @@ -95,9 +97,13 @@ class UnderstatSource: result["errors"].append(f"fetch failed: {e}") return result + # 使用 Repository + league_repo = LeagueRepository(db) + team_repo = TeamRepository(db) + match_repo = MatchRepository(db) + # 查联赛 - stmt = select(League).where(League.code == league) - league_obj = (await db.execute(stmt)).scalar_one_or_none() + league_obj = await league_repo.get_by_code(league) if league_obj is None: result["errors"].append(f"league {league} not found in DB") return result @@ -114,22 +120,16 @@ class UnderstatSource: result["errors"].append(f"normalize: {e}") continue - # 匹配已有 Match(天级) - 直接查询 - home_team = (await db.execute(select(Team).where(Team.name == nm.home_team))).scalar_one_or_none() - away_team = (await db.execute(select(Team).where(Team.name == nm.away_team))).scalar_one_or_none() + # 匹配已有 Match(天级) - 使用 Repository + home_team = await team_repo.get_by_name(nm.home_team) + away_team = await team_repo.get_by_name(nm.away_team) if home_team is None or away_team is None: result["unmatched"] += 1 continue - date_only = nm.date.date() if hasattr(nm.date, "date") else nm.date - stmt = ( - select(Match) - .where(Match.league_id == league_obj.id) - .where(Match.home_team_id == home_team.id) - .where(Match.away_team_id == away_team.id) - .where(func.date(Match.match_date) == date_only) + existing = await match_repo.find_by_teams_and_date( + league_obj.id, home_team.id, away_team.id, nm.date ) - existing = (await db.execute(stmt)).scalar_one_or_none() if existing is None: result["unmatched"] += 1 continue