This commit is contained in:
2026-06-02 16:25:57 +08:00
24 changed files with 285 additions and 53 deletions
@@ -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>
+2
View File
@@ -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);
}
+4
View File
@@ -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);
}
}
+9
View File
@@ -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(
+2 -2
View File
@@ -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;
} }
+7
View File
@@ -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}
+25
View File
@@ -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);
}
}
}
+1
View File
@@ -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; }
} }
} }
+2 -2
View File
@@ -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;
} }
} }
} }