Merge branch 'master' of https://gitea.nxsir.cn/nanxun/IM_NEW
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
using FileService.Application.Ports;
|
using FileService.Application.Ports;
|
||||||
|
using FileService.Domain.IReposities;
|
||||||
|
using IM.Application.Abstractions;
|
||||||
using IM.Commons.IntegrationEvents;
|
using IM.Commons.IntegrationEvents;
|
||||||
using MassTransit;
|
using MassTransit;
|
||||||
using System;
|
using System;
|
||||||
@@ -12,18 +14,23 @@ namespace FileService.Application.EventHandler
|
|||||||
public class UploadTaskCompleteEventHandler : IConsumer<UploadTaskCompleteEvent>
|
public class UploadTaskCompleteEventHandler : IConsumer<UploadTaskCompleteEvent>
|
||||||
{
|
{
|
||||||
private readonly IObjectStorageRouter router;
|
private readonly IObjectStorageRouter router;
|
||||||
private readonly IStorageRedisCache cache;
|
private readonly IUploadFileReposity uploadFile;
|
||||||
|
private readonly IUnitOfWork uwork;
|
||||||
|
private readonly IStorageRedisCache storageCache;
|
||||||
|
|
||||||
public UploadTaskCompleteEventHandler(IObjectStorageRouter router, IStorageRedisCache cache)
|
public UploadTaskCompleteEventHandler(IObjectStorageRouter router, IUploadFileReposity uploadFile, IUnitOfWork uwork, IStorageRedisCache storageCache)
|
||||||
{
|
{
|
||||||
this.router = router;
|
this.router = router;
|
||||||
this.cache = cache;
|
this.uploadFile = uploadFile;
|
||||||
|
this.uwork = uwork;
|
||||||
|
this.storageCache = storageCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Consume(ConsumeContext<UploadTaskCompleteEvent> context)
|
public async Task Consume(ConsumeContext<UploadTaskCompleteEvent> context)
|
||||||
{
|
{
|
||||||
var @event = context.Message;
|
var @event = context.Message;
|
||||||
var storage = router.Route(@event.ProviderCode);
|
var storage = router.Route(@event.ProviderCode);
|
||||||
|
var taskCache = await storageCache.GetAsync(@event.SessionId);
|
||||||
if(@event.ProviderCode == "Local")
|
if(@event.ProviderCode == "Local")
|
||||||
{
|
{
|
||||||
await storage.CompleteUploadAsync(new StorageContracts.CompleteUploadCommand(
|
await storage.CompleteUploadAsync(new StorageContracts.CompleteUploadCommand(
|
||||||
@@ -39,6 +46,14 @@ namespace FileService.Application.EventHandler
|
|||||||
Checksum: s.Checksum
|
Checksum: s.Checksum
|
||||||
)).ToList()
|
)).ToList()
|
||||||
), context.CancellationToken);
|
), context.CancellationToken);
|
||||||
|
uploadFile.Create(new Domain.Entities.UploadFile(
|
||||||
|
ownerId: @event.OperatorId,
|
||||||
|
fileName: @event.FileName,
|
||||||
|
fileSize: taskCache.FileSize,
|
||||||
|
contentType: @event.ContentType,
|
||||||
|
new Domain.ValueObjects.StorageLocation(taskCache.ProviderCode, taskCache.Bucket, taskCache.ObjectKey, taskCache.Region),
|
||||||
|
checkSum: new Domain.ValueObjects.CheckSum("md5", @event.CheckSun)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\FileService.Domain\FileService.Domain.csproj" />
|
<ProjectReference Include="..\FileService.Domain\FileService.Domain.csproj" />
|
||||||
|
<ProjectReference Include="..\IM.Application\IM.Application.csproj" />
|
||||||
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
<ProjectReference Include="..\IM.Commons\IM.Commons.csproj" />
|
||||||
<ProjectReference Include="..\IM.InitCommon\IM.InitCommon.csproj" />
|
<ProjectReference Include="..\IM.InitCommon\IM.InitCommon.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using FileService.Application.UploadFile;
|
using FileService.Application.UploadFile;
|
||||||
|
using FileService.Application.UploadFileTask;
|
||||||
using IM.Commons;
|
using IM.Commons;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ namespace FileService.Application
|
|||||||
public void Initialize(IServiceCollection services)
|
public void Initialize(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<UploadFileService>();
|
services.AddScoped<UploadFileService>();
|
||||||
|
services.AddScoped<UploadFileTaskService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using FileService.Application.StorageContracts;
|
||||||
|
|
||||||
|
namespace FileService.Application.Ports
|
||||||
|
{
|
||||||
|
public interface ILocalChunkStorage
|
||||||
|
{
|
||||||
|
Task SavePartAsync(
|
||||||
|
SaveLocalPartCommand command);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FileService.Application.StorageContracts
|
||||||
|
{
|
||||||
|
public record SaveLocalPartCommand(
|
||||||
|
string UploadSessionId,
|
||||||
|
int PartNumber,
|
||||||
|
Stream Stream,
|
||||||
|
long ContentLength,
|
||||||
|
string? Checksum = null);
|
||||||
|
}
|
||||||
@@ -25,14 +25,14 @@ namespace FileService.Application.StorageContracts
|
|||||||
|
|
||||||
public int TotalPartCount { get; init; }
|
public int TotalPartCount { get; init; }
|
||||||
|
|
||||||
public long UploadedBytes { get; private set; }
|
public long UploadedBytes { get; set; }
|
||||||
|
|
||||||
public DateTimeOffset CreatedAt { get; init; }
|
public DateTimeOffset CreatedAt { get; init; }
|
||||||
|
|
||||||
public DateTimeOffset ExpireAt { get; init; }
|
public DateTimeOffset ExpireAt { get; init; }
|
||||||
|
|
||||||
public Dictionary<int, UploadPart> Parts { get; init; } = new();
|
public Dictionary<int, UploadPart> Parts { get; init; } = new();
|
||||||
|
public UploadRuntimeCache() { }
|
||||||
public UploadRuntimeCache(string taskId, string providerCode,
|
public UploadRuntimeCache(string taskId, string providerCode,
|
||||||
string uploadSessionId, string bucket, string region,
|
string uploadSessionId, string bucket, string region,
|
||||||
string objectKey, long fileSize, int totalPartCount,
|
string objectKey, long fileSize, int totalPartCount,
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using FileService.Application.Ports;
|
using FileService.Application.Ports;
|
||||||
using FileService.Application.StorageContracts;
|
using FileService.Application.StorageContracts;
|
||||||
using FileService.Domain.Entities;
|
|
||||||
using FileService.Domain.IReposities;
|
using FileService.Domain.IReposities;
|
||||||
using IM.Commons;
|
using IM.Commons;
|
||||||
using IM.Commons.IntegrationEvents;
|
using IM.Commons.IntegrationEvents;
|
||||||
using IM.InitCommon;
|
using IM.InitCommon;
|
||||||
using MassTransit;
|
using MassTransit;
|
||||||
using MassTransit.Internals;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FileService.Application.UploadFileTask
|
namespace FileService.Application.UploadFileTask
|
||||||
{
|
{
|
||||||
public class UploadFileTaskService(IUploadTaskReposity reposity,
|
public class UploadFileTaskService(IUploadTaskReposity reposity,
|
||||||
IMapper mapper, IObjectStorageRouter router,
|
IMapper mapper, IObjectStorageRouter router,
|
||||||
IOptions<StorageOptions> options, IStorageRedisCache redis,
|
IOptions<StorageOptions> options, IStorageRedisCache redis,
|
||||||
IPublishEndpoint endpoint
|
IPublishEndpoint endpoint, ILocalChunkStorage localChunkStorage
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
private readonly IUploadTaskReposity reposity = reposity;
|
private readonly IUploadTaskReposity reposity = reposity;
|
||||||
@@ -29,23 +22,24 @@ namespace FileService.Application.UploadFileTask
|
|||||||
private readonly IOptions<StorageOptions> options = options;
|
private readonly IOptions<StorageOptions> options = options;
|
||||||
private readonly IStorageRedisCache redis = redis;
|
private readonly IStorageRedisCache redis = redis;
|
||||||
private readonly IPublishEndpoint endpoint = endpoint;
|
private readonly IPublishEndpoint endpoint = endpoint;
|
||||||
|
private readonly ILocalChunkStorage localChunkStorage = localChunkStorage;
|
||||||
private readonly IObjectStoragePort storage = router.Route(options.Value.DefaultProviderCode);
|
private readonly IObjectStoragePort storage = router.Route(options.Value.DefaultProviderCode);
|
||||||
|
|
||||||
public async Task<Result<TaskInitResponse>> InitTaskAsync(UploadTaskInitCommand command)
|
public async Task<Result<TaskInitResponse>> InitTaskAsync(UploadTaskInitCommand command)
|
||||||
{
|
{
|
||||||
CancellationToken cancellationToken = CancellationToken.None;
|
CancellationToken cancellationToken = CancellationToken.None;
|
||||||
var task = command.ToUploadTask();
|
var task = command.ToUploadTask();
|
||||||
|
var date = DateTime.Now;
|
||||||
var storageOption = options.Value.Providers[options.Value.DefaultProviderCode];
|
var storageOption = options.Value.Providers[options.Value.DefaultProviderCode];
|
||||||
var storage = router.Route(storageOption.ProviderCode);
|
var storage = router.Route(storageOption.ProviderCode);
|
||||||
var initRes = await storage.InitUploadAsync(new StorageContracts.InitiateUploadCommand(
|
var initUpdateCommand = new StorageContracts.InitiateUploadCommand(
|
||||||
ProviderCode: storageOption.ProviderCode,
|
ProviderCode: storageOption.ProviderCode,
|
||||||
Bucket: storageOption.Bucket,
|
Bucket: storageOption.Bucket,
|
||||||
ObjectKey: storageOption.Endpoint,
|
ObjectKey: $"{storageOption.LocalRootPath}\\{date.Year}\\{date.Month}\\{date.Day}\\{command.FileName}",
|
||||||
ContentType:task.ContentType.Value,
|
ContentType: task.ContentType.Value,
|
||||||
ContentLength: command.FileSize,
|
ContentLength: command.FileSize,
|
||||||
null
|
null);
|
||||||
), cancellationToken);
|
var initRes = await storage.InitUploadAsync(initUpdateCommand, cancellationToken);
|
||||||
|
|
||||||
var res = mapper.Map<TaskInitResponse>(initRes);
|
var res = mapper.Map<TaskInitResponse>(initRes);
|
||||||
res.TaskId = task.Id;
|
res.TaskId = task.Id;
|
||||||
@@ -59,7 +53,7 @@ namespace FileService.Application.UploadFileTask
|
|||||||
uploadSessionId: res.UploadSessionId,
|
uploadSessionId: res.UploadSessionId,
|
||||||
bucket: storageOption.Bucket,
|
bucket: storageOption.Bucket,
|
||||||
region: storageOption.Region,
|
region: storageOption.Region,
|
||||||
objectKey: storageOption.Endpoint,
|
objectKey: initUpdateCommand.ObjectKey,
|
||||||
fileSize: task.FileSize,
|
fileSize: task.FileSize,
|
||||||
totalPartCount: (int)(task.FileSize % storageOption.DefaultPartSizeBytes > 0 ?
|
totalPartCount: (int)(task.FileSize % storageOption.DefaultPartSizeBytes > 0 ?
|
||||||
(task.FileSize / storageOption.DefaultPartSizeBytes) + 1 :
|
(task.FileSize / storageOption.DefaultPartSizeBytes) + 1 :
|
||||||
@@ -78,6 +72,11 @@ namespace FileService.Application.UploadFileTask
|
|||||||
return Result.Fail<PresignedUrl>(ResultCode.CHUNK_NOT_FOUND);
|
return Result.Fail<PresignedUrl>(ResultCode.CHUNK_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(taskCache.TotalPartCount < partNum)
|
||||||
|
{
|
||||||
|
return Result.Fail<PresignedUrl>(ResultCode.CHUNK_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
var presignUrl = await storage.GenerateUploadUrlAsync(new GenerateUploadUrlCommand(
|
var presignUrl = await storage.GenerateUploadUrlAsync(new GenerateUploadUrlCommand(
|
||||||
ProviderCode: taskCache.ProviderCode,
|
ProviderCode: taskCache.ProviderCode,
|
||||||
Bucket: taskCache.Bucket,
|
Bucket: taskCache.Bucket,
|
||||||
@@ -99,6 +98,12 @@ namespace FileService.Application.UploadFileTask
|
|||||||
return Result.Fail<UploadTaskResponse>(ResultCode.CHUNK_NOT_FOUND);
|
return Result.Fail<UploadTaskResponse>(ResultCode.CHUNK_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(taskCache.Parts.Count < taskCache.TotalPartCount)
|
||||||
|
{
|
||||||
|
return Result.Fail<UploadTaskResponse>(ResultCode.CHUNK_COMBINE_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
var task = await reposity.FindByIdAsync(Guid.Parse(taskCache.TaskId));
|
var task = await reposity.FindByIdAsync(Guid.Parse(taskCache.TaskId));
|
||||||
//var res = await storage.CompleteUploadAsync(new CompleteUploadCommand(
|
//var res = await storage.CompleteUploadAsync(new CompleteUploadCommand(
|
||||||
// ProviderCode: taskCache.ProviderCode,
|
// ProviderCode: taskCache.ProviderCode,
|
||||||
@@ -116,6 +121,7 @@ namespace FileService.Application.UploadFileTask
|
|||||||
|
|
||||||
await endpoint.Publish(new UploadTaskCompleteEvent()
|
await endpoint.Publish(new UploadTaskCompleteEvent()
|
||||||
{
|
{
|
||||||
|
OperatorId = command.userId,
|
||||||
Bucket = taskCache.Bucket,
|
Bucket = taskCache.Bucket,
|
||||||
FileName = task.FileName.ToString(),
|
FileName = task.FileName.ToString(),
|
||||||
ObjectKey = taskCache.ObjectKey,
|
ObjectKey = taskCache.ObjectKey,
|
||||||
@@ -126,10 +132,39 @@ namespace FileService.Application.UploadFileTask
|
|||||||
ProviderCode = taskCache.ProviderCode,
|
ProviderCode = taskCache.ProviderCode,
|
||||||
Region = taskCache.Region,
|
Region = taskCache.Region,
|
||||||
SessionId = command.UploadSessionId,
|
SessionId = command.UploadSessionId,
|
||||||
TaskId = task.Id
|
TaskId = task.Id,
|
||||||
});
|
FileSize = task.FileSize,
|
||||||
|
ContentType = task.ContentType.ToString(),
|
||||||
|
CheckSun = task.CheckSum.Value
|
||||||
|
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
return Result.Success(mapper.Map<UploadTaskResponse>(task));
|
return Result.Success(mapper.Map<UploadTaskResponse>(task));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Result<CompleteUploadResult>> UploadPartAsync(UploadPartCommand command)
|
||||||
|
{
|
||||||
|
|
||||||
|
var taskCache = await redis.GetAsync(command.SessionId);
|
||||||
|
if(taskCache is null)
|
||||||
|
{
|
||||||
|
return Result.Fail<CompleteUploadResult>(ResultCode.CHUNK_NOT_FOUND);
|
||||||
|
}
|
||||||
|
await localChunkStorage.SavePartAsync(new SaveLocalPartCommand(
|
||||||
|
UploadSessionId: command.SessionId,
|
||||||
|
PartNumber: command.PartNum,
|
||||||
|
Stream: command.Stream,
|
||||||
|
ContentLength: command.ContentLength
|
||||||
|
));
|
||||||
|
taskCache.AddOrUpdatePart(new StorageContracts.UploadPart(command.PartNum, command.PartNum.ToString(), command.ContentLength));
|
||||||
|
await redis.SetAsync(taskCache);
|
||||||
|
var location = new Domain.ValueObjects.StorageLocation(
|
||||||
|
storageProvider: taskCache.ProviderCode,
|
||||||
|
bucket: taskCache.Bucket,
|
||||||
|
objectKey: taskCache.ObjectKey,
|
||||||
|
region: taskCache.Region
|
||||||
|
);
|
||||||
|
return Result.Success(new CompleteUploadResult(location, command.PartNum.ToString(), command.ContentLength));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FileService.Application.UploadFileTask
|
||||||
|
{
|
||||||
|
public record UploadPartCommand(Stream Stream, string SessionId, int PartNum, long ContentLength);
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
using FileService.Domain.IReposities;
|
using FileService.Domain.IReposities;
|
||||||
using FileService.Infrastructure.Reposites;
|
using FileService.Infrastructure.Reposites;
|
||||||
using FileService.Infrastructure.Storage;
|
using FileService.Infrastructure.Storage;
|
||||||
|
using IM.Application.Abstractions;
|
||||||
using IM.Commons;
|
using IM.Commons;
|
||||||
|
using IM.Infrastructure.Efcore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace FileService.Infrastructure
|
namespace FileService.Infrastructure
|
||||||
@@ -17,6 +19,8 @@ namespace FileService.Infrastructure
|
|||||||
services.AddScoped<IRedisService, RedisCacheService>();
|
services.AddScoped<IRedisService, RedisCacheService>();
|
||||||
services.AddScoped<IObjectStorageRouter, ObjectStorageRouter>();
|
services.AddScoped<IObjectStorageRouter, ObjectStorageRouter>();
|
||||||
services.AddScoped<IStorageRedisCache,StorageCacheService>();
|
services.AddScoped<IStorageRedisCache,StorageCacheService>();
|
||||||
|
services.AddScoped<ILocalChunkStorage, LocalStorageAdapter>();
|
||||||
|
services.AddScoped<IUnitOfWork, EfUnitOfWork<FileDbContext>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ using StackExchange.Redis;
|
|||||||
|
|
||||||
namespace FileService.Infrastructure.Storage
|
namespace FileService.Infrastructure.Storage
|
||||||
{
|
{
|
||||||
public class LocalStorageAdapter(IStorageRedisCache redis, IOptions<StorageOptions> options) : IObjectStoragePort
|
public class LocalStorageAdapter(IStorageRedisCache redis, IOptions<StorageOptions> options) : IObjectStoragePort, ILocalChunkStorage
|
||||||
{
|
{
|
||||||
private readonly IStorageRedisCache redis = redis;
|
private readonly IStorageRedisCache redis = redis;
|
||||||
private readonly IOptions<StorageOptions> options = options;
|
private readonly IOptions<StorageOptions> options = options;
|
||||||
|
private readonly StorageProviderOptions providerOptions = options.Value.Providers[options.Value.DefaultProviderCode];
|
||||||
|
|
||||||
public string ProviderCode => "Local";
|
public string ProviderCode => "Local";
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ namespace FileService.Infrastructure.Storage
|
|||||||
public async Task<Result<object>> MergeAsync(string sessionId, string objectKey, IReadOnlyList<UploadPart> parts)
|
public async Task<Result<object>> MergeAsync(string sessionId, string objectKey, IReadOnlyList<UploadPart> parts)
|
||||||
{
|
{
|
||||||
var rootPath = options.Value.Providers[options.Value.DefaultProviderCode].LocalRootPath;
|
var rootPath = options.Value.Providers[options.Value.DefaultProviderCode].LocalRootPath;
|
||||||
var tempPath = Path.Combine(rootPath, "temp"); // 项目根目录下 uploads // 最终文件存储路径(这里可以用你之前 ObjectNameGenerator 生成的名字)
|
var tempPath = Path.Combine(rootPath, sessionId, "parts"); // 项目根目录下 uploads // 最终文件存储路径(这里可以用你之前 ObjectNameGenerator 生成的名字)
|
||||||
var finalPath = Path.Combine(rootPath, objectKey);
|
var finalPath = Path.Combine(rootPath, objectKey);
|
||||||
var finalDir = Path.GetDirectoryName(finalPath);
|
var finalDir = Path.GetDirectoryName(finalPath);
|
||||||
Directory.CreateDirectory(finalDir);
|
Directory.CreateDirectory(finalDir);
|
||||||
@@ -50,7 +51,7 @@ namespace FileService.Infrastructure.Storage
|
|||||||
// new("progress", progress.ToString("F2"))
|
// new("progress", progress.ToString("F2"))
|
||||||
//});
|
//});
|
||||||
}
|
}
|
||||||
var chunkPath = Path.Combine(tempPath, $"{i}.part.tmp");
|
var chunkPath = Path.Combine(tempPath, $"{i}.part");
|
||||||
if (!File.Exists(chunkPath))
|
if (!File.Exists(chunkPath))
|
||||||
return Result.Fail(ResultCode.CHUNK_NOT_FOUND);
|
return Result.Fail(ResultCode.CHUNK_NOT_FOUND);
|
||||||
using (var chunkStream = new FileStream(chunkPath, FileMode.Open))
|
using (var chunkStream = new FileStream(chunkPath, FileMode.Open))
|
||||||
@@ -76,7 +77,7 @@ namespace FileService.Infrastructure.Storage
|
|||||||
{
|
{
|
||||||
var baseUrl = options.Value.Providers[options.Value.DefaultProviderCode].LocalUploadApiBaseUrl;
|
var baseUrl = options.Value.Providers[options.Value.DefaultProviderCode].LocalUploadApiBaseUrl;
|
||||||
return new PresignedUrl(
|
return new PresignedUrl(
|
||||||
baseUrl + $"?partNumber={command.PartNumber}",
|
baseUrl + $"local/parts/upload?sessionId={command.UploadSessionId}&partNumber={command.PartNumber}",
|
||||||
new Dictionary<string, string>(),
|
new Dictionary<string, string>(),
|
||||||
ExpiresAt: DateTimeOffset.Now.Add(options.Value.Providers[options.Value.DefaultProviderCode].UploadUrlExpiresIn)
|
ExpiresAt: DateTimeOffset.Now.Add(options.Value.Providers[options.Value.DefaultProviderCode].UploadUrlExpiresIn)
|
||||||
);
|
);
|
||||||
@@ -88,5 +89,31 @@ namespace FileService.Infrastructure.Storage
|
|||||||
var location = new StorageLocation();
|
var location = new StorageLocation();
|
||||||
return new InitiateUploadResult(sessionId.ToString(),location);
|
return new InitiateUploadResult(sessionId.ToString(),location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SavePartAsync(SaveLocalPartCommand command)
|
||||||
|
{
|
||||||
|
var path = BuildPartPath(
|
||||||
|
command.UploadSessionId,
|
||||||
|
command.PartNumber);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(
|
||||||
|
Path.GetDirectoryName(path)!);
|
||||||
|
|
||||||
|
await using var fs = File.Create(path);
|
||||||
|
|
||||||
|
await command.Stream.CopyToAsync(fs);
|
||||||
|
|
||||||
|
await fs.FlushAsync();
|
||||||
|
}
|
||||||
|
private string BuildPartPath(
|
||||||
|
string uploadSessionId,
|
||||||
|
int partNumber)
|
||||||
|
{
|
||||||
|
return Path.Combine(
|
||||||
|
providerOptions.LocalRootPath,
|
||||||
|
uploadSessionId,
|
||||||
|
"parts",
|
||||||
|
$"{partNumber}.part");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using FileService.Application.StorageContracts;
|
||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace FileService.WebApi.Controllers.FileTask
|
||||||
|
{
|
||||||
|
public class CompleteTaskRequest
|
||||||
|
{
|
||||||
|
public string SessionId { get; set; }
|
||||||
|
public List<UploadPart> Parts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompleteTaskRequestValidator : AbstractValidator<CompleteTaskRequest>
|
||||||
|
{
|
||||||
|
public CompleteTaskRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.SessionId).NotEmpty()
|
||||||
|
.NotNull();
|
||||||
|
|
||||||
|
RuleFor(x => x.Parts)
|
||||||
|
.NotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,10 +36,29 @@ namespace FileService.WebApi.Controllers.FileTask
|
|||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[HttpPost]
|
[HttpGet("Getuploadurl")]
|
||||||
//public async Task<IActionResult> GetUploadUrl()
|
public async Task<IActionResult> GetUploadUrl(string sessionId, int partNum)
|
||||||
//{
|
{
|
||||||
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
var res = await service.GenerateUrlAsync(sessionId, partNum, Guid.Parse(userId));
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
//}
|
[HttpPost("complete")]
|
||||||
|
public async Task<IActionResult> Complete([FromBody] CompleteTaskRequest request)
|
||||||
|
{
|
||||||
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
var res = await service.CompleteTaskAsync(new UploadTaskCompleteCommand(request.SessionId, Guid.Parse(userId), request.Parts));
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("local/parts/upload")]
|
||||||
|
public async Task<IActionResult> LocalUpload(string sessionId, int partNumber, IFormFile file)
|
||||||
|
{
|
||||||
|
//var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
var stream = file.OpenReadStream();
|
||||||
|
var res = await service.UploadPartAsync(new UploadPartCommand(stream, sessionId, partNumber, file.Length));
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,25 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
|
||||||
"iisSettings": {
|
|
||||||
"windowsAuthentication": false,
|
|
||||||
"anonymousAuthentication": true,
|
|
||||||
"iisExpress": {
|
|
||||||
"applicationUrl": "http://localhost:55412",
|
|
||||||
"sslPort": 44396
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"http": {
|
"http": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "http://localhost:5220",
|
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5220"
|
||||||
},
|
},
|
||||||
"https": {
|
"https": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "https://localhost:7007;http://localhost:5220",
|
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
}
|
"CONSUL_URL": "http://192.168.5.100:8501"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7007;http://localhost:5220"
|
||||||
},
|
},
|
||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
@@ -37,5 +29,14 @@
|
|||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:55412",
|
||||||
|
"sslPort": 44396
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IM.Application.Abstractions
|
||||||
|
{
|
||||||
|
public interface IUnitOfWork
|
||||||
|
{
|
||||||
|
Task SaveChangesAsync(
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -8,6 +8,7 @@ namespace IM.Commons.IntegrationEvents
|
|||||||
{
|
{
|
||||||
public class UploadTaskCompleteEvent
|
public class UploadTaskCompleteEvent
|
||||||
{
|
{
|
||||||
|
public Guid OperatorId { get; set;}
|
||||||
public Guid TaskId { get; set; }
|
public Guid TaskId { get; set; }
|
||||||
public string SessionId { get; set; }
|
public string SessionId { get; set; }
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
@@ -15,6 +16,9 @@ namespace IM.Commons.IntegrationEvents
|
|||||||
public string Bucket { get; set; }
|
public string Bucket { get; set; }
|
||||||
public string Region { get; set; }
|
public string Region { get; set; }
|
||||||
public string ObjectKey { get; set; }
|
public string ObjectKey { get; set; }
|
||||||
|
public long FileSize { get; set; }
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
public string CheckSun { get; set; }
|
||||||
public IReadOnlyList<UploadPart> Parts { get; set; }
|
public IReadOnlyList<UploadPart> Parts { get; set; }
|
||||||
}
|
}
|
||||||
public sealed record UploadPart(
|
public sealed record UploadPart(
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ namespace IM.InitCommon
|
|||||||
|
|
||||||
public long MaxObjectSizeBytes { get; init; } = 1024L * 1024 * 1024;
|
public long MaxObjectSizeBytes { get; init; } = 1024L * 1024 * 1024;
|
||||||
|
|
||||||
public int MinPartSizeBytes { get; init; } = 5 * 1024 * 1024;
|
public long MinPartSizeBytes { get; init; } = 5 * 1024 * 1024;
|
||||||
public int DefaultPartSizeBytes { get; init; } = 5 * 1024 * 1024;
|
public long DefaultPartSizeBytes { get; init; } = 5 * 1024 * 1024;
|
||||||
|
|
||||||
public int MaxPartCount { get; init; } = 10_000;
|
public int MaxPartCount { get; init; } = 10_000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.WebApi", "FileS
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Application", "FileService.Application\FileService.Application.csproj", "{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Application", "FileService.Application\FileService.Application.csproj", "{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IM.Application", "IM.Application\IM.Application.csproj", "{3B7CAE97-DE5A-48B9-87BC-A44E04BC9A36}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -167,6 +169,10 @@ Global
|
|||||||
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3B7CAE97-DE5A-48B9-87BC-A44E04BC9A36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3B7CAE97-DE5A-48B9-87BC-A44E04BC9A36}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3B7CAE97-DE5A-48B9-87BC-A44E04BC9A36}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3B7CAE97-DE5A-48B9-87BC-A44E04BC9A36}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -196,6 +202,7 @@ Global
|
|||||||
{6827DF98-EC6C-4854-8E5A-D3FCC66E0A6D} = {136DC96D-82FC-4F77-91A7-B7D91298A323}
|
{6827DF98-EC6C-4854-8E5A-D3FCC66E0A6D} = {136DC96D-82FC-4F77-91A7-B7D91298A323}
|
||||||
{83C58EFA-00FB-443D-8311-2D4664A3FAB7} = {136DC96D-82FC-4F77-91A7-B7D91298A323}
|
{83C58EFA-00FB-443D-8311-2D4664A3FAB7} = {136DC96D-82FC-4F77-91A7-B7D91298A323}
|
||||||
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B} = {136DC96D-82FC-4F77-91A7-B7D91298A323}
|
{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B} = {136DC96D-82FC-4F77-91A7-B7D91298A323}
|
||||||
|
{3B7CAE97-DE5A-48B9-87BC-A44E04BC9A36} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {67902CF1-7322-4BD9-B1BD-10391EC03A00}
|
SolutionGuid = {67902CF1-7322-4BD9-B1BD-10391EC03A00}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using IM.Application.Abstractions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IM.Infrastructure.Efcore
|
||||||
|
{
|
||||||
|
public sealed class EfUnitOfWork<TDbContext> : IUnitOfWork where TDbContext : DbContext
|
||||||
|
{
|
||||||
|
private readonly TDbContext dbContext;
|
||||||
|
|
||||||
|
public EfUnitOfWork(TDbContext dbContext)
|
||||||
|
{
|
||||||
|
this.dbContext = dbContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DomainCommons\IM.DomainCommons.csproj" />
|
<ProjectReference Include="..\DomainCommons\IM.DomainCommons.csproj" />
|
||||||
|
<ProjectReference Include="..\IM.Application\IM.Application.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace MessageService.WebApi.Application.Conversation
|
|||||||
public ConversationMapperConfig()
|
public ConversationMapperConfig()
|
||||||
{
|
{
|
||||||
CreateMap<Domain.Entities.Conversation, ConversationResponse>()
|
CreateMap<Domain.Entities.Conversation, ConversationResponse>()
|
||||||
|
.ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => src.ModificationTime))
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,5 +31,6 @@ namespace MessageService.WebApi.Application.Dtos
|
|||||||
/// 最后一条最新消息
|
/// 最后一条最新消息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LastMessage { get; set; }
|
public string LastMessage { get; set; }
|
||||||
|
public DateTime DateTime { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace IdentityService.WebApi.Applications.Auth
|
|||||||
}
|
}
|
||||||
var token = await BuildTokenAsync(user);
|
var token = await BuildTokenAsync(user);
|
||||||
var refreshToken = await tokenService.CreateRefreshTokenAsync(user.Id);
|
var refreshToken = await tokenService.CreateRefreshTokenAsync(user.Id);
|
||||||
return Result<LoginResponse>.Success(new LoginResponse(user.Id, token, refreshToken, null));
|
return Result<LoginResponse>.Success(new LoginResponse(user.Id, token, refreshToken, null, user.UserName, user.NickName,user.Avatar, user.CreationTime));
|
||||||
}
|
}
|
||||||
public async Task<Result<UserResponse?>> RegisterAsync(string userName, string password, string nickName)
|
public async Task<Result<UserResponse?>> RegisterAsync(string userName, string password, string nickName)
|
||||||
{
|
{
|
||||||
@@ -80,7 +80,7 @@ namespace IdentityService.WebApi.Applications.Auth
|
|||||||
|
|
||||||
var token = await BuildTokenAsync(user);
|
var token = await BuildTokenAsync(user);
|
||||||
|
|
||||||
return Result<LoginResponse>.Success(new LoginResponse(user.Id, token, refreshToken, null));
|
return Result<LoginResponse>.Success(new LoginResponse(user.Id, token, refreshToken, null, user.UserName, user.NickName, user.Avatar, user.CreationTime));
|
||||||
}
|
}
|
||||||
private async Task<string> BuildTokenAsync(Domain.Entities.User user)
|
private async Task<string> BuildTokenAsync(Domain.Entities.User user)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,13 +6,21 @@
|
|||||||
public string Token { get; init; }
|
public string Token { get; init; }
|
||||||
public string RefreshToken { get; init; }
|
public string RefreshToken { get; init; }
|
||||||
public DateTime? Expired { get; init; }
|
public DateTime? Expired { get; init; }
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public string NickName { get; set; }
|
||||||
|
public string? Avatar { get; set; }
|
||||||
|
public DateTimeOffset CreationTime { get; set; }
|
||||||
|
|
||||||
public LoginResponse(Guid userId, string token, string refreshToken, DateTime? expired)
|
public LoginResponse(Guid userId, string token, string refreshToken, DateTime? expired, string userName, string nickName, string? avatar, DateTimeOffset creationTime)
|
||||||
{
|
{
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
Token = token;
|
Token = token;
|
||||||
RefreshToken = refreshToken;
|
RefreshToken = refreshToken;
|
||||||
Expired = expired;
|
Expired = expired;
|
||||||
|
UserName = userName;
|
||||||
|
NickName = nickName;
|
||||||
|
Avatar = avatar;
|
||||||
|
CreationTime = creationTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user