Merge branch 'master' of https://gitea.nxsir.cn/nanxun/IM_NEW
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
using AutoMapper;
|
||||
using AutoMapper;
|
||||
using FileService.Application.Ports;
|
||||
using FileService.Application.StorageContracts;
|
||||
using FileService.Domain.IReposities;
|
||||
using FileService.Domain.ValueObjects;
|
||||
using IM.Commons;
|
||||
using IM.InitCommon;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FileService.Application.UploadFile
|
||||
{
|
||||
@@ -8,11 +13,16 @@ namespace FileService.Application.UploadFile
|
||||
{
|
||||
private readonly IUploadFileReposity reposity;
|
||||
private readonly IMapper mapper;
|
||||
private readonly IObjectStorageRouter router;
|
||||
private readonly IOptions<StorageOptions> options;
|
||||
|
||||
public UploadFileService(IUploadFileReposity reposity, IMapper mapper)
|
||||
public UploadFileService(IUploadFileReposity reposity, IMapper mapper,
|
||||
IObjectStorageRouter router, IOptions<StorageOptions> options)
|
||||
{
|
||||
this.reposity = reposity;
|
||||
this.mapper = mapper;
|
||||
this.router = router;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public async Task<Result<FileResponse>> GetFileInfoAsync(Guid id)
|
||||
@@ -23,7 +33,98 @@ namespace FileService.Application.UploadFile
|
||||
return Result.Fail<FileResponse>(ResultCode.FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
return Result.Success(mapper.Map<FileResponse>(file));
|
||||
var response = mapper.Map<FileResponse>(file);
|
||||
response.Url = router.Route(file.StorageLocation.StorageProvider)
|
||||
.GetPublicUrl(file.StorageLocation);
|
||||
return Result.Success(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单次直传:一次性写入存储并同步落库(不分片、不走 MQ)。
|
||||
/// 上传前按 checksum 检查是否已存在(秒传)。
|
||||
/// </summary>
|
||||
public async Task<Result<FileResponse>> SimpleUploadAsync(SimpleUploadCommand command, CancellationToken token = default)
|
||||
{
|
||||
// 秒传:相同 checksum 的文件已存在则直接返回已有记录
|
||||
if (!string.IsNullOrEmpty(command.CheckSum))
|
||||
{
|
||||
var existing = await reposity.FindByCheckSumGlobalAsync("md5", command.CheckSum);
|
||||
if (existing != null)
|
||||
{
|
||||
var hit = mapper.Map<FileResponse>(existing);
|
||||
var hitStorage = router.Route(existing.StorageLocation.StorageProvider);
|
||||
hit.Url = hitStorage.GetPublicUrl(existing.StorageLocation);
|
||||
return Result.Success(hit);
|
||||
}
|
||||
}
|
||||
|
||||
var providerOption = options.Value.Providers[options.Value.DefaultProviderCode];
|
||||
var storage = router.Route(providerOption.ProviderCode);
|
||||
|
||||
// 公开文件落公开桶/目录,否则落私有桶
|
||||
var bucket = command.IsPublic
|
||||
? (providerOption.PublicBucket ?? providerOption.Bucket)
|
||||
: providerOption.Bucket;
|
||||
|
||||
var ext = Path.GetExtension(command.FileName);
|
||||
var date = DateTime.Now;
|
||||
var objectKey = $"{date:yyyy/MM/dd}/{Guid.NewGuid():N}{ext}";
|
||||
|
||||
var location = await storage.PutObjectAsync(new PutObjectCommand(
|
||||
ProviderCode: providerOption.ProviderCode,
|
||||
Bucket: bucket,
|
||||
ObjectKey: objectKey,
|
||||
ContentType: command.ContentType,
|
||||
Content: command.Content,
|
||||
ContentLength: command.FileSize), token);
|
||||
|
||||
var file = new Domain.Entities.UploadFile(
|
||||
ownerId: command.OwnerId,
|
||||
fileName: SafeFileName(command.FileName),
|
||||
fileSize: command.FileSize,
|
||||
contentType: command.ContentType,
|
||||
storageLocation: location,
|
||||
checkSum: new CheckSum("md5", command.CheckSum ?? string.Empty));
|
||||
|
||||
reposity.Create(file);
|
||||
|
||||
var response = mapper.Map<FileResponse>(file);
|
||||
response.Url = storage.GetPublicUrl(location);
|
||||
return Result.Success(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开文件下载流(鉴权下载)。返回流、内容类型与原始文件名。
|
||||
/// </summary>
|
||||
public async Task<Result<FileDownload>> OpenDownloadAsync(Guid id, Guid requesterId, CancellationToken token = default)
|
||||
{
|
||||
var file = await reposity.FindByIdAsync(id);
|
||||
if (file == null)
|
||||
{
|
||||
return Result.Fail<FileDownload>(ResultCode.FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
var stream = await router.Route(file.StorageLocation.StorageProvider)
|
||||
.OpenReadAsync(file.StorageLocation, token);
|
||||
|
||||
return Result.Success(new FileDownload(
|
||||
stream,
|
||||
file.ContentType.Value,
|
||||
file.FileName.Value));
|
||||
}
|
||||
|
||||
// FileName 值对象限制 20 字符;原始名超长时安全截断(保留扩展名),真实文件名由 objectKey 保证唯一。
|
||||
private static FileName SafeFileName(string fileName)
|
||||
{
|
||||
if (fileName.Length <= 20)
|
||||
{
|
||||
return new FileName(fileName);
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(fileName);
|
||||
var stem = Path.GetFileNameWithoutExtension(fileName);
|
||||
var keep = Math.Max(0, 20 - ext.Length);
|
||||
return new FileName(stem[..Math.Min(stem.Length, keep)] + ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user