finally要早于try块里面的return 0执行

博客指出在程序执行中,finally块要早于try块里的return 0执行,这是关于程序执行顺序的重要信息,在编程中需加以注意。
finally要早于try块里面的return 0执行
import os import sqlite3 import shutil import tempfile import time from datetime import datetime, timedelta from pathlib import Path import mysql.connector from ping3 import ping # ======================== # 配置区域 # ======================== # 全局最小时间门槛(不会早于这个时间同步) MIN_TIME = '2025-09-01 00:00:00' # 分播机配置:IP -> (LineNumber, 数据库UNC路径或映射路径) SORTERS = { '192.168.110.56': (1, r'Y:\sorting.db'), # 示例:也可写成 r'\\192.168.110.56\share\sorting.db' '192.168.110.60': (9, r'Z:\sorting.db'), } # MySQL 配置 MYSQL_CONFIG = { 'host': 'localhost', 'user': 'admin', 'password': 'gapinyc', 'database': 'board_db', 'auth_plugin': 'mysql_native_password', 'charset': 'utf8mb4' } # 要同步的表名列表 TABLES_TO_SYNC = [ 'AppSortingTasks', # 最顶层(无外部依赖) 'AppOperationLogs', # 可能依赖 Tasks,也可能独立 'AppReportRecords', # 通常独立或依赖 Tasks 'AppSortingTaskItems', # 依赖 Tasks 'AppSortingTaskImages', # 通常依赖 Items 或 Tasks 'AppSortingTaskSkus', # 依赖 Items 'AppSortingTaskBoxes' # 可能依赖 Items 或 Tasks ] # ======================== # 工具函数 # ======================== def is_valid_sqlite_db(file_path): """检查文件是否为有效的 SQLite 数据库""" try: with open(file_path, 'rb') as f: header = f.read(16) return header == b'SQLite format 3\x00' except Exception as e: print(f"[CHECK ERROR] 无法读取文件头 {file_path}: {e}") return False def is_file_accessible(path_str): """检测共享路径上的数据库文件是否存在且可读""" try: db_path = Path(path_str) if not db_path.exists(): print(f"[PATH ERROR] 文件不存在: {db_path}") return False if not db_path.is_file(): print(f"[PATH ERROR] 路径不是文件: {db_path}") return False if db_path.stat().st_size == 0: print(f"[PATH ERROR] 文件大小为0字节: {db_path}") return False return True except Exception as e: print(f"[PATH ERROR] 访问路径异常 {path_str}: {e}") return False def connect_sqlite_remote_safely(db_path): """ 安全连接远程 SQLite 数据库:先复制到本地临时文件再打开 返回: (connection, temp_db_path) 元组 或 (None, None) """ temp_db = None try: # 创建临时文件 with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp: temp_db = tmp.name print(f"📥 正在复制数据库: {db_path} → {temp_db}") shutil.copy2(db_path, temp_db) # 验证副本是否为有效 SQLite 文件 if not is_valid_sqlite_db(temp_db): print(f"[ERROR] 复制后的数据库无效: {temp_db}") os.unlink(temp_db) return None, None # 尝试连接 conn = sqlite3.connect(f'file:{temp_db}?mode=ro', uri=True, timeout=10.0) conn.row_factory = sqlite3.Row # 支持按列名访问 print(f"✅ 成功加载数据库副本: {temp_db}") return conn, temp_db # 成功则返回 conn 和路径 except Exception as e: print(f"[ERROR] 复制或连接数据库失败 {db_path}: {e}") # 清理临时文件(如果存在) if temp_db and os.path.exists(temp_db): try: os.unlink(temp_db) print(f"🗑️ 清理失败的临时数据库: {temp_db}") except Exception as clean_e: print(f"[WARN] 无法删除临时文件 {temp_db}: {clean_e}") return None, None def close_sqlite_connection(conn, temp_db_path): """安全关闭 SQLite 连接并删除临时文件""" if conn: try: conn.close() print("🔒 SQLite 连接已关闭") except Exception as e: print(f"[WARN] 关闭 SQLite 连接时出错: {e}") # 删除临时文件 if temp_db_path and os.path.exists(temp_db_path): try: os.unlink(temp_db_path) print(f"🗑️ 已删除临时数据库: {temp_db_path}") except Exception as e: print(f"[WARN] 无法删除临时文件 {temp_db_path}: {e}") def sync_table_from_sqlite_to_mysql(sorter_ip, line_number, sqlite_path, mysql_conn, table_name): sqlite_conn = None temp_db = None mysql_cursor = None try: if not is_file_accessible(sqlite_path): print(f"[FAIL] 源文件不可访问: {sqlite_path}") return False # ✅ 新式解包返回值 sqlite_conn, temp_db = connect_sqlite_remote_safely(sqlite_path) if not sqlite_conn: print(f"[FAIL] 无法安全打开数据库: {sqlite_path}") return False mysql_cursor = mysql_conn.cursor() target_table = f"AI_{table_name}" twelve_hours_ago = (datetime.now() - timedelta(hours=12)).strftime('%Y-%m-%d %H:%M:%S') filter_time = max(MIN_TIME, twelve_hours_ago) print(f" 🕒 同步时间窗口起点: {filter_time}") query = f"SELECT * FROM `{table_name}` WHERE creationtime >= ?" try: rows = list(sqlite_conn.execute(query, (filter_time,))) except sqlite3.Error as e: print(f"[ERROR] 查询表 {table_name} 出错: {e}") return False if not rows: print(f" ⚠️ 表 {table_name} 无符合 creationtime >= '{filter_time}' 的数据") return True print(f" 📥 从 {table_name} 读取到 {len(rows)} 条数据") # === DEBUG: 打印原始 keys === raw_keys = rows[0].keys() print(f"🔍 原始列名列表: {raw_keys}") # 检查是否有非法列名 for k in raw_keys: if not isinstance(k, str): print(f"[WARN] 非字符串列名: {k} (类型: {type(k)})") if k.strip() == 'I': print(f"[CRITICAL] 发现危险列名: '{k}' ← 这会导致 SQL 错误!") if not k.isidentifier(): print(f"[WARN] 非法标识符列名: '{k}'") columns = [k for k in raw_keys] all_columns_with_lineno = columns + ['LineNumber'] placeholders = ', '.join(['%s'] * len(all_columns_with_lineno)) insert_columns = ', '.join([f"`{col}`" for col in all_columns_with_lineno]) update_assignments = ', '.join([f"`{col}` = VALUES(`{col}`)" for col in all_columns_with_lineno]) insert_sql = f""" INSERT INTO {target_table} ({insert_columns}) VALUES ({placeholders}) ON DUPLICATE KEY UPDATE {update_assignments} """ data_to_insert = [tuple(list(row) + [line_number]) for row in rows] mysql_cursor.execute("START TRANSACTION") three_hours_ago = (datetime.now() - timedelta(hours=3)).strftime('%Y-%m-%d %H:%M:%S') delete_sql = f"DELETE FROM {target_table} WHERE creationtime >= %s" mysql_cursor.execute(delete_sql, (three_hours_ago,)) deleted_count = mysql_cursor.rowcount if deleted_count > 0: print(f" [TRUNCATE] 删除 {target_table} 中 {deleted_count} 条旧记录 (>= {three_hours_ago})") mysql_cursor.executemany(insert_sql, data_to_insert) inserted_count = mysql_cursor.rowcount mysql_conn.commit() print(f" ✅ 成功向 {target_table} 插入/更新 {inserted_count} 条数据") return True except Exception as e: print(f"[ERROR] 同步表 {table_name} 时发生异常: {e}") if mysql_conn: mysql_conn.rollback() return False finally: close_sqlite_connection(sqlite_conn, temp_db) # ✅ 显式传递 temp_db if mysql_cursor: mysql_cursor.close() def check_sorter_online(ip): """检查 IP 是否可达""" try: delay = ping(ip, timeout=2) return delay is not None except: return False def sync_single_sorter(ip, line_number, sqlite_path): """同步一台分播机的所有表""" print(f"\n🔍 开始检查分播机 Line{line_number} ({ip})...") if not check_sorter_online(ip): print(f"🔴 分播机 Line{line_number} ({ip}) 网络不通") return False print(f"✅ 分播机 Line{line_number} ({ip}) 网络通畅") if not is_file_accessible(sqlite_path): print(f"❌ 分播机数据库文件不可访问: {sqlite_path}") return False print(f"✅ 分播机数据库文件正常: {sqlite_path}") success = True mysql_conn = None try: mysql_conn = mysql.connector.connect(**MYSQL_CONFIG) if not mysql_conn.is_connected(): print("[FAIL] 无法连接到MySQL") return False for table_name in TABLES_TO_SYNC: result = sync_table_from_sqlite_to_mysql(ip, line_number, sqlite_path, mysql_conn, table_name) if not result: print(f"⚠️ 同步表 {table_name} 失败") success = False except Exception as e: print(f"[ERROR] MySQL 操作异常: {e}") success = False finally: if mysql_conn and mysql_conn.is_connected(): mysql_conn.close() if success: print(f"🟢 分播机 Line{line_number} 所有表同步成功") else: print(f"🔴 分播机 Line{line_number} 部分表同步失败") return success def main(): """主函数:遍历所有分播机进行同步""" print("🚀 开始执行分播机数据同步任务...") overall_success = True for ip, (line_number, db_path) in SORTERS.items(): try: result = sync_single_sorter(ip, line_number, db_path) if not result: overall_success = False except Exception as e: print(f"[CRITICAL] 同步分播机 {ip} 时发生未捕获异常: {e}") overall_success = False print("\n" + ("✅" if overall_success else "❌") + " 全部同步任务完成") if __name__ == "__main__": main() 改造以上代码,解决处理每个表都要重新复制和加载数据库,请提供完整代码
最新发布
09-19
package com.Ls.erp.util; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.*; import java.nio.file.Files; import java.nio.file.attribute.BasicFileAttributes; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Component public class DatabaseBackupScheduler { // 配置参数(根据实际情况修改) private static final String DB_HOST = "localhost"; private static final String DB_PORT = "3306"; private static final String DB_USER = "root"; private static final String DB_PASSWORD = "000000"; private static final String DATABASE_NAME = "erp"; private static final String BACKUP_DIR = "D://2023-02-23ERP测试//2025-08-01//数据库备份查询"; // 替换为实际备份目录 private static final int RETENTION_DAYS = 15; // 保留最近15天的备份 private static final String MYSQLDUMP_PATH = getMysqldumpPath(); @PostConstruct public void init() { startScheduledBackup(); } private static String getMysqldumpPath() { String os = System.getProperty("os.name").toLowerCase(); if (os.contains("win")) { return "C:\\Program Files\\MySQL\\MySQL Server 5.7\\bin\\mysqldump.exe"; } else { return "/usr/bin/mysqldump"; } } private static void startScheduledBackup() { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); // 计算首次执行时间 Calendar now = Calendar.getInstance(); Calendar nextBackupTime = calculateNextBackupTime(now); // 计算初始延迟(毫秒) long initialDelay = nextBackupTime.getTimeInMillis() - now.getTimeInMillis(); // 每天执行一次(24小时) long period = 24 * 60 * 60 * 1000; scheduler.scheduleAtFixedRate( DatabaseBackupScheduler::performBackup, initialDelay, period, TimeUnit.MILLISECONDS ); System.out.println("数据库备份任务已启动。首次备份时间: " + nextBackupTime.getTime()); System.out.println("备份目录: " + BACKUP_DIR); System.out.println("备份保留天数: " + RETENTION_DAYS + "天"); } private static Calendar calculateNextBackupTime(Calendar currentTime) { Calendar nextTime = (Calendar) currentTime.clone(); // 设置备份时间点(12:00 和 17:30) List<Calendar> backupTimes = new ArrayList<>(); Calendar noon = Calendar.getInstance(); noon.set(Calendar.HOUR_OF_DAY, 12); noon.set(Calendar.MINUTE, 0); noon.set(Calendar.SECOND, 0); Calendar evening = Calendar.getInstance(); evening.set(Calendar.HOUR_OF_DAY, 14); evening.set(Calendar.MINUTE, 25); evening.set(Calendar.SECOND, 0); backupTimes.add(noon); backupTimes.add(evening); // 找到下一个最近的备份时间 for (Calendar time : backupTimes) { if (time.after(currentTime)) { return time; } } // 如果今天所有时间点都已过,返回明天的第一个时间点 Calendar firstTime = backupTimes.get(0); firstTime.add(Calendar.DAY_OF_MONTH, 1); return firstTime; } private static void performBackup() { try { // 创建备份目录(如果不存在) File backupDir = new File(BACKUP_DIR); if (!backupDir.exists() && !backupDir.mkdirs()) { System.err.println("无法创建备份目录: " + BACKUP_DIR); return; } // 生成带时间戳的文件名 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String fileName = String.format("%s_%s.sql", DATABASE_NAME, timeStamp); String backupPath = BACKUP_DIR + File.separator + fileName; // 构建备份命令 List<String> command = new ArrayList<>(); command.add(MYSQLDUMP_PATH); command.add("--host=" + DB_HOST); command.add("--port=" + DB_PORT); command.add("--user=" + DB_USER); command.add("--password=" + DB_PASSWORD); command.add("--databases"); command.add(DATABASE_NAME); command.add("--result-file=" + backupPath); command.add("--skip-lock-tables"); command.add("--single-transaction"); command.add("--routines"); // 备份存储过程和函数 command.add("--events"); // 备份事件 command.add("--triggers"); // 备份触发器 // 添加以下关键参数 command.add("--default-character-set=utf8mb4"); command.add("--hex-blob"); // 处理二进制字段 command.add("--skip-comments"); // 避免注释导致问题 command.add("--no-tablespaces"); // 避免权限问题 command.add("--verbose"); // 显示更多信息 // 执行备份命令 ProcessBuilder processBuilder = new ProcessBuilder(command); // 关键修复:使用标准输出重定向 File backupFile = new File(backupPath); processBuilder.redirectOutput(backupFile); // 正确重定向输出到文件 processBuilder.redirectErrorStream(true); // 合并错误流到输出流 System.out.println("[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "] 开始备份数据库: " + DATABASE_NAME); Process process = processBuilder.start(); // 读取命令输出 try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { System.out.println("[MYSQLDUMP] " + line); } } int exitCode = process.waitFor(); if (exitCode == 0) { long fileSizeKB = backupFile.length() / 1024; System.out.println("✅ 数据库备份成功: " + backupPath); System.out.println("📁 备份文件大小: " + fileSizeKB + " KB"); // 验证备份文件 if (isBackupValid(backupFile)) { System.out.println("🔍 备份文件验证通过"); } else { System.err.println("⚠️ 备份文件验证失败,可能存在数据丢失"); } // 清理旧备份 cleanOldBackups(); } else { System.err.println("❌ 数据库备份失败,退出码: " + exitCode); // 记录详细错误信息 try (BufferedReader errorReader = new BufferedReader( new InputStreamReader(process.getErrorStream()))) { String errorLine; while ((errorLine = errorReader.readLine()) != null) { System.err.println("[MYSQLDUMP ERROR] " + errorLine); } } catch (IOException e) { System.err.println("无法读取错误流: " + e.getMessage()); } } } catch (IOException | InterruptedException e) { System.err.println("⚠️ 备份过程中发生错误: " + e.getMessage()); e.printStackTrace(); } } // 新增方法:验证备份文件完整性 private static boolean isBackupValid(File backupFile) { try (BufferedReader reader = new BufferedReader(new FileReader(backupFile))) { boolean hasCreateTable = false; boolean hasInsert = false; boolean hasEndMarker = false; String line; int lineCount = 0; while ((line = reader.readLine()) != null) { lineCount++; // 检查关键结构 if (line.startsWith("CREATE TABLE")) hasCreateTable = true; if (line.startsWith("INSERT INTO")) hasInsert = true; if (line.contains("Dump completed")) hasEndMarker = true; // 检查错误信息 if (line.contains("ERROR") || line.contains("failed")) { System.err.println("备份文件中发现错误: " + line); return false; } } // 基本完整性检查 if (lineCount < 100) { System.err.println("备份文件行数不足: " + lineCount); return false; } if (!hasCreateTable) { System.err.println("缺少CREATE TABLE语句"); return false; } if (!hasInsert) { System.err.println("缺少INSERT INTO语句"); return false; } return hasEndMarker; } catch (IOException e) { System.err.println("验证备份文件时出错: " + e.getMessage()); return false; } } private static void cleanOldBackups() { File backupDir = new File(BACKUP_DIR); if (!backupDir.exists() || !backupDir.isDirectory()) { return; } // 计算截止时间(当前时间减去保留天数) long cutoff = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(RETENTION_DAYS); // 获取所有备份文件 File[] backupFiles = backupDir.listFiles((dir, name) -> name.startsWith(DATABASE_NAME + "_") && name.endsWith(".sql") ); if (backupFiles == null || backupFiles.length == 0) { return; } int deletedCount = 0; for (File backup : backupFiles) { try { // 获取文件的创建时间 BasicFileAttributes attrs = Files.readAttributes( backup.toPath(), BasicFileAttributes.class ); // 如果文件创建时间早于截止时间,则删除 if (attrs.creationTime().toMillis() < cutoff) { if (backup.delete()) { System.out.println("♻️ 已清理旧备份: " + backup.getName()); deletedCount++; } else { System.err.println("⚠️ 无法删除旧备份: " + backup.getName()); } } } catch (IOException e) { System.err.println("⚠️ 检查备份文件时出错: " + backup.getName() + " - " + e.getMessage()); } } System.out.println("🧹 清理完成: 共删除 " + deletedCount + " 个旧备份"); } } 我可以在Java里面去做这个事情吗,我需要的mysql、里面的erp库进行数据与结构的保存,我使用这个好像可以保存erp里面的时间但是存在问题,保存的erp.sql,文件大小是16000KB我手动去mysql里面对erp进行保存,sql文件大小是28430KB还有我去运行erp里面的数据,全部是报错的,我代码存在什么问题吗
08-02
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值