Browse Source

fix: CSV文件被锁时自动写备份防止数据丢失 + 数据库初始化加重试自愈

zly 1 tháng trước cách đây
mục cha
commit
3d13a9b7e9
2 tập tin đã thay đổi với 123 bổ sung57 xóa
  1. 72 38
      TeamAAS-VM/Core/Management.cs
  2. 51 19
      TeamAAS-VM/Data/DatabaseInitializer.cs

+ 72 - 38
TeamAAS-VM/Core/Management.cs

@@ -1765,7 +1765,7 @@ namespace TeamAAS_VP.Core
                                         if (stageItem != null)
                                         {
                                             stage[0] = stageItem.Replace("stage=", "").Trim();
-                                            SendTaskMessage($"stage:{stage}", MessageLevel.Info);
+                                            SendTaskMessage($"stage:{stage[0]}", MessageLevel.Info);
                                         }
                                         string kbsnitem = items.FirstOrDefault(f => f.Contains("KB="));
                                         if (kbsnitem != null)
@@ -3413,7 +3413,7 @@ namespace TeamAAS_VP.Core
                                                     PROCESS_END_TIME = serverTimeResult.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffffff") + "+08:00",
                                                     SUPPLIER_NAME = stationConfig.DataInfo.SUPPLIER_NAME,
                                                     COMMODITY_TYPE = temp_COMMODITY_TYPE,
-                                                    REVISION = stationConfig.DataInfo.REVISION + "_" + stage,//
+                                                    REVISION = stationConfig.DataInfo.REVISION + "_" + stage[i],//
                                                     WO = wo[i],//
                                                     MFG_LOT = stationConfig.DataInfo.MFG_LOT,//
                                                     MFG_ASSY_LINE = stationConfig.DataInfo.MFG_ASSY_LINE,
@@ -3448,7 +3448,7 @@ namespace TeamAAS_VP.Core
                                                         PROCESS_END_TIME = serverTimeResult.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffffff") + "+08:00",
                                                         SUPPLIER_NAME = stationConfig.DataInfo.SUPPLIER_NAME,
                                                         COMMODITY_TYPE = stationConfig.DataInfo.COMMODITY_TYPE,
-                                                        REVISION = stationConfig.DataInfo.REVISION + "_" + stage,//
+                                                        REVISION = stationConfig.DataInfo.REVISION + "_" + stage[i],//
                                                         WO = wo[i],//
                                                         MFG_LOT = stationConfig.DataInfo.MFG_LOT,//
                                                         MFG_ASSY_LINE = stationConfig.DataInfo.MFG_ASSY_LINE,
@@ -3480,7 +3480,7 @@ namespace TeamAAS_VP.Core
                                                         PROCESS_END_TIME = serverTimeResult.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffffff") + "+08:00",
                                                         SUPPLIER_NAME = stationConfig.DataInfo.SUPPLIER_NAME,
                                                         COMMODITY_TYPE = stationConfig.DataInfo.COMMODITY_TYPE,
-                                                        REVISION = stationConfig.DataInfo.REVISION + "_" + stage,//
+                                                        REVISION = stationConfig.DataInfo.REVISION + "_" + stage[i],//
                                                         WO = wo[i],//
                                                         MFG_LOT = stationConfig.DataInfo.MFG_LOT,//
                                                         MFG_ASSY_LINE = stationConfig.DataInfo.MFG_ASSY_LINE,
@@ -3503,7 +3503,7 @@ namespace TeamAAS_VP.Core
                                             //{
                                             //    resultToMes.Clear();
                                             //}
-                                            bool recordResult = RecordStationResult(i, DFC, ProductMainSN, serverTimeResult.Now.Subtract(_LastMesCheckTime[i]).TotalSeconds, _LastMesCheckTime[i], stationConfig.SaveCsvPath);
+                                            bool recordResult = RecordStationResult(i, DFC, ProductMainSN, serverTimeResult.Now.Subtract(_LastMesCheckTime[i]).TotalSeconds, _LastMesCheckTime[i], stationConfig.SaveCsvPath, out string actualCsvPath);
                                             // bool recordResult1 = WriteProductLog( ProductMainSN, serverTimeResult.Now.Subtract(_LastMesCheckTime[i]).TotalSeconds, _LastMesCheckTime[i], stationConfig.SaveCsvPath, DFC);
 
                                             //keyData.Clear();
