You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

读取本地目录结构并写入远程SFTP服务器的技术实现问询

Solution to Sync Directory Structure + Files to Remote SFTP

Got it, let's solve this problem step by step. Your current code handles file uploads, but we need to extend it to preserve the full directory structure (including empty folders) when syncing to the remote SFTP server. Here's how to do it:

Step 1: Capture Local Directory Structure & File Relative Paths

First, we need to collect two things from your source directory:

  • All subdirectories (relative to the root sending directory)
  • Each file's full relative path (so we know exactly where to place it on the remote server)

Update your existing GetFiles method and add a new GetDirectories method:

// Get all subdirectories relative to the root directory
public static IEnumerable<string> GetDirectories(string rootDir)
{
    return Directory.EnumerateDirectories(rootDir, "*", SearchOption.AllDirectories)
        .Select(dir => Path.GetRelativePath(rootDir, dir));
}

// Get files with their full relative path (instead of just filename)
public static IEnumerable<FileInfo> GetFiles(string rootDir)
{
    return Directory.EnumerateFiles(rootDir, "*", SearchOption.AllDirectories)
        .Select(path => 
        {
            var relativePath = Path.GetRelativePath(rootDir, path);
            var stream = File.OpenRead(path);
            return new FileInfo(relativePath, stream); // Your custom FileInfo should use RelativePath here
        })
        .DisposeEach(c => c.Content);
}

Step 2: Add a Helper to Recursively Create SFTP Directories

SFTP clients typically don't support creating nested directories in one call (e.g., /folder1/folder2 won't work if /folder1 doesn't exist). Add this helper method to your remote writer class:

// Recursively create nested directories on SFTP
private void CreateSftpDirectoryRecursive(SftpClient sftp, string fullRemoteDirPath)
{
    // Split path into parts (SFTP uses forward slashes)
    var dirParts = fullRemoteDirPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
    var currentPath = string.Empty;

    foreach (var dirPart in dirParts)
    {
        currentPath += $"/{dirPart}";
        if (!sftp.Exists(currentPath))
        {
            sftp.CreateDirectory(currentPath);
        }
    }
}

Step 3: Update the SFTP Write Method to Handle Directories & Files

Modify your Write method to first create all required remote directories, then upload each file to its correct location:

public async Task Write(string localRootDir, IEnumerable<string> directories, IEnumerable<FileInfo> files)
{
    using (var sftp = new SftpClient(this.sshInfo))
    {
        sftp.Connect();
        var baseRemoteDir = this.remoteDirectory;
        sftp.ChangeDirectory(baseRemoteDir);

        // First, create all required remote directories
        foreach (var localRelativeDir in directories)
        {
            // Convert Windows backslashes to SFTP forward slashes
            var remoteDirPath = Path.Combine(baseRemoteDir, localRelativeDir).Replace('\\', '/');
            CreateSftpDirectoryRecursive(sftp, remoteDirPath);
        }

        // Then upload each file to its corresponding remote path
        foreach (var file in files)
        {
            var remoteFilePath = Path.Combine(baseRemoteDir, file.RelativePath).Replace('\\', '/');
            
            // Double-check the parent directory exists (safety net)
            var remoteFileParentDir = Path.GetDirectoryName(remoteFilePath).Replace('\\', '/');
            if (!sftp.Exists(remoteFileParentDir))
            {
                CreateSftpDirectoryRecursive(sftp, remoteFileParentDir);
            }

            // Upload the file (ensure stream is disposed properly)
            using (var fileStream = file.Content)
            {
                sftp.UploadFile(fileStream, remoteFilePath);
            }
        }

        sftp.Disconnect();
    }
}

Step 4: Adjust the SendBatch Method to Use the New Logic

Update your SendBatch method to collect directories and files, then pass them to the updated Write method:

private async static Task SendBatch(Config config, Batch batch, IRemoteFileWriter writer)
{
    var sendingDir = GetClientSendingDirectory(config, batch.ClientName);
    Directory.CreateDirectory(Path.GetDirectoryName(sendingDir));
    Directory.Move(batch.LocalDirectory, sendingDir);
    Directory.CreateDirectory(batch.LocalDirectory);

    // Collect directories and files from the sending directory
    var directories = GetDirectories(sendingDir);
    var files = GetFiles(sendingDir);

    // Sync structure and files to remote
    await writer.Write(sendingDir, directories, files).ContinueWith(t => 
    {
        if (t.IsCompletedSuccessfully) // Only archive if upload succeeded
        {
            var zipArchivePath = GetArchiveDirectory(config, batch);
            var zipFileName = $"{zipArchivePath} {DateTime.Now.ToString("yyyy-MM-dd HH mm ss")}.Zip";
            ZipFile.CreateFromDirectory(sendingDir, zipFileName);
        }
        else if (t.IsFaulted)
        {
            // Handle upload failure (log error, retry, etc.)
            var exception = t.Exception?.InnerException ?? t.Exception;
            Console.WriteLine($"Upload failed: {exception.Message}");
        }
    });
}

Key Notes to Keep in Mind

  • Path Separators: Always convert Windows backslashes (\) to SFTP forward slashes (/) — SFTP servers expect this format.
  • Empty Folders: The GetDirectories method captures empty subfolders, so they'll be created on the remote server too.
  • Stream Disposal: We use using blocks for file streams to ensure they're properly released after upload.
  • Error Handling: Added a check for t.IsFaulted in ContinueWith to handle upload failures gracefully.

内容的提问来源于stack exchange,提问作者Ahhzeee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:48:47