Files
server-core/scripts/recover_from_binlog.py
T

164 lines
6.0 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""从 binlog 解码文件中恢复被误删的数据(DELETE → INSERT"""
import re
import asyncio
import asyncmy
BINLOG_FILE = "/tmp/binlog_decode.sql"
# 只恢复这些表(非政策/活动/任务/岗位/社区内容)
RECOVER_TABLES = {
"opc_services", "service_orders", "service_views", "service_referrals",
"service_providers", "service_favorites", "service_inquiries", "service_reviews",
"market_items", "market_categories", "market_purchase_orders", "market_purchases",
"finance_records", "settlement_logs", "invoices", "contracts", "payment_bindings",
"compute_recharge_orders", "compute_recharges", "compute_usage_records",
"compute_balance_allocations", "investment_intents", "investor_preferences",
"courses", "training_courses", "training_chapters", "training_lessons",
"training_enrollments", "training_course_enrollments", "training_lesson_progress",
"training_certificates", "skill_tests", "certifications", "certification_types",
"opc_certifications",
"credit_ledger", "credit_scores", "credit_dimensions", "credit_levels",
"credit_rules", "badges", "user_badges",
"messages", "notifications", "announcements", "survey_logs", "plan_logs",
"invite_records", "ratings",
"park_admissions", "company_apply_requests", "company_claim_requests",
}
def parse_binlog(path):
"""解析解码后的 binlog,返回 {table: [row_dict, ...]}"""
results = {}
current_table = None
current_row = {}
in_where = False
with open(path, "r", encoding="utf-8", errors="replace") as f:
for line in f:
line = line.strip()
# 匹配 DELETE FROM
m = re.match(r"### DELETE FROM `opc`\.`(\w+)`", line)
if m:
# 保存上一行
if current_table and current_row:
results.setdefault(current_table, []).append(current_row)
current_table = m.group(1)
current_row = {}
in_where = False
continue
if line == "### WHERE":
in_where = True
continue
if in_where and line.startswith("### @"):
# 解析 @N=value
m2 = re.match(r"### @(\d+)=(.*)", line)
if m2:
idx = int(m2.group(1))
val = m2.group(2)
# 处理 NULL
if val == "NULL":
val = None
else:
# 去掉引号(简单处理)
if val.startswith("'") and val.endswith("'"):
val = val[1:-1]
# 处理转义
val = val.replace("\\'", "'").replace("\\\\", "\\")
current_row[idx] = val
continue
# 遇到其他行,结束当前事件
if current_table and current_row and not line.startswith("###"):
results.setdefault(current_table, []).append(current_row)
current_table = None
current_row = {}
in_where = False
# 最后一个
if current_table and current_row:
results.setdefault(current_table, []).append(current_row)
return results
async def get_columns(conn, table):
"""获取表的列名顺序"""
async with conn.cursor() as cur:
await cur.execute(
"""SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='opc' AND TABLE_NAME=%s ORDER BY ORDINAL_POSITION""",
(table,))
return [r[0] for r in await cur.fetchall()]
async def main():
print("解析 binlog...")
data = parse_binlog(BINLOG_FILE)
print(f"解析到 {len(data)} 个表的删除事件")
conn = await asyncmy.connect(
host="47.108.226.213", port=8091,
user="root", password="sjnxhyjashaywiuwhaja", database="opc")
total_recovered = 0
for table, rows in data.items():
if table not in RECOVER_TABLES:
continue
if not rows:
continue
cols = await get_columns(conn, table)
if not cols:
print(f"{table}: 无法获取列名")
continue
# 过滤掉已经存在的记录(通过主键判断)
# 先查现有主键
pk_col = cols[0] # 假设第一列是主键
existing_ids = set()
try:
async with conn.cursor() as cur:
await cur.execute(f"SELECT `{pk_col}` FROM `{table}`")
existing_ids = {str(r[0]) for r in await cur.fetchall()}
except Exception:
pass
inserted = 0
skipped = 0
async with conn.cursor() as cur:
for row in rows:
# 按列索引映射
values = []
for i, col in enumerate(cols, 1):
val = row.get(i)
values.append(val)
# 跳过已存在的
pk_val = str(values[0]) if values[0] is not None else None
if pk_val and pk_val in existing_ids:
skipped += 1
continue
placeholders = ",".join(["%s"] * len(cols))
col_names = ",".join([f"`{c}`" for c in cols])
try:
await cur.execute(
f"INSERT INTO `{table}` ({col_names}) VALUES ({placeholders})",
values)
inserted += 1
if pk_val:
existing_ids.add(pk_val)
except Exception as e:
print(f"{table}: {e}")
skipped += 1
await conn.commit()
total_recovered += inserted
print(f"{table}: 恢复 {inserted} 条(跳过 {skipped} 条已存在)")
await conn.ensure_closed()
print(f"\n总计恢复: {total_recovered}")
if __name__ == "__main__":
asyncio.run(main())