@@ -3528,20 +3528,34 @@ namespace TeamAAS_VP.Core
 
                                                             //filePath = $"D:\\Image\\Recored\\{dt.ToString("yyyy-MM")}\\{dt.ToString("MM-dd")}\\{currentProduct.Name}\\{ProductMainSN}";
                                                             filePath = $"{PhotoPath}\\Recored\\{dt.ToString("yyyy-MM")}\\{dt.ToString("MM-dd")}\\{currentProduct.Name}\\{ProductMainSN}";
-                                                            //string source_csv2 = $"{stationConfig.SaveCsvPath}\\{dt.ToString("yyyy-MM")}\\{i}_{stationConfig.DeviceName}_{dt.ToString("yyyy-MM-dd")}";
                                                             string csvfilename = $"{i}_{stationConfig.DeviceName}_{dt.ToString("yyyy-MM-dd")}";
-                                                            //复制csv
-                                                            string source_csv = $"{stationConfig.SaveCsvPath}\\{dt.ToString("yyyy-MM")}\\{i}_{stationConfig.DeviceName}_{dt.ToString("yyyy-MM-dd")}.csv";
-                                                            //string source_csv1 = $"{stationConfig.SaveCsvPath}\\{dt.ToString("yyyy-MM")}";
-                                                            string target_csv = Path.Combine(filePath, Path.GetFileName(source_csv));
-                                                            File.Copy(source_csv, target_csv, true);
+                                                            //复制csv — 使用 actualCsvPath(可能是备份路径)
+                                                            string target_csv = Path.Combine(filePath, Path.GetFileName(actualCsvPath));
+                                                            try
+                                                            {
+                                                                if (!Directory.Exists(filePath))
+                                                                    Directory.CreateDirectory(filePath);
+                                                                File.Copy(actualCsvPath, target_csv, true);
+                                                            }
+                                                            catch (Exception copyEx)
+                                                            {
+                                                                LogHelper.WriteLogError($"复制CSV到上传目录失败: src={actualCsvPath}, dst={target_csv}", copyEx);
+                                                            }
                                                             //复制log
-                                                            string source_log = $"..\\Log\\LogInfo\\{dt.ToString("yyyy-MM")}\\{dt.ToString("yyyy-MM-dd")}.txt";
-                                                            string target_log = Path.Combine(filePath, Path.GetFileName(source_log));
-                                                            File.Copy(source_log, target_log, true);
+                                                            try
+                                                            {
+                                                                string source_log = $"..\\Log\\LogInfo\\{dt.ToString("yyyy-MM")}\\{dt.ToString("yyyy-MM-dd")}.txt";
+                                                                string target_log = Path.Combine(filePath, Path.GetFileName(source_log));
+                                                                File.Copy(source_log, target_log, true);
+                                                            }
+                                                            catch (Exception copyLogEx)
+                                                            {
+                                                                LogHelper.WriteLogError($"复制Log到上传目录失败", copyLogEx);
+                                                            }
 
 
-                                                            CompressionImage(filePath, filename, device.Station, device.FixtureId);
+                                                            try { CompressionImage(filePath, filename, device.Station, device.FixtureId); }
+                                                            catch (Exception zipEx) { LogHelper.WriteLogError($"压缩图片文件夹失败", zipEx); }
                                                             //上传csv
                                                             bool isAfter20 = dt.Hour >= 20;
                                                             //判断是否是新的一天
@@ -3549,12 +3563,16 @@ namespace TeamAAS_VP.Core
                                                             //当天还没执行过压缩
                                                             if (isAfter20 && isNewDay && !_hasCompressedToday)
                                                             {
-                                                                // 执行压缩
-                                                                CompressionSingleCsv(source_csv, csvfilename, device.Station, device.FixtureId);
-                                                                _lastCompressDate = dt;
-                                                                _hasCompressedToday = true;
-                                                                LogHelper.WriteLogMes("csv上传数据成功");
-                                                                SendTaskMessage($"csv上传数据成功", MessageLevel.Debug);
+                                                                // 执行压缩 — 使用 actualCsvPath
+                                                                try
+                                                                {
+                                                                    CompressionSingleCsv(actualCsvPath, csvfilename, device.Station, device.FixtureId);
+                                                                    _lastCompressDate = dt;
+                                                                    _hasCompressedToday = true;
+                                                                    LogHelper.WriteLogMes("csv上传数据成功");
+                                                                    SendTaskMessage($"csv上传数据成功", MessageLevel.Debug);
+                                                                }
+                                                                catch (Exception csvZipEx) { LogHelper.WriteLogError($"压缩CSV文件失败", csvZipEx); }
                                                             }
                                                             // 跨天后重置当天执行标记
                                                             if (!isNewDay && _hasCompressedToday)
