Code Review 第二轮修复:前端 HTTP 统一 + 数据采集可靠性
F1: 统一前端 HTTP 客户端 - 新增 frontend/src/lib/http.ts(共享客户端,统一超时/错误/401 处理) - Matches.tsx 的列表/分页/进行中/预测请求改用 http 客户端 - 公开站与 Admin 行为一致 D2: events 采集按联赛分批提交 - _run_bzzoiro 改为 per-league 独立事务,避免超长事务 - 单联赛失败不影响其他联赛 F3: 仪表盘真实计数 - admin_stats 补充 matches/stats/standings 真实聚合 - Dashboard 展示比赛总数/已完赛/统计行/积分榜 F4: LLM 连通性测试不依赖 match_id=1 - 新增 POST /admin/llm/ping 端点(只发一次 chat,不依赖比赛) - testLLMConnection 优先调用 ping,失败回退旧方式 F5: 导航改用 React Router Link - App.tsx 中 <a href> 全部替换为 <Link to> D5: 删除 away_possession 死代码 - bzzoiro.py 中移除计算与注释 D1: 管线基础设施表文档化 - RawEvent/IngestFailure/DataQualityCheck/DataLineage 添加「预留未启用」注释 Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
co-authored by
new-provider/LongCat-2.0 <
parent
675e176603
commit
b6e367a640
@@ -3,6 +3,7 @@ import TeamSideTag from '../components/TeamSideTag'
|
||||
import { fetchMatchDetail, fetchMatchContext } from '../admin/dal'
|
||||
import type { MatchDetailOut, MatchContextOut, MatchRecentPrediction, TeamRecentMatch } from '../admin/types'
|
||||
import type { MatchStatsDetail } from '../admin/types'
|
||||
import { http } from '../lib/http'
|
||||
|
||||
interface Match {
|
||||
id: number
|
||||
@@ -238,11 +239,8 @@ export default function Matches() {
|
||||
setError(null)
|
||||
try {
|
||||
const params = new URLSearchParams({ league, status, limit: '50' })
|
||||
const res = await fetch(`/api/v1/matches?${params}`)
|
||||
const data = await http.get<{ items: Match[]; next_cursor: string | null }>(`/matches?${params}`)
|
||||
if (seq !== loadSeq.current) return // 已有更新的请求,丢弃本次结果
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
if (seq !== loadSeq.current) return
|
||||
setMatches(data.items)
|
||||
setNextCursor(data.next_cursor ?? null)
|
||||
} catch (e) {
|
||||
@@ -260,10 +258,7 @@ export default function Matches() {
|
||||
setLoadingMore(true)
|
||||
try {
|
||||
const params = new URLSearchParams({ league, status, limit: '50', cursor: nextCursor })
|
||||
const res = await fetch(`/api/v1/matches?${params}`)
|
||||
if (seq !== loadSeq.current) return
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
const data = await http.get<{ items: Match[]; next_cursor: string | null }>(`/matches?${params}`)
|
||||
if (seq !== loadSeq.current) return
|
||||
setMatches(prev => [...prev, ...data.items])
|
||||
setNextCursor(data.next_cursor ?? null)
|
||||
@@ -279,9 +274,7 @@ export default function Matches() {
|
||||
const loadLive = useCallback(async () => {
|
||||
try {
|
||||
const params = new URLSearchParams({ league, status: 'in_play', limit: '20' })
|
||||
const res = await fetch(`/api/v1/matches?${params}`)
|
||||
if (!res.ok) return
|
||||
const data = await res.json()
|
||||
const data = await http.get<{ items: Match[] }>(`/matches?${params}`)
|
||||
setLiveMatches(data.items ?? [])
|
||||
} catch {
|
||||
/* ignore:进行中非核心功能 */
|
||||
@@ -340,19 +333,11 @@ export default function Matches() {
|
||||
predictAbort.current = controller
|
||||
const timer = setTimeout(() => controller.abort(), 300_000)
|
||||
try {
|
||||
const res = await fetch('/api/v1/predict', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ match_id: m.id, mode: 'multi' }),
|
||||
const data = await http.post<Prediction>('/predict', { match_id: m.id, mode: 'multi' }, {
|
||||
timeoutMs: 300_000,
|
||||
signal: controller.signal,
|
||||
})
|
||||
if (seq !== predictSeq.current) return
|
||||
if (!res.ok) {
|
||||
const t = await res.text()
|
||||
throw new Error(`HTTP ${res.status}: ${t}`)
|
||||
}
|
||||
const data = await res.json()
|
||||
if (seq !== predictSeq.current) return
|
||||
setPrediction(data)
|
||||
} catch (e) {
|
||||
if (seq !== predictSeq.current) return
|
||||
|
||||
Reference in New Issue
Block a user