Files
yes.blog/Yes.Infrastructure/Helpers/ZipHelper.cs
2025-05-16 23:14:52 +08:00

275 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace Yes.Infrastructure.Helpers
{
public class ZipHelper
{
public static void ExtractAndProcessZip(string zipFilePath, string targetDirectory, string folderName)
{
#region 参数验证
if (string.IsNullOrEmpty(zipFilePath))
throw new ArgumentException("ZIP 文件路径不能为空。", nameof(zipFilePath));
if (string.IsNullOrEmpty(targetDirectory))
throw new ArgumentException("目标目录路径不能为空。", nameof(targetDirectory));
if (!File.Exists(zipFilePath))
throw new FileNotFoundException("指定的 ZIP 文件不存在。", zipFilePath);
if (!Directory.Exists(targetDirectory))
Directory.CreateDirectory(targetDirectory);
#endregion
string tempExtractPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempExtractPath);
try
{
// 解压到临时目录
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempExtractPath, overwriteFiles: true);
// 找到实际文件目录(跳过单层空文件夹)
string contentRoot = GetContentRootDirectory(tempExtractPath);
string targetFolderPath = Path.Combine(targetDirectory, folderName);
#region 创建目标文件夹并移动文件
Directory.CreateDirectory(targetFolderPath);
foreach (var file in Directory.GetFiles(contentRoot))
{
string destFile = Path.Combine(targetFolderPath, Path.GetFileName(file));
File.Move(file, destFile, true);
}
foreach (var dir in Directory.GetDirectories(contentRoot))
{
string destDir = Path.Combine(targetFolderPath, Path.GetFileName(dir));
// 若目标目录存在,先删除
if (Directory.Exists(destDir))
{
Directory.Delete(destDir, recursive: true);
}
Directory.Move(dir, destDir);
}
#endregion
}
finally
{
// 清理临时目录
if (Directory.Exists(tempExtractPath))
Directory.Delete(tempExtractPath, recursive: true);
}
}
/// <summary>
/// 递归查找实际内容目录(跳过仅包含单个文件夹的层级)
/// </summary>
private static string GetContentRootDirectory(string startPath)
{
string currentPath = startPath;
while (true)
{
string[] subDirs = Directory.GetDirectories(currentPath);
string[] files = Directory.GetFiles(currentPath);
if (files.Length > 0 || subDirs.Length == 0)
return currentPath;
if (subDirs.Length == 1)
currentPath = subDirs[0];
else
return currentPath; // 多个子目录,停止递归
}
}
public static void ExtractToDirectory(string zipPath, string targetDirectory, string themeName)
{
if (!Directory.Exists(targetDirectory))
{
Directory.CreateDirectory(targetDirectory);
}
var requiredFileName = "config.json";
using (var zipFile = new ZipFile(zipPath))
{
bool fileFound = false;
foreach (ZipEntry entry in zipFile)
{
if (entry.IsDirectory)
continue;
string entryName = entry.Name.Trim('/');
if (string.IsNullOrEmpty(entryName))
continue;
string[] parts = entryName.Split('/');
// 文件名必须匹配
if (!parts[^1].Equals(requiredFileName, StringComparison.OrdinalIgnoreCase))
continue;
// 所有父级目录必须为 themeName
bool allParentsAreTopDir = parts.Take(parts.Length - 1)
.All(p => p.Equals(themeName, StringComparison.OrdinalIgnoreCase));
if (allParentsAreTopDir)
{
fileFound = true;
break;
}
}
if (!fileFound)
{
throw new FileNotFoundException(
$"ZIP 文件中未找到配置文件: {requiredFileName}");
}
foreach (ZipEntry entry in zipFile)
{
string originalPath = entry.Name.Trim('/');
if (string.IsNullOrEmpty(originalPath)) continue;
string[] parts = originalPath.Split('/');
int repeatCount = 0;
for (int i = 0; i < parts.Length; i++)
{
if (parts[i] == themeName)
{
repeatCount++;
}
else
{
break;
}
}
var newPathParts = new List<string>();
newPathParts.Add(themeName);
newPathParts.AddRange(parts.Skip(repeatCount));
string newRelativePath = Path.Combine(newPathParts.ToArray());
string targetPath = Path.Combine(targetDirectory, newRelativePath);
if (entry.IsDirectory)
{
Directory.CreateDirectory(targetPath);
}
else
{
if (!IsSafeFile(entry.Name))
{
continue;
}
using (var stream = zipFile.GetInputStream(entry))
{
using (var fileStream = File.Create(targetPath))
{
stream.CopyTo(fileStream);
}
}
}
}
}
}
private static bool IsSafeFile(string fileName)
{
var ext = Path.GetExtension(fileName).ToLower();
return new[] { ".liquid", ".js", ".css", ".html", ".md", ".ttf", ".svg", ".eot", ".woff", ".woff2", ".toml", ".xml" }.Contains(ext);
}
private static string GenerateSafeFolderName(string input, int maxLength = 200, char replaceSpaceWith = '_')
{
if (string.IsNullOrEmpty(input))
return "UnnamedFolder";
// 定义所有非法字符(Windows + Linux)
string invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()) + "<>:\"|?*");
Regex invalidCharRegex = new Regex($"[{invalidChars}]");
// 替换非法字符为空格
string sanitized = invalidCharRegex.Replace(input, " ");
// 替换连续空格为单个空格
sanitized = Regex.Replace(sanitized, @"\s+", " ").Trim();
// 替换空格为指定字符
sanitized = sanitized.Replace(" ", replaceSpaceWith.ToString());
// 处理保留名称(Windows)
string[] reservedNames = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
if (Array.Exists(reservedNames, name => name.Equals(sanitized, StringComparison.OrdinalIgnoreCase)))
{
sanitized += "_safe";
}
// 截断到最大长度
if (sanitized.Length > maxLength)
{
sanitized = sanitized.Substring(0, maxLength);
}
// 如果最终结果为空,返回默认值
if (string.IsNullOrEmpty(sanitized))
{
sanitized = "UnnamedFolder";
}
return sanitized;
}
/// <summary>
/// 将指定路径的文件夹打包为 ZIP 文件
/// </summary>
/// <param name="sourceFolderPath">要压缩的文件夹路径</param>
/// <param name="destinationZipPath">生成的 ZIP 文件路径</param>
/// <param name="compressionLevel">压缩级别</param>
/// <param name="overwrite">是否覆盖已存在的 ZIP 文件</param>
public static void CompressFolderToZip(
string sourceFolderPath,
string destinationZipPath,
CompressionLevel compressionLevel = CompressionLevel.BEST_SPEED,
bool overwrite = true)
{
if (string.IsNullOrEmpty(sourceFolderPath))
throw new ArgumentException("源文件夹路径不能为空。", nameof(sourceFolderPath));
if (string.IsNullOrEmpty(destinationZipPath))
throw new ArgumentException("目标 ZIP 文件路径不能为空。", nameof(destinationZipPath));
if (!Directory.Exists(sourceFolderPath))
throw new DirectoryNotFoundException($"源文件夹 '{sourceFolderPath}' 不存在。");
var destinationDirectory = Path.GetDirectoryName(destinationZipPath);
if (!Directory.Exists(destinationDirectory))
Directory.CreateDirectory(destinationDirectory);
if (File.Exists(destinationZipPath) && overwrite)
File.Delete(destinationZipPath);
try
{
System.IO.Compression.ZipFile.CreateFromDirectory(sourceFolderPath,destinationZipPath);
}
catch (Exception ex)
{
throw new IOException($"压缩文件夹失败:{ex.Message}", ex);
}
}
}
}