@@ -5682,8 +5700,9 @@ namespace TeamAAS_VP.Core
         /// <param name="savePath"></param>
         /// <param name="isFirstEnter"></param>
         /// <returns></returns>
-        private bool RecordStationResult(int stationIndex, IEnumerable<DataFormCtq> dfcs, string productMainSN, double ct, DateTime timestamp, string savePath, bool isFirstEnter = true)
+        private bool RecordStationResult(int stationIndex, IEnumerable<DataFormCtq> dfcs, string productMainSN, double ct, DateTime timestamp, string savePath, out string actualPath, bool isFirstEnter = true)
         {
+            actualPath = null;
             try
             {
                 //获取当前工位的配置
@@ -5734,26 +5753,41 @@ namespace TeamAAS_VP.Core
                     sb.Append($"{dfc.PPID},{dfc.PROCESS_NAME},{dfc.PROCESS_START_TIME},{dfc.CTQ_NAME},{dfc.CTQ_VALUE},{dfc.CTQ_LSL},{dfc.CTQ_USL},{dfc.PROCESS_END_TIME},{dfc.SUPPLIER_NAME},{dfc.COMMODITY_TYPE},{dfc.REVISION},{dfc.WO},{dfc.MFG_LOT},{dfc.MFG_ASSY_LINE},{dfc.PROCESS_OUTCOME},{dfc.PROCESS_MACHINE_ID},{dfc.CTQ_UNIT_OF_MEASURE},{dfc.ERROR_MESSAGE},{dfc.PD_ATTR_01},{dfc.PD_ATTR_02},{dfc.PD_ATTR_03},{dfc.PD_ATTR_04},{dfc.PD_ATTR_05},{dfc.PD_ATTR_06}");
                     sb.AppendLine();
                 }
-                // 使用 StreamWriter 以追加模式打开文件
-                StreamWriter sw = new StreamWriter(fullPath, true, Encoding.UTF8);
-                sw.Write(sb.ToString());
-                sw.Close();
-                sw.Dispose();
-                return true;
+
+                // 使用 StreamWriter 以追加模式写入,使用 using 确保异常时句柄不泄漏
+                try
+                {
+                    using (StreamWriter sw = new StreamWriter(fullPath, true, Encoding.UTF8))
+                    {
+                        sw.Write(sb.ToString());
+                    }
+                    actualPath = fullPath;
+                    return true;
+                }
+                catch (IOException ioEx) when (ioEx.Message.Contains("另一进程") || ioEx.Message.Contains("being used"))
+                {
+                    // 主文件被占用,生成备份文件写入
+                    string mainDir = Path.GetDirectoryName(fullPath);
+                    if (string.IsNullOrEmpty(mainDir))
+                        mainDir = savePath;
+                    string backupDir = Path.Combine(mainDir, "Backup");
+                    if (!Directory.Exists(backupDir))
+                        Directory.CreateDirectory(backupDir);
+                    string backupFileName = $"{stationIndex}_{stationConfig.DeviceName}_{DateTime.Now:yyyy-MM-dd_HHmmssfff}.csv";
+                    string backupPath = Path.Combine(backupDir, backupFileName);
+                    using (StreamWriter sw = new StreamWriter(backupPath, false, Encoding.UTF8))
+                    {
+                        sw.Write(sb.ToString());
+                    }
+                    actualPath = backupPath;
+                    SendTaskMessage($"主CSV文件被占用,已写入备份文件: {backupPath}", MessageLevel.Alarm);
+                    LogHelper.WriteLogInfo($"CSV主文件被占用,已写备份: 主={fullPath}, 备份={backupPath}");
+                    return true;
+                }
             }
             catch (Exception ex)
             {
-                if (!isFirstEnter)
-                {
-                    return false;
-                }
                 LogHelper.WriteLogError("记录其他工位结果时出错!", ex);
-                //我希望在这里生成一个临时文件来保存这次的记录,因为如果本地文件被打开时可能会导致无法写入,如果不保存的话可能会丢失这次的记录,所以我想在这里生成一个临时文件来保存这次的记录
-                //生成临时文件的路径,格式为savePath\yyyy-MM\工位编号_yyyy-MM-dd_temp\工位编号_yyyy-MM-dd_HH-mm-ss-fff_temp.csv
-                string datePath = timestamp.ToString("yyyy-MM") + "\\";
-                string tempDirPath = Path.Combine(savePath, datePath, $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd")}_temp");
-                string tempFileName = $"{stationIndex}_{timestamp.ToString("yyyy-MM-dd_HH-mm-ss-fff")}_temp.csv";
-                RecordStationResult(stationIndex, dfcs, productMainSN, ct, timestamp, tempFileName, false);
                 return false;
             }
         }

