using System; using System.Collections.Concurrent; using System.IO; using SimpleCore.Library; namespace StandardScene.CommonTools { /// /// 按文件路径串行化读写,保证高并发下对同一文件的更新原子、不覆盖其他记录。 /// 调用方在委托中完成“读当前内容 → 修改 → 返回新内容”,由本类负责加锁与写回。 /// public static class AtomicFileUpdateHelper { private static readonly ConcurrentDictionary PathLocks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// 对指定路径执行原子更新:在持锁下读取当前文件内容,调用 update 得到新内容并写回。 /// 不修改其他记录时,应在 update 中仅变更需要变更的条目后返回完整内容。 /// /// 文件完整路径 /// 接收当前文件文本(若文件不存在则为 null),返回要写回的新文本;返回 null 表示不写入 public static void ExecuteAtomicUpdate(string filePath, Func update) { if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath)); if (update == null) throw new ArgumentNullException(nameof(update)); var lockObj = PathLocks.GetOrAdd(filePath, _ => new object()); lock (lockObj) { string current = null; try { if (File.Exists(filePath)) current = File.ReadAllText(filePath); } catch (Exception ex) { Diagnosis.Log($"AtomicFileUpdate read error: {filePath}, {ex.Message}", "AtomicFileUpdate", true); throw; } string newContent = update(current); if (newContent == null) return; var dir = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); try { var tempPath = filePath + ".tmp"; File.WriteAllText(tempPath, newContent); if (File.Exists(filePath)) File.Replace(tempPath, filePath, null); else File.Move(tempPath, filePath); } catch (Exception ex) { Diagnosis.Log($"AtomicFileUpdate write error: {filePath}, {ex.Message}", "AtomicFileUpdate", true); throw; } } } } }