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 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)
{
Parts[part.PartNumber] = part;
@@ -14,26 +14,23 @@ using System.Threading.Tasks;
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 IMapper mapper;
private readonly IObjectStorageRouter router;
private readonly IOptions<StorageOptions> options;
public UploadFileTaskService(IUploadTaskReposity reposity, IMapper mapper, IObjectStorageRouter router, IOptions<StorageOptions> options)
{
this.reposity = reposity;
this.mapper = mapper;
this.router = router;
this.options = options;
}
private readonly IUploadTaskReposity reposity = reposity;
private readonly IMapper mapper = mapper;
private readonly IObjectStorageRouter router = router;
private readonly IOptions<StorageOptions> options = options;
private readonly IStorageRedisCache redis = redis;
public async Task<Result<TaskInitResponse>> InitTaskAsync(UploadTaskInitCommand command)
{
CancellationToken cancellationToken = CancellationToken.None;
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(
ProviderCode: options.Value.ProviderCode,
Bucket: options.Value.Bucket,
@@ -45,6 +42,20 @@ namespace FileService.Application.UploadFileTask
var res = mapper.Map<TaskInitResponse>(initRes);
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);
@@ -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.Mvc;
using System.Security.Claims;
namespace FileService.WebApi.Controllers.FileTask
{
@@ -9,11 +13,27 @@ namespace FileService.WebApi.Controllers.FileTask
[ApiController]
public class FileTaskController : ControllerBase
{
private readonly UploadFileTaskService service;
public FileTaskController(UploadFileTaskService service)
{
this.service = service;
}
[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
{
public class StorageOptions
{
public string DefaultProviderCode { get; set; }
public Dictionary<string, StorageProviderOptions> Providers { get; set; }
}
public class StorageProviderOptions
{
public string ProviderCode { get; init; } = default!;
@@ -37,6 +43,7 @@ namespace IM.InitCommon
public long MaxObjectSizeBytes { get; init; } = 1024L * 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;
}
-2
View File
@@ -65,8 +65,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.WebApi", "FileS
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Application", "FileService.Application\FileService.Application.csproj", "{A05B43F3-3391-4ACC-A8BD-B9B7AEABC90B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MessageService (2)", "MessageService (2)", "{9FA3D6BD-1EC1-3BA5-80CB-CE02773A58D5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+26 -40
View File
@@ -1,15 +1,19 @@
pipeline {
agent { label '构建机1' }
agent any
parameters {
booleanParam(
name: 'FORCE_BUILD_ALL',
defaultValue: false,
description: '强制构建所有镜像'
)
}
environment {
IMAGE_PREFIX = "im"
IMAGE_TAG = "${BUILD_NUMBER}"
DOCKER_BUILDKIT = "1"
// 如果你有 Harbor,可以改成:
// REGISTRY = "harbor.xxx.com/im"
REGISTRY = "reg.nxsir.cn/im"
REGISTRY = ""
}
stages {
@@ -22,21 +26,23 @@ pipeline {
stage('检测变更') {
steps {
script {
def diffCmd = '''
if git rev-parse HEAD~1 >/dev/null 2>&1; then
git diff --name-only HEAD~1 HEAD
else
git ls-files
fi
'''
if (params.FORCE_BUILD_ALL || env.BUILD_NUMBER == '1') {
env.BUILD_ALL = "true"
env.CHANGED_FILES = "FIRST_BUILD"
echo "首次构建或手动强制构建,构建全部镜像"
} else {
env.BUILD_ALL = "false"
env.CHANGED_FILES = sh(
script: diffCmd,
returnStdout: true
).trim()
env.CHANGED_FILES = sh(
script: '''
git diff --name-only HEAD~1 HEAD
''',
returnStdout: true
).trim()
echo "变更文件:"
echo env.CHANGED_FILES
echo "变更文件:"
echo env.CHANGED_FILES
}
}
}
}
@@ -129,7 +135,7 @@ pipeline {
def changedFiles = env.CHANGED_FILES.split("\\n") as List
services.each { svc ->
def needBuild = changedFiles.any { file ->
def needBuild = env.BUILD_ALL == "true" || changedFiles.any { file ->
svc.paths.any { path ->
file.startsWith(path)
}
@@ -147,16 +153,6 @@ pipeline {
-t ${latestName} \
-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 {
echo "跳过构建:${svc.name},没有相关代码变更"
}
@@ -165,14 +161,4 @@ pipeline {
}
}
}
post {
success {
echo "流水线执行成功"
}
failure {
echo "流水线执行失败"
}
}
}