fix: 服务渠道分组软删同名可恢复,并修正 COS 自定义域名签名
All checks were successful
Build and Deploy / deploy (push) Successful in 1m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m44s
同名软删分组创建时恢复记录,更新时释放唯一键冲突;COS 自定义 CDN 域名关闭 signHost,并补充常见图片 Content-Type。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
8ce8b82f48
commit
623db4ffdb
@ -64,6 +64,21 @@ public partial class ServiceChannelService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新时若目标名称被软删记录占用,则改名释放唯一键,避免 uk_scg_name 冲突。
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 校验渠道编码唯一性。
|
/// 校验渠道编码唯一性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -128,9 +128,21 @@ public partial class ServiceChannelService : ApplicationService
|
|||||||
public async Task CreateGroupAsync(ServiceChannelGroupSaveInput input)
|
public async Task CreateGroupAsync(ServiceChannelGroupSaveInput input)
|
||||||
{
|
{
|
||||||
await ValidateGroupInputAsync(input, null);
|
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
|
await ServiceChannelGroupRepository.InsertAsync(new ServiceChannelGroup
|
||||||
{
|
{
|
||||||
Name = input.Name.Trim(),
|
Name = name,
|
||||||
AppId = input.AppId,
|
AppId = input.AppId,
|
||||||
ApiServiceProviderId = input.ApiServiceProviderId
|
ApiServiceProviderId = input.ApiServiceProviderId
|
||||||
});
|
});
|
||||||
@ -146,7 +158,9 @@ public partial class ServiceChannelService : ApplicationService
|
|||||||
{
|
{
|
||||||
var group = await GetGroupOrThrowAsync(input.Id);
|
var group = await GetGroupOrThrowAsync(input.Id);
|
||||||
await ValidateGroupInputAsync(input, group.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.AppId = input.AppId;
|
||||||
group.ApiServiceProviderId = input.ApiServiceProviderId;
|
group.ApiServiceProviderId = input.ApiServiceProviderId;
|
||||||
await ServiceChannelGroupRepository.UpdateAsync(group);
|
await ServiceChannelGroupRepository.UpdateAsync(group);
|
||||||
|
|||||||
@ -245,6 +245,32 @@ public class FileStorageService : ApplicationService
|
|||||||
return "text/html; charset=utf-8";
|
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";
|
return "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -365,8 +365,11 @@ public sealed class TencentCosFileStorage : IFileStorage, IDisposable
|
|||||||
if (!string.IsNullOrWhiteSpace(_customHost)
|
if (!string.IsNullOrWhiteSpace(_customHost)
|
||||||
&& !_customHost.EndsWith(".myqcloud.com", StringComparison.OrdinalIgnoreCase))
|
&& !_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.host = _customHost;
|
||||||
preSign.signHost = true;
|
preSign.signHost = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,4 +6,9 @@ namespace Oms.Domain.ChannelsAggregate.Repositories;
|
|||||||
public interface IServiceChannelGroupRepository : IRepository<ServiceChannelGroup>
|
public interface IServiceChannelGroupRepository : IRepository<ServiceChannelGroup>
|
||||||
{
|
{
|
||||||
Task<ServiceChannelGroup?> GetByNameAsync(string name, CancellationToken cancellationToken = default);
|
Task<ServiceChannelGroup?> GetByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按名称查询分组(含软删除),用于唯一名冲突时恢复或释放名称。
|
||||||
|
/// </summary>
|
||||||
|
Task<ServiceChannelGroup?> GetByNameIncludingDeletedAsync(string name, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ using Fake.DependencyInjection;
|
|||||||
using Oms.Domain.ChannelsAggregate.Entities;
|
using Oms.Domain.ChannelsAggregate.Entities;
|
||||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||||
using Oms.SqlSugarCore.Repositories;
|
using Oms.SqlSugarCore.Repositories;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
namespace Oms.SqlSugarCore.Channels;
|
namespace Oms.SqlSugarCore.Channels;
|
||||||
|
|
||||||
@ -11,4 +12,17 @@ public class ServiceChannelGroupRepository : OmsSqlSugarRepository<ServiceChanne
|
|||||||
{
|
{
|
||||||
return FirstOrDefaultAsync(x => x.Name == name, cancellationToken);
|
return FirstOrDefaultAsync(x => x.Name == name, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceChannelGroup?> GetByNameIncludingDeletedAsync(
|
||||||
|
string name,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var db = await GetDbContextAsync(cancellationToken);
|
||||||
|
var rows = await db.Queryable<ServiceChannelGroup>()
|
||||||
|
.ClearFilter()
|
||||||
|
.Where(x => x.Name == name)
|
||||||
|
.Take(1)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
return rows.FirstOrDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user