14 changed files with 362 additions and 1 deletions
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.ElsaNext.BlobStoring</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.ElsaNext.BlobStoring</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Remove="LINGYUN\Abp\ElsaNext\BlobStoring\Localization\**\*.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\ElsaNext\BlobStoring\Localization\**\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.BlobStoring" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext\LINGYUN.Abp.ElsaNext.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,38 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.BlobStoring.Extensions; |
||||
|
using LINGYUN.Abp.ElsaNext.Localization; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringModule), |
||||
|
typeof(AbpElsaNextModule))] |
||||
|
public class AbpElsaNextBlobStoringModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IModule>(elsa => |
||||
|
{ |
||||
|
elsa.UseAbpBlobStoring(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpElsaNextBlobStoringModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<ElsaNextResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/ElsaNext/BlobStoring/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Elsa.Workflows.Models; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
public abstract class BlobActivity : CodeActivity |
||||
|
{ |
||||
|
[Input(Description = "Path of the blob.")] |
||||
|
public Input<string> Path { get; set; } = default!; |
||||
|
|
||||
|
protected virtual IBlobContainer<ElsaBlobContainer> GetBlobContainer(ActivityExecutionContext context) |
||||
|
{ |
||||
|
return context.GetRequiredService<IBlobContainer<ElsaBlobContainer>>(); |
||||
|
} |
||||
|
|
||||
|
protected async virtual ValueTask OnErrorCompletedAsync(ActivityCompletedContext context) |
||||
|
{ |
||||
|
await context.TargetContext.CompleteActivityAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public abstract class BlobActivity<TResult> : CodeActivity<TResult> |
||||
|
{ |
||||
|
[Input(Description = "Path of the blob.")] |
||||
|
public Input<string> Path { get; set; } = default!; |
||||
|
|
||||
|
protected virtual IBlobContainer<ElsaBlobContainer> GetBlobContainer(ActivityExecutionContext context) |
||||
|
{ |
||||
|
return context.GetRequiredService<IBlobContainer<ElsaBlobContainer>>(); |
||||
|
} |
||||
|
|
||||
|
protected async virtual ValueTask OnErrorCompletedAsync(ActivityCompletedContext context) |
||||
|
{ |
||||
|
await context.TargetContext.CompleteActivityAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "BlobStoring", "Delete a blob.", Kind = ActivityKind.Task)] |
||||
|
public class DeleteBlob : BlobActivity<bool> |
||||
|
{ |
||||
|
protected async override ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var cancellationToken = context.CancellationToken; |
||||
|
var path = Path.Get(context); |
||||
|
var blobContainer = GetBlobContainer(context); |
||||
|
var result = await blobContainer.DeleteAsync(path, cancellationToken); |
||||
|
|
||||
|
Result.Set(context, result); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "BlobStoring", "Open a blob.", Kind = ActivityKind.Task)] |
||||
|
public class ReadBlob : BlobActivity<Stream> |
||||
|
{ |
||||
|
[Port] public IActivity? Error { get; set; } |
||||
|
|
||||
|
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var cancellationToken = context.CancellationToken; |
||||
|
var path = Path.Get(context); |
||||
|
var blobContainer = GetBlobContainer(context); |
||||
|
|
||||
|
var logger = context.GetRequiredService<ILogger<ReadBlob>>(); |
||||
|
try |
||||
|
{ |
||||
|
var data = await blobContainer.GetAsync(path, cancellationToken); |
||||
|
|
||||
|
Result.Set(context, data); |
||||
|
await context.CompleteActivityAsync(); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
logger.LogWarning(e, "Error while open a blob."); |
||||
|
context.AddExecutionLogEntry("Error", e.Message, payload: new |
||||
|
{ |
||||
|
e.StackTrace |
||||
|
}); |
||||
|
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Workflows; |
||||
|
using Elsa.Workflows.Attributes; |
||||
|
using Elsa.Workflows.Models; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.IO; |
||||
|
using System.IO.Compression; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Activities; |
||||
|
|
||||
|
[Activity("Elsa", "BlobStoring", "Save a blob.", Kind = ActivityKind.Task)] |
||||
|
public class SaveBlob : BlobActivity |
||||
|
{ |
||||
|
[Input(Description = "Blob exists whether overwrite.")] |
||||
|
public Input<bool> Overwrite { get; set; } = null!; |
||||
|
|
||||
|
[Input(Description = "The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.")] |
||||
|
public Input<object> Data { get; set; } = null!; |
||||
|
|
||||
|
[Port] public IActivity? Error { get; set; } |
||||
|
|
||||
|
protected async override ValueTask ExecuteAsync(ActivityExecutionContext context) |
||||
|
{ |
||||
|
var cancellationToken = context.CancellationToken; |
||||
|
var data = await ResolveAsStreamAsync(Data.Get(context), cancellationToken); |
||||
|
var overwrite = Overwrite.GetOrDefault(context); |
||||
|
var path = Path.Get(context); |
||||
|
var blobContainer = GetBlobContainer(context); |
||||
|
|
||||
|
var logger = context.GetRequiredService<ILogger<SaveBlob>>(); |
||||
|
try |
||||
|
{ |
||||
|
await blobContainer.SaveAsync(path, data, overwrite, cancellationToken); |
||||
|
await context.CompleteActivityAsync(); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
logger.LogWarning(e, "Error while save a blob."); |
||||
|
context.AddExecutionLogEntry("Error", e.Message, payload: new |
||||
|
{ |
||||
|
e.StackTrace |
||||
|
}); |
||||
|
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<Stream> ResolveAsStreamAsync(object data, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
if (data is Stream stream) |
||||
|
return stream; |
||||
|
|
||||
|
if (data is byte[] bytes) |
||||
|
return new MemoryStream(bytes); |
||||
|
|
||||
|
if (data is IFormFile formFile) |
||||
|
return formFile.OpenReadStream(); |
||||
|
|
||||
|
if (data is string stringData) |
||||
|
return new MemoryStream(Encoding.UTF8.GetBytes(stringData)); |
||||
|
|
||||
|
if (data is IEnumerable enumerable) |
||||
|
{ |
||||
|
var files = enumerable.Cast<object>().ToList(); |
||||
|
return files.Count == 1 ? await ResolveAsStreamAsync(files[0], cancellationToken) : await CreateZipArchiveAsync(files, cancellationToken); |
||||
|
} |
||||
|
|
||||
|
throw new NotSupportedException($"The provided data type is not supported: {data.GetType().Name}"); |
||||
|
} |
||||
|
|
||||
|
private async Task<Stream> CreateZipArchiveAsync(IEnumerable files, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var currentFileIndex = 0; |
||||
|
var zipStream = new MemoryStream(); |
||||
|
var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true); |
||||
|
|
||||
|
foreach (var file in files) |
||||
|
{ |
||||
|
var entryName = $"file-{currentFileIndex}.bin"; |
||||
|
var entry = zipArchive.CreateEntry(entryName); |
||||
|
var fileStream = await ResolveAsStreamAsync(file, cancellationToken); |
||||
|
await using var entryStream = entry.Open(); |
||||
|
await fileStream.CopyToAsync(entryStream, cancellationToken); |
||||
|
await entryStream.FlushAsync(cancellationToken); |
||||
|
entryStream.Close(); |
||||
|
currentFileIndex++; |
||||
|
} |
||||
|
|
||||
|
zipStream.Seek(0, SeekOrigin.Begin); |
||||
|
return zipStream; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.BlobStoring; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring; |
||||
|
|
||||
|
[BlobContainerName("workflow")] |
||||
|
public class ElsaBlobContainer |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Elsa.Features.Services; |
||||
|
using LINGYUN.Abp.ElsaNext.BlobStoring.Features; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Extensions; |
||||
|
|
||||
|
public static class ModuleExtensions |
||||
|
{ |
||||
|
public static IModule UseAbpBlobStoring(this IModule configuration, Action<BlobStoringFeature>? configure = null) |
||||
|
{ |
||||
|
configuration.Configure(configure); |
||||
|
return configuration; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Elsa.Extensions; |
||||
|
using Elsa.Features.Abstractions; |
||||
|
using Elsa.Features.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ElsaNext.BlobStoring.Features; |
||||
|
|
||||
|
public class BlobStoringFeature : FeatureBase |
||||
|
{ |
||||
|
public BlobStoringFeature(IModule module) : base(module) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void Configure() |
||||
|
{ |
||||
|
Module.AddActivitiesFrom<BlobStoringFeature>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Path of the blob.": "Path of the blob.", |
||||
|
"Blob exists whether overwrite.": "Blob exists whether overwrite.", |
||||
|
"The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.": "The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.", |
||||
|
"BlobStoring": "Blob Storing", |
||||
|
"Delete Blob": "Delete a blob.", |
||||
|
"Read Blob.": "Read a blob.", |
||||
|
"Save Blob.": "Save a blob." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Path of the blob.": "文件存储路径。", |
||||
|
"Blob exists whether overwrite.": "覆盖已有文件。", |
||||
|
"The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.": "要保存的文件数据。可以是流、二进制数据、字符串、表单文件或一组文件。", |
||||
|
"BlobStoring": "对象存储", |
||||
|
"Delete Blob": "删除文件", |
||||
|
"Read Blob": "打开文件", |
||||
|
"Save Blob": "保存文件" |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue