Compare commits
No commits in common. "f8cabe19096ae61caca094e99d29c13fafde165e" and "62d423ad4d575e260fadd2cea532ac715598d424" have entirely different histories.
f8cabe1909
...
62d423ad4d
@ -39,37 +39,6 @@ public static class FileStoragePath
|
|||||||
&& (value.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|
&& (value.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|
||||||
|| value.StartsWith("https://", StringComparison.OrdinalIgnoreCase));
|
|| value.StartsWith("https://", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否为本系统对象存储域名(自定义域 cos.* / 腾讯云默认域)。
|
|
||||||
/// 这类地址带临时签名,库内应存 object key,读出时重新签名;不可当外链原样返回。
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsManagedObjectStorageUrl(string? value)
|
|
||||||
{
|
|
||||||
if (!IsAbsoluteUrl(value))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var host = new Uri(value!).Host;
|
|
||||||
if (string.IsNullOrWhiteSpace(host))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
host = host.ToLowerInvariant();
|
|
||||||
return host.StartsWith("cos.", StringComparison.Ordinal)
|
|
||||||
|| host.Contains("myqcloud.com", StringComparison.Ordinal)
|
|
||||||
|| host.Contains("tencentcos.cn", StringComparison.Ordinal)
|
|
||||||
|| host.Contains("tencentcos.com", StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
catch (UriFormatException)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取对象时尝试的 key:含/不含 <c>admin/</c> 前缀(自定义域路径常带 PathBase)。
|
/// 读取对象时尝试的 key:含/不含 <c>admin/</c> 前缀(自定义域路径常带 PathBase)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ namespace Oms.Application.Storage;
|
|||||||
public static class OmsObjectStorageExtensions
|
public static class OmsObjectStorageExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将 key / 相对路径规范为库内 object key;本系统 COS 签名 URL 剥签后存 key;其它绝对外链原样返回;空返回 null。
|
/// 将 key / 相对路径规范为库内 object key;绝对外链原样返回;空返回 null。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string? ToStorageKey(string? keyOrUrl)
|
public static string? ToStorageKey(string? keyOrUrl)
|
||||||
{
|
{
|
||||||
@ -23,12 +23,6 @@ public static class OmsObjectStorageExtensions
|
|||||||
return "0";
|
return "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 私有桶签名 URL 会过期,禁止写入库;只保留 object key。
|
|
||||||
if (FileStoragePath.IsManagedObjectStorageUrl(raw))
|
|
||||||
{
|
|
||||||
return FileStoragePath.NormalizeKey(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FileStoragePath.IsAbsoluteUrl(raw))
|
if (FileStoragePath.IsAbsoluteUrl(raw))
|
||||||
{
|
{
|
||||||
return raw;
|
return raw;
|
||||||
@ -38,7 +32,7 @@ public static class OmsObjectStorageExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 临时访问地址。空值返回空串;真正外链原样返回;逻辑 key / 本系统 COS URL 走 <see cref="IObjectStorage.GetUrlAsync"/> 重新签名。
|
/// 临时访问地址。空值返回空串;外链原样返回;逻辑 key 走 <see cref="IObjectStorage.GetUrlAsync"/>。
|
||||||
/// Local 存储目录不可写/不可读时降级返回 key,避免拖垮下单等主流程。
|
/// Local 存储目录不可写/不可读时降级返回 key,避免拖垮下单等主流程。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<string> GetPublicUrlAsync(
|
public static async Task<string> GetPublicUrlAsync(
|
||||||
@ -52,12 +46,7 @@ public static class OmsObjectStorageExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
var raw = keyOrUrl.Trim();
|
var raw = keyOrUrl.Trim();
|
||||||
// 历史数据可能把带 q-sign 的完整 URL 存进库;直接返回会 AccessDenied/Request has expired。
|
if (FileStoragePath.IsAbsoluteUrl(raw))
|
||||||
if (FileStoragePath.IsManagedObjectStorageUrl(raw))
|
|
||||||
{
|
|
||||||
raw = FileStoragePath.NormalizeKey(raw);
|
|
||||||
}
|
|
||||||
else if (FileStoragePath.IsAbsoluteUrl(raw))
|
|
||||||
{
|
{
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
using Fake.ObjectStorage;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
|
||||||
using Oms.Application.Storage;
|
using Oms.Application.Storage;
|
||||||
|
|
||||||
namespace Oms.Tests;
|
namespace Oms.Tests;
|
||||||
@ -18,69 +16,4 @@ public class FileStoragePathTests
|
|||||||
keys.Should().Contain("admin/uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
keys.Should().Contain("admin/uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
||||||
keys.Should().Contain("uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
keys.Should().Contain("uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("https://cos.ideship.com/uploads/a.pdf?q-sign-algorithm=sha1", true)]
|
|
||||||
[InlineData("http://cos.test.ideship.com/admin/uploads/a.pdf", true)]
|
|
||||||
[InlineData("https://bucket-123.cos.ap-guangzhou.myqcloud.com/uploads/a.pdf", true)]
|
|
||||||
[InlineData("https://third-party.example.com/label.pdf", false)]
|
|
||||||
[InlineData("uploads/prints/orders/a.pdf", false)]
|
|
||||||
public void IsManagedObjectStorageUrl_ShouldDetectCosHosts(string value, bool expected)
|
|
||||||
{
|
|
||||||
FileStoragePath.IsManagedObjectStorageUrl(value).Should().Be(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ToStorageKey_ShouldStripExpiredCosSignedUrlToObjectKey()
|
|
||||||
{
|
|
||||||
var expired =
|
|
||||||
"https://cos.ideship.com/uploads/prints/orders/IDE2610081704036507-20261008170404.pdf?q-sign-algorithm=sha1&q-sign-time=1;2";
|
|
||||||
|
|
||||||
OmsObjectStorageExtensions.ToStorageKey(expired)
|
|
||||||
.Should()
|
|
||||||
.Be("uploads/prints/orders/IDE2610081704036507-20261008170404.pdf");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ToStorageKey_ShouldKeepExternalAbsoluteUrl()
|
|
||||||
{
|
|
||||||
const string external = "https://third-party.example.com/label.pdf?token=1";
|
|
||||||
OmsObjectStorageExtensions.ToStorageKey(external).Should().Be(external);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetPublicUrl_ShouldResignManagedCosAbsoluteUrl()
|
|
||||||
{
|
|
||||||
var expired =
|
|
||||||
"https://cos.ideship.com/uploads/prints/orders/IDE2610081704036507-20261008170404.pdf?q-sign-algorithm=sha1&q-sign-time=1;2";
|
|
||||||
const string fresh =
|
|
||||||
"https://cos.ideship.com/uploads/prints/orders/IDE2610081704036507-20261008170404.pdf?q-sign-algorithm=sha1&fresh=1";
|
|
||||||
|
|
||||||
var storage = new Mock<IObjectStorage>(MockBehavior.Strict);
|
|
||||||
storage
|
|
||||||
.Setup(x => x.GetUrlAsync(
|
|
||||||
"uploads/prints/orders/IDE2610081704036507-20261008170404.pdf",
|
|
||||||
It.IsAny<TimeSpan?>(),
|
|
||||||
It.IsAny<CancellationToken>()))
|
|
||||||
.ReturnsAsync(fresh);
|
|
||||||
|
|
||||||
var url = await storage.Object.GetPublicUrlAsync(expired);
|
|
||||||
|
|
||||||
url.Should().Be(fresh);
|
|
||||||
storage.VerifyAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetPublicUrl_ShouldPassThroughExternalAbsoluteUrl()
|
|
||||||
{
|
|
||||||
const string external = "https://third-party.example.com/label.pdf";
|
|
||||||
var storage = new Mock<IObjectStorage>(MockBehavior.Strict);
|
|
||||||
|
|
||||||
var url = await storage.Object.GetPublicUrlAsync(external);
|
|
||||||
|
|
||||||
url.Should().Be(external);
|
|
||||||
storage.Verify(
|
|
||||||
x => x.GetUrlAsync(It.IsAny<string>(), It.IsAny<TimeSpan?>(), It.IsAny<CancellationToken>()),
|
|
||||||
Times.Never);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user