| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- using Cognex.VisionPro;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace TeamAAS_VP.Core
- {
- /// <summary>
- /// VisionPro 文件操作辅助类:提供安全保存、加载、备份、恢复与验证等功能。
- /// 该类旨在通过临时文件、备份与验证机制减少文件损坏风险并支持自动恢复。
- /// </summary>
- public static class VisionProFileHelper
- {
- #region 配置选项
- /// <summary>
- /// VisionPro 文件处理的配置项。
- /// - EnableBackup: 是否启用备份机制
- /// - KeepBackupFiles: 是否保留版本化备份文件(否则仅保留 .bak 临时备份)
- /// - MaxBackupVersions: 保留的最大版本备份数量(超过则删除最旧)
- /// - BackupDirectory: 存放版本备份的目录名(相对于原文件所在目录)
- /// - ValidateAfterSave: 保存后是否验证文件完整性
- /// </summary>
- public class VisionProOptions
- {
- public bool EnableBackup { get; set; } = true;
- public bool KeepBackupFiles { get; set; } = false;
- public int MaxBackupVersions { get; set; } = 3;
- public string BackupDirectory { get; set; } = "VisionProBackups";
- public bool ValidateAfterSave { get; set; } = true;
- }
- private static VisionProOptions _options = new VisionProOptions();
- /// <summary>
- /// 配置 VisionPro 文件处理选项。
- /// </summary>
- /// <param name="configure">用于配置 VisionProOptions 的委托(可为 null)</param>
- public static void Configure(Action<VisionProOptions> configure)
- {
- configure?.Invoke(_options);
- }
- #endregion
- #region 安全保存方法
- /// <summary>
- /// 安全保存 VisionPro 对象到指定文件路径。
- /// 实现要点:
- /// - 支持在保存前根据配置创建版本化备份或 .bak 备份
- /// - 先保存到临时文件,再进行原子替换(File.Replace 或 File.Move)
- /// - 可选的保存后验证,验证失败会抛出异常并尝试从备份恢复
- /// </summary>
- /// <param name="obj">要保存的 VisionPro 对象(不能为空)</param>
- /// <param name="filePath">目标文件路径(不能为空或空白)</param>
- /// <param name="backupEnabled">可选:覆盖全局备份配置</param>
- /// <returns>成功返回 true,如发生异常会抛出 IOException</returns>
- public static bool SaveObjectToFileSafe(object obj, string filePath, bool? backupEnabled = null)
- {
- if (obj == null)
- throw new ArgumentNullException(nameof(obj));
- if (string.IsNullOrWhiteSpace(filePath))
- throw new ArgumentException("文件路径不能为空", nameof(filePath));
- bool useBackup = backupEnabled ?? _options.EnableBackup;
- // 创建目录(若不存在)
- string directory = Path.GetDirectoryName(filePath);
- if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
- {
- Directory.CreateDirectory(directory);
- }
- // 生成临时文件路径用于先写入
- string tempFilePath = GetVisionProTempFilePath(filePath);
- try
- {
- // 如果启用备份且目标文件已存在,则创建备份(版本化或延续 .bak 机制)
- if (useBackup && File.Exists(filePath))
- {
- CreateVisionProBackup(filePath);
- }
- // 将对象保存到临时文件
- CogSerializer.SaveObjectToFile(obj, tempFilePath);
- // 可选:验证临时文件的完整性
- if (_options.ValidateAfterSave && !ValidateVisionProFile(tempFilePath))
- {
- throw new InvalidOperationException("保存的VisionPro文件验证失败");
- }
- // 原子性替换:如果目标存在使用 File.Replace(可生成 .bak),否则直接移动临时文件
- if (File.Exists(filePath))
- {
- string backupPath = filePath + ".bak";
- File.Replace(tempFilePath, filePath, backupPath, true);
- // 若配置不保留备份,删除 .bak 文件
- if (!_options.KeepBackupFiles && File.Exists(backupPath))
- {
- SafeDelete(backupPath);
- }
- }
- else
- {
- File.Move(tempFilePath, filePath);
- }
- Log($"成功保存VisionPro对象到: {filePath}");
- return true;
- }
- catch (Exception ex)
- {
- Log($"保存VisionPro对象失败: {ex.Message}");
- // 尝试从备份恢复(若启用了备份)
- if (useBackup)
- {
- Log("尝试从备份恢复...");
- TryRestoreVisionProFile(filePath);
- }
- throw new IOException($"保存VisionPro文件失败: {ex.Message}", ex);
- }
- finally
- {
- // 无论成功与否,尝试清理临时文件
- SafeDelete(tempFilePath);
- }
- }
- /// <summary>
- /// 安全加载 VisionPro 对象(带自动恢复机制)。
- /// 尝试加载主文件,若失败并且允许自动恢复则尝试从 .bak 或版本备份恢复。
- /// </summary>
- /// <typeparam name="T">目标对象类型</typeparam>
- /// <param name="filePath">文件路径</param>
- /// <param name="autoRecover">是否启用自动恢复</param>
- /// <returns>加载得到的对象(成功),否则抛出 FileNotFoundException</returns>
- public static T LoadObjectFromFileSafe<T>(string filePath, bool autoRecover = true) where T : class
- {
- if (string.IsNullOrWhiteSpace(filePath))
- throw new ArgumentException("文件路径不能为空", nameof(filePath));
- // 清理可能残留的临时文件
- CleanVisionProTempFile(filePath);
- // 记录最后一次异常用于抛出上下文
- Exception lastException = null;
- try
- {
- if (File.Exists(filePath))
- {
- var obj = CogSerializer.LoadObjectFromFile(filePath) as T;
- if (obj == null)
- throw new InvalidCastException($"文件 {filePath} 不是有效的 {typeof(T).Name} 类型");
- return obj;
- }
- }
- catch (Exception ex)
- {
- lastException = ex;
- Log($"加载主文件失败: {ex.Message}");
- if (autoRecover)
- {
- try
- {
- // 尝试从 .bak 或其他备份恢复并返回对象
- T recovered = TryRecoverVisionProFile<T>(filePath);
- if (recovered != null)
- return recovered;
- }
- catch (Exception recoveryEx)
- {
- Log($"恢复尝试失败: {recoveryEx.Message}");
- }
- }
- }
- // 如果主文件不存在且允许恢复,尝试查找版本化备份进行恢复
- if (autoRecover && !File.Exists(filePath))
- {
- T recovered = TryFindAndRecoverVisionProFile<T>(filePath);
- if (recovered != null)
- return recovered;
- }
- throw new FileNotFoundException($"VisionPro文件 {filePath} 不存在且无法恢复", lastException);
- }
- /// <summary>
- /// 简化版安全加载:遇到任何异常时返回提供的默认值(不抛出)。
- /// </summary>
- public static T SafeLoadObjectFromFile<T>(string filePath, T defaultValue = null) where T : class
- {
- try
- {
- return LoadObjectFromFileSafe<T>(filePath, true);
- }
- catch
- {
- return defaultValue;
- }
- }
- #endregion
- #region 恢复和验证方法
- /// <summary>
- /// 检查并修复单个 VisionPro 文件:
- /// - 检查并尝试从临时文件恢复
- /// - 检查 .bak 备份并在合理时恢复
- /// - 检查版本化备份并恢复最新的可用版本
- /// </summary>
- /// <param name="filePath">要检查的文件路径</param>
- /// <returns>如果执行了恢复操作返回 true,否则返回 false</returns>
- public static bool CheckAndRepairVisionProFile(string filePath)
- {
- try
- {
- // 检查临时文件并尝试恢复
- string tempPath = filePath + ".vp_tmp";
- if (File.Exists(tempPath))
- {
- TryRecoverFromVisionProTemp(tempPath);
- }
- // 检查 .bak 备份(File.Replace 可能产生)
- string backupPath = filePath + ".bak";
- if (File.Exists(backupPath))
- {
- // 验证备份文件并在主文件不存在或备份更新时恢复
- if (ValidateVisionProFile(backupPath))
- {
- if (!File.Exists(filePath) ||
- File.GetLastWriteTime(backupPath) > File.GetLastWriteTime(filePath))
- {
- File.Copy(backupPath, filePath, true);
- Log($"从备份恢复VisionPro文件: {filePath}");
- return true;
- }
- }
- }
- // 查找并恢复版本化备份(BackupDirectory 或其他模式)
- var latestBackup = FindLatestVisionProBackup(filePath);
- if (latestBackup != null)
- {
- File.Copy(latestBackup, filePath, true);
- Log($"从版本备份恢复VisionPro文件: {filePath}");
- return true;
- }
- return false;
- }
- catch (Exception ex)
- {
- Log($"修复VisionPro文件失败: {ex.Message}");
- return false;
- }
- }
- /// <summary>
- /// 强制从备份恢复(优先 .bak,然后版本化备份)。
- /// </summary>
- /// <param name="filePath">目标文件路径</param>
- /// <returns>恢复成功返回 true,否则 false</returns>
- public static bool ForceRecoverVisionProFile(string filePath)
- {
- try
- {
- // 优先使用 .bak 恢复(如存在且验证通过)
- string backupPath = filePath + ".bak";
- if (File.Exists(backupPath) && ValidateVisionProFile(backupPath))
- {
- File.Copy(backupPath, filePath, true);
- Log($"强制从备份恢复: {filePath}");
- return true;
- }
- // 否则查找版本化备份并恢复最新可用的
- var latestBackup = FindLatestVisionProBackup(filePath);
- if (latestBackup != null)
- {
- File.Copy(latestBackup, filePath, true);
- Log($"强制从版本备份恢复: {filePath}");
- return true;
- }
- return false;
- }
- catch (Exception ex)
- {
- Log($"强制恢复失败: {ex.Message}");
- return false;
- }
- }
- /// <summary>
- /// 尝试从已知备份恢复主文件(单参数便捷版本)。
- /// 会检查 .bak 与版本化备份并在找到有效备份时覆盖主文件。
- /// </summary>
- private static bool TryRestoreVisionProFile(string filePath)
- {
- try
- {
- string backupPath = filePath + ".bak";
- if (File.Exists(backupPath) && ValidateVisionProFile(backupPath))
- {
- File.Copy(backupPath, filePath, true);
- Log($"从备份恢复文件: {filePath}");
- return true;
- }
- // 若 .bak 不可用,尝试版本化备份
- var latestBackup = FindLatestVisionProBackup(filePath);
- if (latestBackup != null)
- {
- File.Copy(latestBackup, filePath, true);
- Log($"从版本备份恢复文件: {filePath}");
- return true;
- }
- return false;
- }
- catch (Exception ex)
- {
- Log($"恢复文件失败: {ex.Message}");
- return false;
- }
- }
- /// <summary>
- /// 批量检查并修复目录下所有 .vpp 文件。
- /// </summary>
- /// <param name="directoryPath">根目录路径</param>
- public static void CheckAndRepairDirectory(string directoryPath)
- {
- if (!Directory.Exists(directoryPath))
- return;
- // 查找所有 .vpp 文件(包含子目录)
- var vppFiles = Directory.GetFiles(directoryPath, "*.vpp", SearchOption.AllDirectories);
- Log($"开始检查和修复 {vppFiles.Length} 个VisionPro文件...");
- int repairedCount = 0;
- foreach (var file in vppFiles)
- {
- if (CheckAndRepairVisionProFile(file))
- {
- repairedCount++;
- }
- }
- Log($"完成检查和修复,共修复 {repairedCount} 个文件");
- }
- #endregion
- #region 私有实现方法
- /// <summary>
- /// 根据配置创建备份:
- /// - 若 KeepBackupFiles 为 true,则在指定 BackupDirectory 中创建带时间戳的版本备份并清理旧版本;
- /// - 若为 false,则不在此方法中处理 .bak(.bak 由 File.Replace 在保存时产生)。
- /// </summary>
- /// <param name="originalPath">原文件路径</param>
- private static void CreateVisionProBackup(string originalPath)
- {
- try
- {
- if (_options.KeepBackupFiles)
- {
- // 在原文件目录下创建 backupDir(如果不存在)
- string backupDir = Path.Combine(Path.GetDirectoryName(originalPath), _options.BackupDirectory);
- if (!Directory.Exists(backupDir))
- Directory.CreateDirectory(backupDir);
- string fileName = Path.GetFileNameWithoutExtension(originalPath);
- string extension = Path.GetExtension(originalPath);
- string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
- string backupName = $"{fileName}_{timestamp}{extension}";
- string backupPath = Path.Combine(backupDir, backupName);
- File.Copy(originalPath, backupPath, true);
- Log($"创建VisionPro版本备份: {backupPath}");
- // 清理超过 MaxBackupVersions 的旧备份
- CleanupOldVisionProBackups(backupDir, fileName);
- }
- }
- catch (Exception ex)
- {
- Log($"创建VisionPro备份失败: {ex.Message}");
- }
- }
- /// <summary>
- /// 生成一个用于临时保存的唯一文件路径(位于系统临时目录)。
- /// </summary>
- /// <param name="originalPath">与原文件关联的文件名(用于可读性)</param>
- /// <returns>临时文件路径</returns>
- private static string GetVisionProTempFilePath(string originalPath)
- {
- return originalPath + ".tmp";
- //string tempDir = Path.GetTempPath();
- //string safeName = Path.GetFileName(originalPath)
- // .Replace(" ", "_")
- // .Replace(":", "_");
- //return Path.Combine(tempDir, $"vp_{Guid.NewGuid():N}_{safeName}");
- }
- /// <summary>
- /// 尝试通过加载文件来验证 VisionPro 文件的完整性(加载失败视为无效)。
- /// </summary>
- /// <param name="filePath">要验证的文件路径</param>
- /// <returns>若能成功加载返回 true,否则 false</returns>
- private static bool ValidateVisionProFile(string filePath)
- {
- try
- {
- // 使用 Cognex 的序列化加载验证完整性
- var obj = CogSerializer.LoadObjectFromFile(filePath);
- return obj != null;
- }
- catch
- {
- return false;
- }
- }
- /// <summary>
- /// 尝试从 .bak 备份恢复并返回加载的对象(泛型)。
- /// 若恢复成功同时尝试将备份复制回主文件以保持一致性。
- /// </summary>
- private static T TryRecoverVisionProFile<T>(string originalPath) where T : class
- {
- string backupPath = originalPath + ".bak";
- if (File.Exists(backupPath))
- {
- try
- {
- var obj = CogSerializer.LoadObjectFromFile(backupPath) as T;
- if (obj != null)
- {
- // 当备份可用时,尝试恢复主文件
- TryRestoreVisionProFile(originalPath);
- Log($"从备份恢复VisionPro对象: {originalPath}");
- return obj;
- }
- }
- catch (Exception ex)
- {
- Log($"从备份恢复VisionPro对象失败: {ex.Message}");
- }
- }
- return default;
- }
- /// <summary>
- /// 查找版本化备份并尝试恢复为指定类型,成功时会将备份复制为主文件并返回对象。
- /// </summary>
- private static T TryFindAndRecoverVisionProFile<T>(string originalPath) where T : class
- {
- var backup = FindLatestVisionProBackup(originalPath);
- if (backup != null)
- {
- try
- {
- var obj = CogSerializer.LoadObjectFromFile(backup) as T;
- if (obj != null)
- {
- File.Copy(backup, originalPath, true);
- Log($"从版本备份恢复VisionPro对象: {originalPath}");
- return obj;
- }
- }
- catch (Exception ex)
- {
- Log($"从版本备份恢复VisionPro对象失败: {ex.Message}");
- }
- }
- return default;
- }
- /// <summary>
- /// 在备份目录和当前目录下查找与原文件对应的最新备份文件路径(按写入时间排序)。
- /// 支持多种命名模式以提高鲁棒性。
- /// </summary>
- private static string FindLatestVisionProBackup(string originalPath)
- {
- string directory = Path.GetDirectoryName(originalPath);
- string fileName = Path.GetFileNameWithoutExtension(originalPath);
- string extension = Path.GetExtension(originalPath);
- if (!Directory.Exists(directory))
- return null;
- // 优先检查专用备份目录
- string backupDir = Path.Combine(directory, _options.BackupDirectory);
- if (Directory.Exists(backupDir))
- {
- var backups = Directory.GetFiles(backupDir, $"{fileName}_*{extension}")
- .OrderByDescending(f => File.GetLastWriteTime(f))
- .ToList();
- if (backups.Any())
- return backups.First();
- }
- // 检查当前目录中常见的备份命名模式
- var backupPatterns = new[]
- {
- $"{fileName}.*.bak",
- $"{fileName}_*{extension}",
- $"{fileName}.backup.*"
- };
- foreach (var pattern in backupPatterns)
- {
- try
- {
- var backups = Directory.GetFiles(directory, pattern)
- .OrderByDescending(f => File.GetLastWriteTime(f))
- .ToList();
- if (backups.Any())
- return backups.First();
- }
- catch
- {
- continue;
- }
- }
- return null;
- }
- /// <summary>
- /// 恢复主文件的重载版本(从指定备份路径复制到原文件路径)。
- /// </summary>
- private static void TryRestoreVisionProFile(string backupPath, string originalPath)
- {
- try
- {
- File.Copy(backupPath, originalPath, true);
- Log($"恢复VisionPro主文件: {originalPath}");
- }
- catch (Exception ex)
- {
- Log($"恢复VisionPro主文件失败: {ex.Message}");
- }
- }
- /// <summary>
- /// 清理与指定原文件可能残留的各种临时文件(本进程或其他异常中断时产生)。
- /// 支持单个文件名与通配符模式(在系统临时目录下)。
- /// </summary>
- /// <param name="originalPath">原文件路径</param>
- private static void CleanVisionProTempFile(string originalPath)
- {
- // 可能的临时文件模式集合
- var tempPatterns = new[]
- {
- originalPath + ".vp_tmp",
- originalPath + ".tmp",
- Path.GetTempPath() + $"vp_*_{Path.GetFileName(originalPath)}"
- };
- foreach (var pattern in tempPatterns)
- {
- try
- {
- if (File.Exists(pattern))
- {
- File.Delete(pattern);
- Log($"清理VisionPro临时文件: {pattern}");
- }
- else if (pattern.Contains("*"))
- {
- // 通配符处理:删除系统临时目录下匹配的文件
- var files = Directory.GetFiles(Path.GetTempPath(), $"vp_*_{Path.GetFileName(originalPath)}");
- foreach (var file in files)
- {
- File.Delete(file);
- Log($"清理VisionPro临时文件: {file}");
- }
- }
- }
- catch (Exception ex)
- {
- Log($"清理VisionPro临时文件失败: {ex.Message}");
- }
- }
- }
- /// <summary>
- /// 尝试从临时文件恢复到主文件(当临时文件完整且主文件缺失或临时文件更可靠时)。
- /// 若临时文件无效则删除该临时文件。
- /// </summary>
- /// <param name="tempPath">临时文件路径(通常以 .vp_tmp 结尾)</param>
- private static void TryRecoverFromVisionProTemp(string tempPath)
- {
- try
- {
- string originalPath = tempPath.Replace(".vp_tmp", "");
- // 当主文件不存在或者临时文件验证通过时,尝试恢复
- if (!File.Exists(originalPath) || ValidateVisionProFile(tempPath))
- {
- if (ValidateVisionProFile(tempPath))
- {
- File.Move(tempPath, originalPath);
- Log($"从临时文件恢复VisionPro文件: {originalPath}");
- }
- else
- {
- File.Delete(tempPath);
- }
- }
- }
- catch
- {
- SafeDelete(tempPath);
- }
- }
- /// <summary>
- /// 清理备份目录中过多的旧版本,保留最新的 _options.MaxBackupVersions 个版本。
- /// </summary>
- private static void CleanupOldVisionProBackups(string backupDir, string baseName)
- {
- try
- {
- var files = Directory.GetFiles(backupDir)
- .Where(f => Path.GetFileName(f).StartsWith(baseName + "_"))
- .OrderByDescending(f => File.GetLastWriteTime(f))
- .ToList();
- for (int i = _options.MaxBackupVersions; i < files.Count; i++)
- {
- SafeDelete(files[i]);
- }
- }
- catch
- {
- // 忽略清理错误以避免影响主流程
- }
- }
- /// <summary>
- /// 安全删除文件(吞掉异常),用于清理临时/备份文件时保证不抛出影响主逻辑的异常。
- /// </summary>
- private static void SafeDelete(string filePath)
- {
- try
- {
- if (File.Exists(filePath))
- File.Delete(filePath);
- }
- catch
- {
- // 忽略删除错误
- }
- }
- /// <summary>
- /// 日志记录适配器(当前使用 LogHelper.WriteLogDebug)。
- /// 可根据项目替换为其它日志实现。
- /// </summary>
- /// <param name="message">日志消息</param>
- private static void Log(string message)
- {
- // 这里可以使用你喜欢的日志系统
- //Console.WriteLine($"[VisionProFileHelper] {DateTime.Now:HH:mm:ss} - {message}");
- LogHelper.WriteLogDebug($"[VisionProFileHelper] {DateTime.Now:HH:mm:ss} - {message}");
- // 或者使用:Debug.WriteLine(message);
- // 或者使用:Logger.Log(message);
- }
- #endregion
- }
- }
|