diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
index af3ea5f..2a00598 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
@@ -64,6 +64,21 @@ public partial class ServiceChannelService
}
}
+ ///
+ /// 更新时若目标名称被软删记录占用,则改名释放唯一键,避免 uk_scg_name 冲突。
+ ///
+ private async Task FreeSoftDeletedNameIfNeededAsync(string name, int currentId)
+ {
+ var conflict = await ServiceChannelGroupRepository.GetByNameIncludingDeletedAsync(name);
+ if (conflict == null || !conflict.IsDeleted || conflict.Id == currentId)
+ {
+ return;
+ }
+
+ conflict.Name = $"{name}__deleted_{conflict.Id}";
+ await ServiceChannelGroupRepository.UpdateAsync(conflict);
+ }
+
///
/// 校验渠道编码唯一性。
///
diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
index dea8476..7188468 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
@@ -128,9 +128,21 @@ public partial class ServiceChannelService : ApplicationService
public async Task CreateGroupAsync(ServiceChannelGroupSaveInput input)
{
await ValidateGroupInputAsync(input, null);
+ var name = input.Name.Trim();
+ var deleted = await ServiceChannelGroupRepository.GetByNameIncludingDeletedAsync(name);
+ if (deleted != null && deleted.IsDeleted)
+ {
+ deleted.IsDeleted = false;
+ deleted.Name = name;
+ deleted.AppId = input.AppId;
+ deleted.ApiServiceProviderId = input.ApiServiceProviderId;
+ await ServiceChannelGroupRepository.UpdateAsync(deleted);
+ return;
+ }
+
await ServiceChannelGroupRepository.InsertAsync(new ServiceChannelGroup
{
- Name = input.Name.Trim(),
+ Name = name,
AppId = input.AppId,
ApiServiceProviderId = input.ApiServiceProviderId
});
@@ -146,7 +158,9 @@ public partial class ServiceChannelService : ApplicationService
{
var group = await GetGroupOrThrowAsync(input.Id);
await ValidateGroupInputAsync(input, group.Id);
- group.Name = input.Name.Trim();
+ var name = input.Name.Trim();
+ await FreeSoftDeletedNameIfNeededAsync(name, group.Id);
+ group.Name = name;
group.AppId = input.AppId;
group.ApiServiceProviderId = input.ApiServiceProviderId;
await ServiceChannelGroupRepository.UpdateAsync(group);
diff --git a/src/Oms.Application/Services/Storage/FileStorageService.cs b/src/Oms.Application/Services/Storage/FileStorageService.cs
index 9223b0e..1c256d0 100644
--- a/src/Oms.Application/Services/Storage/FileStorageService.cs
+++ b/src/Oms.Application/Services/Storage/FileStorageService.cs
@@ -245,6 +245,32 @@ public class FileStorageService : ApplicationService
return "text/html; charset=utf-8";
}
+ if (path.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
+ {
+ return "image/png";
+ }
+
+ if (path.EndsWith(".webp", StringComparison.OrdinalIgnoreCase))
+ {
+ return "image/webp";
+ }
+
+ if (path.EndsWith(".gif", StringComparison.OrdinalIgnoreCase))
+ {
+ return "image/gif";
+ }
+
+ if (path.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase))
+ {
+ return "image/bmp";
+ }
+
+ if (path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)
+ || path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
+ {
+ return "image/jpeg";
+ }
+
return "application/octet-stream";
}
diff --git a/src/Oms.Application/Storage/TencentCosFileStorage.cs b/src/Oms.Application/Storage/TencentCosFileStorage.cs
index 9f152d7..017c170 100644
--- a/src/Oms.Application/Storage/TencentCosFileStorage.cs
+++ b/src/Oms.Application/Storage/TencentCosFileStorage.cs
@@ -365,8 +365,11 @@ public sealed class TencentCosFileStorage : IFileStorage, IDisposable
if (!string.IsNullOrWhiteSpace(_customHost)
&& !_customHost.EndsWith(".myqcloud.com", StringComparison.OrdinalIgnoreCase))
{
+ // 自定义 CDN 域名:URL 走 cos.ideship.com,但不要把 Host 签进 q-header-list。
+ // signHost=true 时浏览器直开签名链常因 CDN 回源 Host 不一致而 403(img naturalWidth=0),
+ // 对象其实在桶里(/file-storage/stream 可读)。
preSign.host = _customHost;
- preSign.signHost = true;
+ preSign.signHost = false;
}
}
diff --git a/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelGroupRepository.cs b/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelGroupRepository.cs
index 749f4a0..021cd8f 100644
--- a/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelGroupRepository.cs
+++ b/src/Oms.Domain/ChannelsAggregate/Repositories/IServiceChannelGroupRepository.cs
@@ -6,4 +6,9 @@ namespace Oms.Domain.ChannelsAggregate.Repositories;
public interface IServiceChannelGroupRepository : IRepository
{
Task GetByNameAsync(string name, CancellationToken cancellationToken = default);
+
+ ///
+ /// 按名称查询分组(含软删除),用于唯一名冲突时恢复或释放名称。
+ ///
+ Task GetByNameIncludingDeletedAsync(string name, CancellationToken cancellationToken = default);
}
diff --git a/src/Oms.SqlSugarCore/Channels/ServiceChannelGroupRepository.cs b/src/Oms.SqlSugarCore/Channels/ServiceChannelGroupRepository.cs
index 22c5b78..cf6ddd2 100644
--- a/src/Oms.SqlSugarCore/Channels/ServiceChannelGroupRepository.cs
+++ b/src/Oms.SqlSugarCore/Channels/ServiceChannelGroupRepository.cs
@@ -2,6 +2,7 @@ using Fake.DependencyInjection;
using Oms.Domain.ChannelsAggregate.Entities;
using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.SqlSugarCore.Repositories;
+using SqlSugar;
namespace Oms.SqlSugarCore.Channels;
@@ -11,4 +12,17 @@ public class ServiceChannelGroupRepository : OmsSqlSugarRepository x.Name == name, cancellationToken);
}
+
+ public async Task GetByNameIncludingDeletedAsync(
+ string name,
+ CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ var rows = await db.Queryable()
+ .ClearFilter()
+ .Where(x => x.Name == name)
+ .Take(1)
+ .ToListAsync(cancellationToken);
+ return rows.FirstOrDefault();
+ }
}