1111
This commit is contained in:
@@ -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,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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 "流水线执行失败"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user