209 lines
6.4 KiB
Python
209 lines
6.4 KiB
Python
# -*- 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("_", "\\_")
|