+ 51 - 19
TeamAAS-VM/Data/DatabaseInitializer.cs

@@ -21,31 +21,63 @@ namespace TeamAAS_VP.Data
 
         public void InitializeDatabase()
         {
-            try
+            const int maxRetry = 3;
+            Exception lastEx = null;
+
+            for (int attempt = 1; attempt <= maxRetry; attempt++)
             {
-                // 创建数据库(如果不存在)
-                _db.DbMaintenance.CreateDatabase();
-                // 创建表
-                _db.CodeFirst.InitTables(typeof(User), typeof(ProductionRecord), typeof(UserLoginRecord), typeof(AlarmRecord), typeof(LockResult), typeof(ProductWithMes));
+                try
+                {
+                    // 创建数据库(如果不存在)
+                    _db.DbMaintenance.CreateDatabase();
+                    // 创建表
+                    _db.CodeFirst.InitTables(typeof(User), typeof(ProductionRecord), typeof(UserLoginRecord), typeof(AlarmRecord), typeof(LockResult), typeof(ProductWithMes));
 
-                // 创建索引
-                CreateIndexes();
+                    // 创建索引
+                    CreateIndexes();
 
-                // 如果用户表为空,插入三个基础用户
-                var userCount = _db.Queryable<User>().Count();
-                if (userCount == 0)
+                    // 如果用户表为空,插入三个基础用户
+                    var userCount = _db.Queryable<User>().Count();
+                    if (userCount == 0)
+                    {
+                        var op = new User { UserName = "操作员", UserPassword = "", userPart = Enums.UserPart.Operator, CreateTime = DateTime.Now, IsRemember = false };
+                        var eng = new User { UserName = "工程师", UserPassword = "10086", userPart = Enums.UserPart.Engineer, CreateTime = DateTime.Now, IsRemember = false };
+                        var admin = new User { UserName = "管理员", UserPassword = "team123456", userPart = Enums.UserPart.Administrator, CreateTime = DateTime.Now, IsRemember = false };
+                        _db.Insertable(new List<User> { op, eng, admin }).ExecuteCommand();
+                    }
+                    return; // 成功,退出
+                }
+                catch (Exception ex)
                 {
-                    var op = new User { UserName = "操作员", UserPassword = "", userPart = Enums.UserPart.Operator, CreateTime = DateTime.Now, IsRemember = false };
-                    var eng = new User { UserName = "工程师", UserPassword = "10086", userPart = Enums.UserPart.Engineer, CreateTime = DateTime.Now, IsRemember = false };
-                    var admin = new User { UserName = "管理员", UserPassword = "team123456", userPart = Enums.UserPart.Administrator, CreateTime = DateTime.Now, IsRemember = false };
-                    _db.Insertable(new List<User> { op, eng, admin }).ExecuteCommand();
+                    lastEx = ex;
+                    bool isDbCorrupted = ex.Message.Contains("invalid") || ex.Message.Contains("corrupt")
+                                      || ex.Message.Contains("无效") || ex.Message.Contains("损坏");
+
+                    if (attempt < maxRetry)
+                    {
+                        if (isDbCorrupted)
+                        {
+                            // 数据库可能损坏,尝试删除重建
+                            try
+                            {
+                                string dbPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "AlignerDB.db");
+                                if (System.IO.File.Exists(dbPath))
+                                {
+                                    System.IO.File.Delete(dbPath);
+                                }
+                                LogHelper.WriteLogInfo($"数据库文件已删除,准备重建 (attempt {attempt})");
+                            }
+                            catch
+                            {
+                                // 删除失败,继续重试
+                            }
+                        }
+                        System.Threading.Thread.Sleep(500 * attempt); // 递增等待
+                    }
                 }
             }
-            catch (Exception ex)
-            {
-                // 记录日志
-                throw new Exception($"数据库初始化失败: {ex.Message}", ex);
-            }
+            // 所有重试均失败
+            throw new Exception($"数据库初始化失败(已重试{maxRetry}次): {lastEx?.Message}", lastEx);
         }
 
         public bool DatabaseExists()