From 88c89bd85ea9c9ec2ddaad98cb26336042e117c8 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Fri, 23 Oct 2026 14:16:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20COS=20access-url=20=E9=87=8D=E7=AD=BE?= =?UTF-8?q?=E5=89=A5=E6=8E=89=20PathBase=EF=BC=8C=E9=81=BF=E5=85=8D=20admi?= =?UTF-8?q?n/admin=20NoSuchKey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自定义域 URL 带 /admin 前缀,归一化为 uploads/... 再 GetUrl,防止双重 PathBase。 Co-authored-by: Cursor --- .../Storage/FileStoragePath.cs | 27 +++++++++++- src/Oms.Tests/FileStoragePathTests.cs | 44 ++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/Oms.Application/Storage/FileStoragePath.cs b/src/Oms.Application/Storage/FileStoragePath.cs index a9af070..6a17bbe 100644 --- a/src/Oms.Application/Storage/FileStoragePath.cs +++ b/src/Oms.Application/Storage/FileStoragePath.cs @@ -28,7 +28,32 @@ public static class FileStoragePath 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); + } + + /// + /// 去掉公开 URL 上的 PathBase(可重复的 admin/),保留 uploads/... 等逻辑 key。 + /// + 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; } /// diff --git a/src/Oms.Tests/FileStoragePathTests.cs b/src/Oms.Tests/FileStoragePathTests.cs index 9a26ce1..493c7d9 100644 --- a/src/Oms.Tests/FileStoragePathTests.cs +++ b/src/Oms.Tests/FileStoragePathTests.cs @@ -15,8 +15,9 @@ public class FileStoragePathTests 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("admin/uploads/prints/orders/IDE2610061059117673-20261006105912.pdf"); } [Theory] @@ -41,6 +42,47 @@ public class FileStoragePathTests .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(MockBehavior.Strict); + storage + .Setup(x => x.GetUrlAsync( + "uploads/prints/orders/IDE2610231052073356-20261023105208.pdf", + It.IsAny(), + It.IsAny())) + .ReturnsAsync(fresh); + + var url = await storage.Object.GetPublicUrlAsync(expired); + + url.Should().Be(fresh); + storage.VerifyAll(); + } + [Fact] public void ToStorageKey_ShouldKeepExternalAbsoluteUrl() {