Files
henan-backend/olympus-py/olympus_db.py
T
2026-09-21 19:21:35 +08:00

230 lines
9.1 KiB
Python

"""
Olympus 数据库操作模块
用法:
python olympus_db.py # 建表 + 写入饰品桶种子数据
连接参数可用环境变量覆盖: OLY_DB_HOST / OLY_DB_PORT / OLY_DB_USER / OLY_DB_PASSWORD / OLY_DB_NAME
默认对齐本地开发库 (127.0.0.1:23306 changzhou,见 docker/compose/docker-compose.yml)。
"""
import json
import os
import time
from contextlib import contextmanager
from pathlib import Path
import pymysql
from olympus.config import ORNAMENTS
DB_CONFIG = {
'host': os.environ.get('OLY_DB_HOST', '127.0.0.1'),
'port': int(os.environ.get('OLY_DB_PORT', '23306')),
'user': os.environ.get('OLY_DB_USER', 'changzhou'),
'password': os.environ.get('OLY_DB_PASSWORD', 'iTSJSPPZM3LSGAPC'),
'database': os.environ.get('OLY_DB_NAME', 'changzhou'),
'charset': 'utf8mb4',
'local_infile': True,
}
SCHEMA_FILE = Path(__file__).resolve().parent / 'schema.sql'
@contextmanager
def get_conn():
conn = pymysql.connect(**DB_CONFIG)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
# ============================================================
# 建表(执行 schema.sql 中所有语句,注释行自动跳过)
# ============================================================
def _load_schema_statements():
text = SCHEMA_FILE.read_text(encoding='utf-8')
lines = [ln for ln in text.splitlines() if not ln.strip().startswith('--')]
statements = [s.strip() for s in '\n'.join(lines).split(';') if s.strip()]
return statements
def create_tables():
"""建表 + 写入饰品桶种子数据(幂等,可重复执行)"""
stmts = _load_schema_statements()
with get_conn() as conn:
cur = conn.cursor()
for sql in stmts:
cur.execute(sql)
print(f'OK: executed {len(stmts)} statements (tables + ornament seed).')
def verify_tables():
"""校验所有 olympus 表已存在并输出行数"""
with get_conn() as conn:
cur = conn.cursor()
cur.execute("SHOW TABLES LIKE 'game_olympus_%'")
tables = [row[0] for row in cur.fetchall()]
for t in sorted(tables):
cur.execute(f"SELECT COUNT(*) FROM {t}")
print(f'{t}: {cur.fetchone()[0]} rows')
return tables
# ============================================================
# Ornament 操作
# ============================================================
def insert_ornaments():
"""按 config.ORNAMENTS 覆盖写入饰品桶(与 schema.sql 种子一致)"""
with get_conn() as conn:
cur = conn.cursor()
for o in ORNAMENTS:
cur.execute("""
INSERT INTO game_olympus_ornament (id, game_type, expect_mul, name, range_low, range_high)
VALUES (%s,'OLYMPUS',%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE expect_mul=VALUES(expect_mul), name=VALUES(name),
range_low=VALUES(range_low), range_high=VALUES(range_high)
""", (o['id'], o['expect_mul'], o['name'], o['range_low'], o['range_high']))
# ============================================================
# Box 操作
# ============================================================
def insert_box(box):
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""
INSERT INTO game_olympus_box (box_id, game_type, box_type, bet, name, price,
total_ornaments, rtp, sort, status)
VALUES (%s,'OLYMPUS',%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE box_type=VALUES(box_type),
bet=VALUES(bet), name=VALUES(name),
price=VALUES(price), total_ornaments=VALUES(total_ornaments),
rtp=VALUES(rtp)
""", (box['box_id'], box['box_type'], box['bet'], box['name'],
box['price'], box['total_ornaments'], box['rtp'],
box.get('sort', 0), box.get('status', '1')))
def query_boxes():
with get_conn() as conn:
cur = conn.cursor(pymysql.cursors.DictCursor)
cur.execute("SELECT * FROM game_olympus_box WHERE status='1' ORDER BY box_id")
return cur.fetchall()
# ============================================================
# Round / Step 批量写入(单连接清空+写入,死锁自动重试)
# ============================================================
def get_max_round_id():
with get_conn() as conn:
cur = conn.cursor()
cur.execute("SELECT COALESCE(MAX(round_id), 0) FROM game_olympus_round")
return cur.fetchone()[0]
def get_max_step_id():
with get_conn() as conn:
cur = conn.cursor()
cur.execute("SELECT COALESCE(MAX(id), 0) FROM game_olympus_step")
return cur.fetchone()[0]
def _j(v, default='[]'):
if v is None:
return default
return v if isinstance(v, str) else json.dumps(v, ensure_ascii=False)
def write_box_distribution(box_id, counts, audience='common'):
"""单连接:按身份清空并写入饰品桶分布(只动该 audience,不影响其他身份)"""
for attempt in range(5):
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute(
"DELETE FROM game_olympus_box_ornament WHERE box_id=%s AND audience=%s",
(box_id, audience))
if counts:
sql = ("INSERT INTO game_olympus_box_ornament"
" (box_id, ornament_id, audience, count, used)"
" VALUES (%s,%s,%s,%s,0)")
rows = [(box_id, oid, audience, cnt)
for oid, cnt in sorted(counts.items()) if cnt > 0]
cur.executemany(sql, rows)
return
except Exception as e:
if 'Deadlock' in str(e) and attempt < 4:
time.sleep(0.5 * (attempt + 1))
continue
raise
def write_box_distributions(box_id, counts_by_audience):
"""一次写入多套身份分布。counts_by_audience: {audience: {ornament_id: count}}"""
for audience, counts in counts_by_audience.items():
write_box_distribution(box_id, counts, audience)
def write_rounds_and_steps(box_id, rounds_data, steps_data):
"""单连接:清空旧回合+步骤,批量写入新数据"""
for attempt in range(5):
try:
with get_conn() as conn:
cur = conn.cursor()
cur.execute("""DELETE FROM game_olympus_step WHERE round_id IN
(SELECT round_id FROM game_olympus_round WHERE box_id=%s)""", (box_id,))
cur.execute("DELETE FROM game_olympus_round WHERE box_id=%s", (box_id,))
if rounds_data:
sql_r = """INSERT INTO game_olympus_round
(round_id, box_id, ornament_id, actual_mul, free_spin_count, total_cascades)
VALUES (%s,%s,%s,%s,%s,%s)"""
rows_r = [(rd['round_id'], rd['box_id'], rd['ornament_id'],
rd['actual_mul'], rd['free_spin_count'], rd['total_cascades'])
for rd in rounds_data]
cur.executemany(sql_r, rows_r)
if steps_data:
sql_s = """INSERT INTO game_olympus_step
(id, round_id, free_spin_id, cascade_seq, grid_state,
multiplier_detail, symbol_links, accumulated_symbols, spin_winners,
removed_positions, drop_data, fill_data,
scatter_trigger, retrigger_info, base_score, win_amount,
accumulator_before, accumulator_after, spin_win, win_capped,
bigwin_level, is_final)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"""
rows_s = [(s['id'], s['round_id'], s['free_spin_id'], s['cascade_seq'],
_j(s['grid_state']),
_j(s.get('multiplier_detail')),
_j(s.get('symbol_links')),
_j(s.get('accumulated_symbols'), default='{}'),
_j(s.get('spin_winners')),
_j(s.get('removed_positions')),
_j(s.get('drop_data')),
_j(s.get('fill_data')),
_j(s.get('scatter_trigger'), default=None),
_j(s.get('retrigger_info'), default=None),
s['base_score'], s['win_amount'],
s['accumulator_before'], s['accumulator_after'],
s['spin_win'], s.get('win_capped', 0),
s.get('bigwin_level'), s['is_final'])
for s in steps_data]
cur.executemany(sql_s, rows_s)
return
except Exception as e:
if 'Deadlock' in str(e) and attempt < 4:
time.sleep(0.5 * (attempt + 1))
continue
raise
if __name__ == '__main__':
create_tables()
verify_tables()