#!/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())