This commit is contained in:
2026-05-09 19:14:32 +08:00
7 changed files with 116 additions and 59 deletions
@@ -33,6 +33,23 @@ namespace FileService.Application.StorageContracts
public Dictionary<int, UploadPart> Parts { get; init; } = new(); public Dictionary<int, UploadPart> Parts { get; init; } = new();
public UploadRuntimeCache(string taskId, string providerCode,
string uploadSessionId, string bucket, string region,
string objectKey, long fileSize, int totalPartCount,
DateTimeOffset? expireAt = null)
{
TaskId = taskId;
ProviderCode = providerCode;
UploadSessionId = uploadSessionId;
Bucket = bucket;
Region = region;
ObjectKey = objectKey;
FileSize = fileSize;
TotalPartCount = totalPartCount;
ExpireAt = expireAt ?? DateTime.MaxValue;
CreatedAt = DateTime.Now;
}
public void AddOrUpdatePart(UploadPart part) public void AddOrUpdatePart(UploadPart part)
{ {
Parts[part.PartNumber] = part; Parts[part.PartNumber] = part;
@@ -14,26 +14,23 @@ using System.Threading.Tasks;
namespace FileService.Application.UploadFileTask namespace FileService.Application.UploadFileTask
{ {
public class UploadFileTaskService public class UploadFileTaskService(IUploadTaskReposity reposity,
IMapper mapper, IObjectStorageRouter router,
IOptions<StorageOptions> options, IStorageRedisCache redis)
{ {
private readonly IUploadTaskReposity reposity; private readonly IUploadTaskReposity reposity = reposity;
private readonly IMapper mapper; private readonly IMapper mapper = mapper;
private readonly IObjectStorageRouter router; private readonly IObjectStorageRouter router = router;
private readonly IOptions<StorageOptions> options; private readonly IOptions<StorageOptions> options = options;
private readonly IStorageRedisCache redis = redis;
public UploadFileTaskService(IUploadTaskReposity reposity, IMapper mapper, IObjectStorageRouter router, IOptions<StorageOptions> options)
{
this.reposity = reposity;
this.mapper = mapper;
this.router = router;
this.options = options;
}
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 storage = router.Route(options.Value.ProviderCode);
var storageOption = options.Value.Providers[options.Value.DefaultProviderCode];
var storage = router.Route(storageOption.ProviderCode);
var initRes = await storage.InitUploadAsync(new StorageContracts.InitiateUploadCommand( var initRes = await storage.InitUploadAsync(new StorageContracts.InitiateUploadCommand(
ProviderCode: options.Value.ProviderCode, ProviderCode: options.Value.ProviderCode,
Bucket: options.Value.Bucket, Bucket: options.Value.Bucket,
@@ -45,6 +42,20 @@ namespace FileService.Application.UploadFileTask
var res = mapper.Map<TaskInitResponse>(initRes); var res = mapper.Map<TaskInitResponse>(initRes);
res.TaskId = task.Id; res.TaskId = task.Id;
reposity.Create(task);
await redis.SetAsync(new StorageContracts.UploadRuntimeCache(
taskId: task.Id.ToString(),
providerCode: storage.ProviderCode,
uploadSessionId: res.UploadSessionId,
bucket: storageOption.Bucket,
region: storageOption.Region,
objectKey: storageOption.Endpoint,
fileSize: task.FileSize,
totalPartCount: (int)(task.FileSize % storageOption.DefaultPartSizeBytes > 0 ?
(task.FileSize / storageOption.DefaultPartSizeBytes) + 1 :
task.FileSize / storageOption.DefaultPartSizeBytes)
));
return Result.Success(res); return Result.Success(res);
@@ -1,6 +1,10 @@
using Microsoft.AspNetCore.Authorization; using FileService.Application.UploadFileTask;
using FileService.Infrastructure;
using IM.ASPNETCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace FileService.WebApi.Controllers.FileTask namespace FileService.WebApi.Controllers.FileTask
{ {
@@ -9,11 +13,27 @@ namespace FileService.WebApi.Controllers.FileTask
[ApiController] [ApiController]
public class FileTaskController : ControllerBase public class FileTaskController : ControllerBase
{ {
private readonly UploadFileTaskService service;
public FileTaskController(UploadFileTaskService service)
{
this.service = service;
}
[HttpPost("init")] [HttpPost("init")]
public async Task<IActionResult> Init() [UnitOfWork(typeof(FileDbContext))]
public async Task<IActionResult> Init(FileTaskInitRequest request)
{ {
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var res = await service.InitTaskAsync(new UploadTaskInitCommand(
UploaderId: Guid.Parse(userId),
ConversationId: request.ConversationId,
FileName: request.FileName,
FileSize: request.FileSize,
contentType: request.ContentType,
checkSum: request.CheckSum
));
return Ok(res);
} }
} }
} }
@@ -0,0 +1,18 @@
using FluentValidation;
namespace FileService.WebApi.Controllers.FileTask
{
public class FileTaskInitRequest
{
public Guid ConversationId { get; set; }
public string FileName { get; set; }
public long FileSize { get; set; }
public string ContentType { get; set; }
public string CheckSum { get; set; }
}
public class FileTaskInitRequestValidator: AbstractValidator<FileTaskInitRequest>
{
}
}
+7
View File
@@ -7,6 +7,12 @@ using System.Threading.Tasks;
namespace IM.InitCommon namespace IM.InitCommon
{ {
public class StorageOptions public class StorageOptions
{
public string DefaultProviderCode { get; set; }
public Dictionary<string, StorageProviderOptions> Providers { get; set; }
}
public class StorageProviderOptions
{ {
public string ProviderCode { get; init; } = default!; public string ProviderCode { get; init; } = default!;
@@ -37,6 +43,7 @@ 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 int MinPartSizeBytes { get; init; } = 5 * 1024 * 1024;
public int DefaultPartSizeBytes { get; init; } = 5 * 1024 * 1024;
public int MaxPartCount { get; init; } = 10_000; public int MaxPartCount { get; init; } = 10_000;
} }
-2
View File
@@ -65,8 +65,6 @@ 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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MessageService (2)", "MessageService (2)", "{9FA3D6BD-1EC1-3BA5-80CB-CE02773A58D5}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
+21 -35
View File
@@ -1,15 +1,19 @@
pipeline { pipeline {
agent { label '构建机1' } agent any
parameters {
booleanParam(
name: 'FORCE_BUILD_ALL',
defaultValue: false,
description: '强制构建所有镜像'
)
}
environment { environment {
IMAGE_PREFIX = "im" IMAGE_PREFIX = "im"
IMAGE_TAG = "${BUILD_NUMBER}" IMAGE_TAG = "${BUILD_NUMBER}"
DOCKER_BUILDKIT = "1" DOCKER_BUILDKIT = "1"
REGISTRY = ""
// 如果你有 Harbor,可以改成:
// REGISTRY = "harbor.xxx.com/im"
REGISTRY = "reg.nxsir.cn/im"
} }
stages { stages {
@@ -22,16 +26,17 @@ pipeline {
stage('检测变更') { stage('检测变更') {
steps { steps {
script { script {
def diffCmd = ''' if (params.FORCE_BUILD_ALL || env.BUILD_NUMBER == '1') {
if git rev-parse HEAD~1 >/dev/null 2>&1; then env.BUILD_ALL = "true"
git diff --name-only HEAD~1 HEAD env.CHANGED_FILES = "FIRST_BUILD"
else echo "首次构建或手动强制构建,构建全部镜像"
git ls-files } else {
fi env.BUILD_ALL = "false"
'''
env.CHANGED_FILES = sh( env.CHANGED_FILES = sh(
script: diffCmd, script: '''
git diff --name-only HEAD~1 HEAD
''',
returnStdout: true returnStdout: true
).trim() ).trim()
@@ -40,6 +45,7 @@ pipeline {
} }
} }
} }
}
stage('构建镜像') { stage('构建镜像') {
steps { steps {
@@ -129,7 +135,7 @@ pipeline {
def changedFiles = env.CHANGED_FILES.split("\\n") as List def changedFiles = env.CHANGED_FILES.split("\\n") as List
services.each { svc -> services.each { svc ->
def needBuild = changedFiles.any { file -> def needBuild = env.BUILD_ALL == "true" || changedFiles.any { file ->
svc.paths.any { path -> svc.paths.any { path ->
file.startsWith(path) file.startsWith(path)
} }
@@ -147,16 +153,6 @@ pipeline {
-t ${latestName} \ -t ${latestName} \
-f ${svc.dockerfile} . -f ${svc.dockerfile} .
""" """
if (env.REGISTRY?.trim()) {
sh """
docker tag ${imageName} ${REGISTRY}/${svc.image}:${IMAGE_TAG}
docker tag ${latestName} ${REGISTRY}/${svc.image}:latest
docker push ${REGISTRY}/${svc.image}:${IMAGE_TAG}
docker push ${REGISTRY}/${svc.image}:latest
"""
}
} else { } else {
echo "跳过构建:${svc.name},没有相关代码变更" echo "跳过构建:${svc.name},没有相关代码变更"
} }
@@ -165,14 +161,4 @@ pipeline {
} }
} }
} }
post {
success {
echo "流水线执行成功"
}
failure {
echo "流水线执行失败"
}
}
} }