namespace Dpz.Core.Service.ObjectStorage;
/// <summary>
/// COS 对象 Key 与公开访问 URL 的统一转换工具
/// </summary>
internal static class S3ObjectKeyHelper
{
/// <summary>
/// 根据目录段和文件名生成对象 Key
/// </summary>
public static string BuildKey(IEnumerable<string> path, string filename)
{
if (string.IsNullOrWhiteSpace(filename))
{
throw new ArgumentNullException(nameof(filename));
}
var segments = path.SelectMany(SplitPath).ToList();
segments.AddRange(SplitPath(filename));
return BuildKey(segments);
}
/// <summary>
/// 根据已经包含文件名的路径段生成对象 Key
/// </summary>
public static string BuildKey(IEnumerable<string> pathToFile)
{
var segments = pathToFile.SelectMany(SplitPath).ToList();
return segments.Count == 0
? throw new ArgumentException("对象路径不能为空", nameof(pathToFile))
: string.Join("/", segments);
}
/// <summary>
/// 从相对路径或完整 URL 中还原 COS 对象 Key
/// </summary>
public static string NormalizeKey(string pathToFile)
{
if (string.IsNullOrWhiteSpace(pathToFile))
{
throw new ArgumentNullException(nameof(pathToFile));
}
var path = pathToFile.Trim();
if (Uri.TryCreate(path, UriKind.Absolute, out var uri))
{
path = uri.AbsolutePath;
}
path = path.TrimStart('/');
path = Uri.UnescapeDataString(path);
return BuildKey([path]);
}
/// <summary>
/// 把目录路径转换成 ListObjectsV2 可用的 Prefix
/// </summary>
public static string NormalizeDirectoryPrefix(string path)
{
ArgumentNullException.ThrowIfNull(path);
var value = path.Trim();
if (value is "" or "/")
{
return string.Empty;
}
value = Uri.TryCreate(value, UriKind.Absolute, out var uri)
? Uri.UnescapeDataString(uri.AbsolutePath)
: Uri.UnescapeDataString(value);
value = value.Trim('/');
return string.IsNullOrWhiteSpace(value) ? string.Empty : value + "/";
}
/// <summary>
/// 生成对象的公开访问 URL
/// </summary>
public static string BuildAccessUrl(string cdnHost, string key)
{
if (string.IsNullOrWhiteSpace(cdnHost))
{
throw new ArgumentNullException(nameof(cdnHost));
}
var encodedKey = string.Join(
"/",
NormalizeKey(key).Split('/').Select(Uri.EscapeDataString)
);
return $"{cdnHost.TrimEnd('/')}/{encodedKey}";
}
/// <summary>
/// 取指定 Prefix 下一级目录或文件的显示名称
/// </summary>
public static string GetDisplayName(string keyOrPrefix, string parentPrefix)
{
var value = keyOrPrefix;
if (!string.IsNullOrEmpty(parentPrefix) && value.StartsWith(parentPrefix))
{
value = value[parentPrefix.Length..];
}
return value.Trim('/').Split('/').FirstOrDefault() ?? string.Empty;
}
/// <summary>
/// 组合远程目录和相对文件路径
/// </summary>
public static string CombineRemotePath(string remoteDirectory, string relativeFilePath)
{
var prefix = NormalizeDirectoryPrefix(remoteDirectory);
var key = NormalizeKey(relativeFilePath.Replace(Path.DirectorySeparatorChar, '/'));
return prefix + key;
}
private static IEnumerable<string> SplitPath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
yield break;
}
foreach (
var segment in path.Split(
['/', '\\'],
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
)
{
yield return Uri.UnescapeDataString(segment);
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个用于处理 S3/COS 对象存储 的工具类,主要负责对象键(Key)与公开访问 URL 之间的转换和规范化。
主要功能
1. BuildKey - 构建对象键
两个重载方法用于生成标准的对象存储键:
// 方法1: 从目录段和文件名生成
BuildKey(IEnumerable<string> path, string filename)
// 示例: path=["folder1", "folder2"], filename="file.txt"
// 返回: "folder1/folder2/file.txt"
// 方法2: 从完整路径段生成
BuildKey(IEnumerable<string> pathToFile)
2. NormalizeKey - 规范化键
从各种格式的路径还原成标准的对象键:
// 输入可以是:
// - 相对路径: "folder/file.txt"
// - 完整URL: "https://cdn.com/folder/file.txt"
// - URL编码路径: "folder%2Ffile.txt"
// 都会输出: "folder/file.txt"
处理步骤:
- 如果是完整URL,提取路径部分
- 移除开头的
/ - 解码 URL 编码字符
- 通过分割重组确保格式统一
3. NormalizeDirectoryPrefix - 规范化目录前缀
生成用于 ListObjectsV2 API 的目录前缀:
// 输入: "folder/subfolder" 或 "/folder/subfolder/" 或 URL
// 输出: "folder/subfolder/" (始终以/结尾)
// 特殊: "" 或 "/" 输入会返回空字符串
4. BuildAccessUrl - 生成公开访问 URL
结合 CDN 域名和对象键生成完整访问链接:
// cdnHost = "https://cdn.example.com"
// key = "folder/文件.txt"
// 返回: "https://cdn.example.com/folder/%E6%96%87%E4%BB%B6.txt"
注意: 对路径的每个段分别进行 URL 编码,确保特殊字符正确处理。
5. GetDisplayName - 获取显示名称
提取相对于父目录的下一级名称:
// keyOrPrefix = "parent/child/file.txt"
// parentPrefix = "parent/"
// 返回: "child"
6. CombineRemotePath - 组合远程路径
合并远程目录和本地相对路径:
// remoteDirectory = "upload/images"
// relativeFilePath = "2024\01\photo.jpg" (Windows路径)
// 返回: "upload/images/2024/01/photo.jpg"
核心辅助方法
SplitPath - 路径分割
私有方法,负责智能分割路径:
// 输入: "folder1/folder2\\file%20name.txt"
// 输出: ["folder1", "folder2", "file name.txt"]
特点:
- 同时支持
/和\分隔符 - 自动去除空段和两端空格
- URL 解码每个段
设计特点
- 容错性强:支持多种输入格式(相对路径、绝对URL、不同分隔符)
- 统一标准:所有输出都是规范的 S3 对象键格式(使用
/分隔) - 编码处理:正确处理 URL 编码/解码,避免特殊字符问题
- 参数验证:对空值和无效输入有完善的检查
使用场景
- 上传文件时生成对象键
- 从URL还原对象键以进行操作
- 列举目录内容(生成查询前缀)
- 生成文件的公开访问链接
- 跨平台路径处理(Windows/Linux)
AI 正在分析代码…
评论加载中...