fix: align backend APIs and upload flow
This commit is contained in:
@@ -6,6 +6,7 @@ using FileService.Domain.ValueObjects;
|
||||
using IM.Commons;
|
||||
using IM.InitCommon;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FileService.Application.UploadFile
|
||||
{
|
||||
@@ -15,27 +16,35 @@ namespace FileService.Application.UploadFile
|
||||
private readonly IMapper mapper;
|
||||
private readonly IObjectStorageRouter router;
|
||||
private readonly IOptions<StorageOptions> options;
|
||||
private readonly IGroupAccessService groupAccessService; private readonly IM.InitCommon.Management.RuntimePolicy runtime;
|
||||
|
||||
public UploadFileService(IUploadFileReposity reposity, IMapper mapper,
|
||||
IObjectStorageRouter router, IOptions<StorageOptions> options)
|
||||
IObjectStorageRouter router, IOptionsSnapshot<StorageOptions> options,
|
||||
IGroupAccessService groupAccessService, IM.InitCommon.Management.RuntimePolicy runtime)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
this.mapper = mapper;
|
||||
this.router = router;
|
||||
this.options = options;
|
||||
this.groupAccessService = groupAccessService; this.runtime = runtime;
|
||||
}
|
||||
|
||||
public async Task<Result<FileResponse>> GetFileInfoAsync(Guid id)
|
||||
public async Task<Result<FileResponse>> GetFileInfoAsync(Guid id, Guid requesterId)
|
||||
{
|
||||
var file = await reposity.FindByIdAsync(id);
|
||||
if (file == null)
|
||||
{
|
||||
return Result.Fail<FileResponse>(ResultCode.FILE_NOT_FOUND);
|
||||
}
|
||||
if (!await CanAccessAsync(file, requesterId))
|
||||
{
|
||||
return Result.Fail<FileResponse>(ResultCode.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
var response = mapper.Map<FileResponse>(file);
|
||||
response.Url = router.Route(file.StorageLocation.StorageProvider)
|
||||
.GetPublicUrl(file.StorageLocation);
|
||||
response.IsPublic = response.IsPublic || response.Url != null;
|
||||
return Result.Success(response);
|
||||
}
|
||||
|
||||
@@ -45,15 +54,31 @@ namespace FileService.Application.UploadFile
|
||||
/// </summary>
|
||||
public async Task<Result<FileResponse>> SimpleUploadAsync(SimpleUploadCommand command, CancellationToken token = default)
|
||||
{
|
||||
// 秒传:相同 checksum 的文件已存在则直接返回已有记录
|
||||
if (!string.IsNullOrEmpty(command.CheckSum))
|
||||
runtime.CheckFile(command.FileName, command.FileSize);
|
||||
if (command.FileSize <= 0 || command.FileSize > options.Value.Providers[options.Value.DefaultProviderCode].MaxObjectSizeBytes) return Result.Fail<FileResponse>(ResultCode.FILE_TOO_LARGE);
|
||||
var checksum = command.CheckSum;
|
||||
if (string.IsNullOrWhiteSpace(checksum))
|
||||
{
|
||||
var existing = await reposity.FindByCheckSumGlobalAsync("md5", command.CheckSum);
|
||||
if (existing != null)
|
||||
var hash = await MD5.HashDataAsync(command.Content, token);
|
||||
checksum = Convert.ToHexString(hash).ToLowerInvariant();
|
||||
if (command.Content.CanSeek)
|
||||
{
|
||||
command.Content.Position = 0;
|
||||
}
|
||||
}
|
||||
// 秒传:相同 checksum 的文件已存在则直接返回已有记录
|
||||
if (!string.IsNullOrEmpty(checksum))
|
||||
{
|
||||
var existing = await reposity.FindByCheckSumGlobalAsync("md5", checksum);
|
||||
var existingPublicUrl = existing == null
|
||||
? null
|
||||
: router.Route(existing.StorageLocation.StorageProvider).GetPublicUrl(existing.StorageLocation);
|
||||
if (existing != null && (existingPublicUrl != null ||
|
||||
(!command.IsPublic && existing.OwnerId == command.OwnerId)))
|
||||
{
|
||||
var hit = mapper.Map<FileResponse>(existing);
|
||||
var hitStorage = router.Route(existing.StorageLocation.StorageProvider);
|
||||
hit.Url = hitStorage.GetPublicUrl(existing.StorageLocation);
|
||||
hit.Url = existingPublicUrl;
|
||||
hit.IsPublic = hit.IsPublic || hit.Url != null;
|
||||
return Result.Success(hit);
|
||||
}
|
||||
}
|
||||
@@ -84,7 +109,8 @@ namespace FileService.Application.UploadFile
|
||||
fileSize: command.FileSize,
|
||||
contentType: command.ContentType,
|
||||
storageLocation: location,
|
||||
checkSum: new CheckSum("md5", command.CheckSum ?? string.Empty));
|
||||
checkSum: new CheckSum("md5", checksum),
|
||||
isPublic: command.IsPublic);
|
||||
|
||||
reposity.Create(file);
|
||||
|
||||
@@ -103,6 +129,10 @@ namespace FileService.Application.UploadFile
|
||||
{
|
||||
return Result.Fail<FileDownload>(ResultCode.FILE_NOT_FOUND);
|
||||
}
|
||||
if (!await CanAccessAsync(file, requesterId))
|
||||
{
|
||||
return Result.Fail<FileDownload>(ResultCode.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
var stream = await router.Route(file.StorageLocation.StorageProvider)
|
||||
.OpenReadAsync(file.StorageLocation, token);
|
||||
@@ -113,17 +143,33 @@ namespace FileService.Application.UploadFile
|
||||
file.FileName.Value));
|
||||
}
|
||||
|
||||
// FileName 值对象限制 20 字符;原始名超长时安全截断(保留扩展名),真实文件名由 objectKey 保证唯一。
|
||||
private async Task<bool> CanAccessAsync(Domain.Entities.UploadFile file, Guid requesterId)
|
||||
{
|
||||
var publicUrl = router.Route(file.StorageLocation.StorageProvider).GetPublicUrl(file.StorageLocation);
|
||||
if (file.IsPublic || publicUrl != null || file.OwnerId == requesterId) return true;
|
||||
if (string.Equals(file.ChatType, "PRIVATE", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return file.TargetId == requesterId;
|
||||
}
|
||||
if (string.Equals(file.ChatType, "GROUP", StringComparison.OrdinalIgnoreCase) && file.TargetId.HasValue)
|
||||
{
|
||||
return await groupAccessService.CheckMemberAsync(requesterId, file.TargetId.Value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 文件名超长时安全截断(保留扩展名),真实存储键由 objectKey 保证唯一。
|
||||
private static FileName SafeFileName(string fileName)
|
||||
{
|
||||
if (fileName.Length <= 20)
|
||||
const int maxFileNameLength = 255;
|
||||
if (fileName.Length <= maxFileNameLength)
|
||||
{
|
||||
return new FileName(fileName);
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(fileName);
|
||||
var stem = Path.GetFileNameWithoutExtension(fileName);
|
||||
var keep = Math.Max(0, 20 - ext.Length);
|
||||
var keep = Math.Max(0, maxFileNameLength - ext.Length);
|
||||
return new FileName(stem[..Math.Min(stem.Length, keep)] + ext);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user