Files
server-core/scripts/recover_from_binlog.py
T

164 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())