fix: COS access-url 重签剥掉 PathBase,避免 admin/admin NoSuchKey
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
自定义域 URL 带 /admin 前缀,归一化为 uploads/... 再 GetUrl,防止双重 PathBase。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
1cef872823
commit
88c89bd85e
@ -28,7 +28,32 @@ public static class FileStoragePath
|
|||||||
throw new ArgumentException("object key 不能为空", nameof(pathOrUrl));
|
throw new ArgumentException("object key 不能为空", nameof(pathOrUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
return key;
|
// 自定义域公开 URL 常带 PathBase「admin/」;库内/COS 逻辑 key 为 uploads/...。
|
||||||
|
// 若不剥离,GetUrl 再拼 KeyPrefix/PathBase 会变成 admin/admin/uploads → NoSuchKey。
|
||||||
|
return StripPublicPathBase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 去掉公开 URL 上的 PathBase(可重复的 admin/),保留 uploads/... 等逻辑 key。
|
||||||
|
/// </summary>
|
||||||
|
public static string StripPublicPathBase(string key)
|
||||||
|
{
|
||||||
|
var value = key.Replace('\\', '/').Trim().TrimStart('/');
|
||||||
|
while (value.StartsWith("admin/", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var rest = value["admin/".Length..];
|
||||||
|
// 仅剥「PathBase + 业务前缀」;避免误伤 admin 业务目录(若有)
|
||||||
|
if (rest.StartsWith("uploads/", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| rest.StartsWith("admin/", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
value = rest;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -15,8 +15,9 @@ public class FileStoragePathTests
|
|||||||
|
|
||||||
var keys = FileStoragePath.ExpandReadKeys(url);
|
var keys = FileStoragePath.ExpandReadKeys(url);
|
||||||
|
|
||||||
keys.Should().Contain("admin/uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
// NormalizeKey 已剥 PathBase,主 key 为 uploads/...;Expand 仍保留 admin/ 候选以兼容历史对象
|
||||||
keys.Should().Contain("uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
keys.Should().Contain("uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
||||||
|
keys.Should().Contain("admin/uploads/prints/orders/IDE2610061059117673-20261006105912.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -41,6 +42,47 @@ public class FileStoragePathTests
|
|||||||
.Be("uploads/prints/orders/IDE2610081704036507-20261008170404.pdf");
|
.Be("uploads/prints/orders/IDE2610081704036507-20261008170404.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToStorageKey_ShouldStripPathBaseAdminFromCustomDomainUrl()
|
||||||
|
{
|
||||||
|
var url =
|
||||||
|
"https://cos.test.ideship.com/admin/uploads/prints/orders/IDE2610231052073356-20261023105208.pdf?q-sign-algorithm=sha1";
|
||||||
|
|
||||||
|
OmsObjectStorageExtensions.ToStorageKey(url)
|
||||||
|
.Should()
|
||||||
|
.Be("uploads/prints/orders/IDE2610231052073356-20261023105208.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NormalizeKey_ShouldCollapseDoubleAdminPathBase()
|
||||||
|
{
|
||||||
|
FileStoragePath.NormalizeKey("admin/admin/uploads/prints/orders/a.pdf")
|
||||||
|
.Should()
|
||||||
|
.Be("uploads/prints/orders/a.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPublicUrl_ShouldResignCustomDomainUrlWithoutDoubleAdmin()
|
||||||
|
{
|
||||||
|
var expired =
|
||||||
|
"https://cos.test.ideship.com/admin/uploads/prints/orders/IDE2610231052073356-20261023105208.pdf?q-sign-algorithm=sha1&q-sign-time=1;2";
|
||||||
|
const string fresh =
|
||||||
|
"https://cos.test.ideship.com/admin/uploads/prints/orders/IDE2610231052073356-20261023105208.pdf?q-sign-algorithm=sha1&fresh=1";
|
||||||
|
|
||||||
|
var storage = new Mock<IObjectStorage>(MockBehavior.Strict);
|
||||||
|
storage
|
||||||
|
.Setup(x => x.GetUrlAsync(
|
||||||
|
"uploads/prints/orders/IDE2610231052073356-20261023105208.pdf",
|
||||||
|
It.IsAny<TimeSpan?>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(fresh);
|
||||||
|
|
||||||
|
var url = await storage.Object.GetPublicUrlAsync(expired);
|
||||||
|
|
||||||
|
url.Should().Be(fresh);
|
||||||
|
storage.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ToStorageKey_ShouldKeepExternalAbsoluteUrl()
|
public void ToStorageKey_ShouldKeepExternalAbsoluteUrl()
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user