# -*- coding: utf-8 -*- """ 数据库层:SQLite 统一存储结构化数据。 Schema 采用"运动-联赛-球队-球员-比赛"分层设计,天然支持扩展: - 新增 CBA → sports 表加一行 basketball,leagues 表加 CBA(复用全部代码) - 新增足球 → sports 表加 football,league 可加 英超/西甲,球员字段可扩展 - 新增人物角色 → persons.role 自由扩展(coach/agent/commentator/host/gm...) """ import sqlite3 import threading from config import DB_PATH _SCHEMA = """ CREATE TABLE IF NOT EXISTS sports ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT UNIQUE NOT NULL, -- 'basketball' / 'football' ... name TEXT NOT NULL, -- '篮球' / '足球' name_en TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS leagues ( id INTEGER PRIMARY KEY AUTOINCREMENT, sport_id INTEGER NOT NULL REFERENCES sports(id), code TEXT UNIQUE NOT NULL, -- 'NBA' / 'CBA' / 'EPL' name TEXT NOT NULL, -- '美国职业篮球联赛' name_en TEXT, country TEXT, season TEXT, -- '2025-26' created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS teams ( id INTEGER PRIMARY KEY AUTOINCREMENT, league_id INTEGER NOT NULL REFERENCES leagues(id), code TEXT, -- 'LAL' name TEXT NOT NULL, -- '洛杉矶湖人' name_en TEXT, -- 'Los Angeles Lakers' city TEXT, arena TEXT, founded INTEGER, champion_count INTEGER, head_coach TEXT, logo_url TEXT, intro TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS players ( id INTEGER PRIMARY KEY AUTOINCREMENT, team_id INTEGER REFERENCES teams(id), name TEXT NOT NULL, name_en TEXT, position TEXT, -- PG/SG/SF/PF/C number INTEGER, height_cm INTEGER, weight_kg INTEGER, birth_date TEXT, country TEXT, draft_year INTEGER, draft_pick INTEGER, -- 0 = 落选 salary_m REAL, -- 年薪(万美元) season_pts REAL, season_reb REAL, season_ast REAL, season_stl REAL, season_blk REAL, season_min REAL, -- 本赛季场均 career_pts REAL, career_reb REAL, career_ast REAL, -- 生涯场均 career_games INTEGER, career_steals INTEGER, career_blocks INTEGER, -- 生涯总数 awards TEXT, -- 主要荣誉 bio TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS games ( id INTEGER PRIMARY KEY AUTOINCREMENT, league_id INTEGER NOT NULL REFERENCES leagues(id), season TEXT, round_name TEXT, -- 常规赛/季后赛首轮/总决赛... game_time TEXT, -- '2026-06-05 09:00' status TEXT, -- scheduled / finished / live home_team_id INTEGER REFERENCES teams(id), away_team_id INTEGER REFERENCES teams(id), home_score INTEGER, away_score INTEGER, venue TEXT, broadcast TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS game_player_stats ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL REFERENCES games(id), player_id INTEGER NOT NULL REFERENCES players(id), team_id INTEGER NOT NULL REFERENCES teams(id), points INTEGER DEFAULT 0, rebounds INTEGER DEFAULT 0, assists INTEGER DEFAULT 0, steals INTEGER DEFAULT 0, blocks INTEGER DEFAULT 0, minutes INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS standings ( id INTEGER PRIMARY KEY AUTOINCREMENT, league_id INTEGER NOT NULL REFERENCES leagues(id), season TEXT, team_id INTEGER NOT NULL REFERENCES teams(id), wins INTEGER DEFAULT 0, losses INTEGER DEFAULT 0, win_pct REAL DEFAULT 0, conference TEXT, -- 西部/东部 rank INTEGER ); CREATE TABLE IF NOT EXISTS news ( id INTEGER PRIMARY KEY AUTOINCREMENT, sport_id INTEGER NOT NULL REFERENCES sports(id), league_id INTEGER REFERENCES leagues(id), team_id INTEGER REFERENCES teams(id), title TEXT NOT NULL, content TEXT NOT NULL, author TEXT, source TEXT, publish_time TEXT, tags TEXT, kind TEXT DEFAULT 'news', -- news / wiki(百科词条) created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS persons ( id INTEGER PRIMARY KEY AUTOINCREMENT, sport_id INTEGER NOT NULL REFERENCES sports(id), league_id INTEGER REFERENCES leagues(id), team_id INTEGER REFERENCES teams(id), name TEXT NOT NULL, name_en TEXT, role TEXT, -- coach/agent/commentator/host/gm/legend... role_cn TEXT, -- 教练/经纪人/评论员/主持人/总经理... title TEXT, bio TEXT, achievements TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE INDEX IF NOT EXISTS idx_players_team ON players(team_id); CREATE INDEX IF NOT EXISTS idx_players_name ON players(name); CREATE INDEX IF NOT EXISTS idx_games_time ON games(game_time); CREATE INDEX IF NOT EXISTS idx_games_team ON games(home_team_id); CREATE INDEX IF NOT EXISTS idx_news_time ON news(publish_time); CREATE INDEX IF NOT EXISTS idx_persons_role ON persons(role); """ _lock = threading.Lock() def get_conn(): conn = sqlite3.connect(DB_PATH, timeout=30) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA foreign_keys=ON") return conn def init_db(): with _lock: conn = get_conn() try: conn.executescript(_SCHEMA) conn.commit() finally: conn.close() def query(sql, args=()): conn = get_conn() try: cur = conn.execute(sql, args) return [dict(r) for r in cur.fetchall()] finally: conn.close() def query_one(sql, args=()): rows = query(sql, args) return rows[0] if rows else None def execute(sql, args=()): conn = get_conn() try: cur = conn.execute(sql, args) conn.commit() return cur.lastrowid finally: conn.close() def executemany(sql, rows): conn = get_conn() try: conn.executemany(sql, rows) conn.commit() finally: conn.close() def table_count(table): return query_one(f"SELECT COUNT(*) AS c FROM {table}")["c"] def fuzzy(expr): # SQL LIKE 模糊匹配(转义 % _) return expr.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")