为了确保路径 A 下的文件和文件夹的结构与路径 B 下保持一致,可以编写一个 C# 程序来递归比较路径 A 和路径 B 中的文件夹和文件,并根据变化来更新路径 B 中的内容。具体来说,代码逻辑需要做到以下几点:
- 同步新创建的文件和文件夹:如果 A 中有 B 中没有的文件或文件夹,需要将它们复制到 B 中。
- 同步重命名的文件和文件夹:如果 A 中的文件或文件夹被重命名,B 中相应的文件或文件夹也需要进行重命名。
- 同步删除的文件和文件夹:如果 A 中的文件或文件夹被删除,B 中相应的文件或文件夹也需要删除。
以下是实现该功能的一个代码示例:
C# 示例代码
using log4net;
using log4net.Config;
using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Xml.Linq;
public static class DirectorySync
{
// 创建一个静态日志实例
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// 获取当前日期前三天的日期
private static DateTime GetThreeDaysAgoDate()
{
return DateTime.Now.AddDays(-3);
}
// 计算文件的哈希值(MD5)
public static string GetFileHash(string filePath)
{
using (var md5 = MD5.Create())
{
using (var stream = File.OpenRead(filePath))
{
return BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
}
}
}
public static void SyncDirectories(string sourceDir, string targetDir)
{
// 1. 确保源目录存在
if (!Directory.Exists(sourceDir))
{
Log4Helper.Print(1, $"源目录 {sourceDir} 不存在");
return;
}
// 2. 确保目标目录存在
if (!Directory.Exists(targetDir))
{
Directory.CreateDirectory(targetDir);
Log4Helper.Print(1, $"创建目标 {targetDir} 目录成功");
}
// 3. 同步子目录
foreach (string sourceSubDir in Directory.GetDirectories(sourceDir))
{
string dirName = Path.GetFileName(sourceSubDir);
string targetSubDir = Path.Combine(targetDir, dirName);
if (!Directory.Exists(targetSubDir))
{
Directory.CreateDirectory(targetSubDir);
Log4Helper.Print(1, $"复制 {targetSubDir} 目录成功");
}
// 递归同步子目录
SyncDirectories(sourceSubDir, targetSubDir);
}
// 4. 删除目标目录中多余的子目录
foreach (string targetSubDir in Directory.GetDirectories(targetDir))
{
string dirName = Path.GetFileName(targetSubDir);
string correspondingSourceSubDir = Path.Combine(sourceDir, dirName);
if (!Directory.Exists(correspondingSourceSubDir))
{
Directory.Delete(targetSubDir, true);
Log4Helper.Print(1, $"删除目标目录 {targetSubDir} 多余的目录成功");
}
}
// 5. 同步文件
foreach (string sourceFilePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(sourceFilePath);
string targetFilePath = Path.Combine(targetDir, fileName);
// 获取文件的最后修改时间
FileInfo sourceFileInfo = new FileInfo(sourceFilePath);
DateTime fileLastWriteTime = sourceFileInfo.LastWriteTime;
// 如果文件在最近三天内修改过
if (fileLastWriteTime >= GetThreeDaysAgoDate())
{
// 如果文件不存在,或者文件的哈希值不同,则进行同步
if (!File.Exists(targetFilePath) || GetFileHash(sourceFilePath) != GetFileHash(targetFilePath))
{
File.Copy(sourceFilePath, targetFilePath, true);
Log4Helper.Print(1, $"复制 {sourceFilePath} 到 {targetFilePath} 目录成功");
}
}
}
// 6. 删除目标目录中多余的文件
foreach (string targetFilePath in Directory.GetFiles(targetDir))
{
string fileName = Path.GetFileName(targetFilePath);
string correspondingSourceFilePath = Path.Combine(sourceDir, fileName);
if (!File.Exists(correspondingSourceFilePath))
{
File.Delete(targetFilePath);
Log4Helper.Print(1, $"删除目标文件 {targetFilePath} 成功");
}
}
}
static void Main()
{
// 初始化 log4net 配置
XmlConfigurator.Configure(new FileInfo("log4net.config"));
// 输出日志
Log4Helper.Print(1, "应用程序启动");
// 加载配置文件
XDocument config = XDocument.Load("config.config");
// 读取源路径和目标路径
string sourcePath = config.Root.Element("Paths").Element("SourcePath").Value;
string targetPath = config.Root.Element("Paths").Element("TargetPath").Value;
// 获取当前日期
string currentDate = DateTime.Now.ToString("yyyyMMdd"); // 格式为 20241104
// 拼接源目录
string sourceDir = Path.Combine(sourcePath, currentDate);
string targetDir = Path.Combine(targetPath, currentDate);
// 调用同步函数
SyncDirectories(sourceDir, targetDir);
}
}
public static class Log4Helper
{
//private Log4Helper() { }
#region 引用路径
/// <summary>
/// 一般信息
/// </summary>
private static readonly ILog logInfo = LogManager.GetLogger("LogInfo");
/// <summary>
/// 一般警告
/// </summary>
private static readonly ILog logWarn = LogManager.GetLogger("LogWarn");
/// <summary>
/// 一般错误
/// </summary>
private static readonly ILog logError = LogManager.GetLogger("LogError");
/// <summary>
/// 致命错误
/// </summary>
private static readonly ILog logFatalError = LogManager.GetLogger("LogFatalError");
/// <summary>
/// 调试日志
/// </summary>
private static readonly ILog logDebug = LogManager.GetLogger("LogDebug");
/// <summary>
/// 运行日志
/// </summary>
private static readonly ILog logRunOperate = LogManager.GetLogger("LogRunOperate");
/// <summary>
/// 数据日志
/// </summary>
private static readonly ILog logData = LogManager.GetLogger("LogData");
#endregion
#region 一般信息记录
private static void LogInfo(string str)
{
if (logInfo.IsInfoEnabled)
{
logInfo.Info(str); // 记录一般信息
}
}
#endregion
#region 一般警告
private static void LogWarn(string str)
{
if (logWarn.IsWarnEnabled)
{
logWarn.Warn(str); // 记录一般警告
}
}
#endregion
#region 一般错误
private static void LogError(string str)
{
if (logError.IsErrorEnabled)
{
logError.Error(str); // 记录一般错误
}
}
#endregion
#region 致命错误
private static void LogFatalError(string message, Exception exception)
{
if (logFatalError.IsFatalEnabled)
{
logFatalError.Fatal(message, exception);
}
}
private static void LogFatalError(string message)
{
if (logFatalError.IsFatalEnabled)
{
logFatalError.Fatal(message);
}
}
#endregion
#region 调试日志
private static void LogDebug(string str)
{
if (logDebug.IsDebugEnabled)
{
logDebug.Debug(str); // 调试日志
}
}
#endregion
#region 运行日志
private static void LogRunLog(string str)
{
if (logRunOperate.IsInfoEnabled)
{
logRunOperate.Info(str); // 记录运行日志
}
}
#endregion
#region 数据日志
private static void LogDataLog(string str)
{
if (logData.IsInfoEnabled)
{
logData.Info(str); // 记录数据日志
}
}
#endregion
public static void Print(int level, string message, Exception exception = null)
{
switch (level)
{
case 1:
Log4Helper.LogInfo(message);
break;
case 2:
Log4Helper.LogWarn(message);
break;
case 3:
Log4Helper.LogError(message);
break;
case 4:
if (exception == null)
{
Log4Helper.LogFatalError(message);
}
else
{
Log4Helper.LogFatalError(message, exception);
}
break;
case 5:
Log4Helper.LogDebug(message);
break;
case 6:
Log4Helper.LogRunLog(message);
break;
case 7:
Log4Helper.LogDataLog(message);
break;
default:
Console.WriteLine("未知的日志等级: " + level);
break;
}
}
}
代码说明
-
确保目标目录存在:
- 如果目标目录 B 不存在,使用
Directory.CreateDirectory(targetDir)
创建它。
- 如果目标目录 B 不存在,使用
-
同步文件夹:
- 遍历源路径 A 的所有子文件夹。
- 如果目标路径 B 中不存在对应的子文件夹,则创建它。
- 使用递归方式确保所有层级的子文件夹都得到同步。
-
删除目标路径中多余的文件夹:
- 遍历目标路径 B 的所有子文件夹。
- 如果这些子文件夹在源路径 A 中不存在,则删除它们。
-
同步文件:
- 遍历源路径 A 中的所有文件。
- 如果目标路径 B 中不存在相应的文件,则将源文件复制过去。
- 如果文件已经存在,但源文件的
LastWriteTime
比目标文件的新,说明源文件有更新,则替换目标文件。
-
删除多余的文件:
- 遍历目标路径 B 中的所有文件。
- 如果这些文件在源路径 A 中不存在,则删除它们。
优化和扩展
-
重命名检测:
- 上述代码不会明确处理重命名的情况,它会认为源路径中不存在对应的文件或文件夹,因此删除旧的并重新复制新的。
- 如果需要保留重命名记录并高效同步,可以考虑使用文件和文件夹的唯一标识(如 ID 或 Hash 值),这超出了简单的同步逻辑,需要更复杂的数据记录和对比机制。
-
日志和错误处理:
- 添加日志记录每个文件和文件夹的同步操作,可以方便调试。
- 添加异常处理,以确保在某些操作失败时不会中断整个同步过程。
-
效率提升:
- 对于大型目录结构,频繁的文件遍历和复制操作可能会导致性能问题。可以考虑使用增量同步工具,避免每次都全量遍历。