refactor(scripting): 按钮动作链改造指向 IScriptExecutionService
- WorkflowApplicationModule 依赖 ScriptingApplicationModule - BuildFlowButtonActionExecutor 改用 IScriptExecutionService.ExecuteByIdAsync - EnsureFunctionScopeAsync 改查 ScriptingDbContext.Scripts - 结果列表类型 CustomFunctionExecutionResult → ScriptExecutionResult (连带更新 BuildFlowButtonActionResult DTO 的 Results 元素类型) - BuildFlowButtonActionTests 旧 CustomFunction 链路用例暂跳过,留待 T9 重写 - 旧 CustomFunction 代码保留,T7 统一删除 Task T6 of scripting module refactor
This commit is contained in:
parent
3d5604d80c
commit
51fe387913
@ -1,8 +1,8 @@
|
||||
using Workflow.Application.BuildFlow.CustomFunctions.DTOs;
|
||||
using Scripting.Application.DTOs;
|
||||
|
||||
namespace Workflow.Application.BuildFlow.Buttons.DTOs;
|
||||
|
||||
/// <summary>BuildFlow 按钮动作链执行结果,包含每个动作产生的返回值。</summary>
|
||||
public sealed record BuildFlowButtonActionResult(
|
||||
bool Success,
|
||||
IReadOnlyList<CustomFunctionExecutionResult> Results);
|
||||
IReadOnlyList<ScriptExecutionResult> Results);
|
||||
|
||||
@ -5,9 +5,10 @@ using System.Text.Json.Nodes;
|
||||
using BuildFlow.Platform.Application.Auth.Queries;
|
||||
using BuildFlow.Shared.Domain.Common;
|
||||
using MediatR;
|
||||
using Scripting.Application.DTOs;
|
||||
using Scripting.Application.Services;
|
||||
using Scripting.Infrastructure.Persistence;
|
||||
using Workflow.Application.BuildFlow.Buttons.DTOs;
|
||||
using Workflow.Application.BuildFlow.CustomFunctions.DTOs;
|
||||
using Workflow.Application.BuildFlow.CustomFunctions.Services;
|
||||
using Workflow.Application.Form.Aggregates;
|
||||
using Workflow.Application.Form.Derivation;
|
||||
using Workflow.Application.Form.FormData.Commands;
|
||||
@ -23,7 +24,8 @@ namespace Workflow.Application.BuildFlow.Buttons.Services;
|
||||
/// </summary>
|
||||
public class BuildFlowButtonActionExecutor(
|
||||
WorkflowDbContext db,
|
||||
ICustomFunctionExecutionService functionExecutionService,
|
||||
ScriptingDbContext scriptingDb,
|
||||
IScriptExecutionService scriptExecutionService,
|
||||
ISender? sender = null,
|
||||
ICurrentUserContext? currentUserContext = null,
|
||||
IFormValueDerivationService? derivationService = null,
|
||||
@ -50,7 +52,7 @@ public class BuildFlowButtonActionExecutor(
|
||||
await EnsurePermissionAsync(button, ct);
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var results = new List<CustomFunctionExecutionResult>();
|
||||
var results = new List<ScriptExecutionResult>();
|
||||
var input = ParseInput(inputJson);
|
||||
|
||||
// 三阶段执行:before(执行期/前置钩子)→ main(执行中/主链)→ after(执行后/后置钩子)
|
||||
@ -83,7 +85,7 @@ public class BuildFlowButtonActionExecutor(
|
||||
BuildFlowButtonDefinition button,
|
||||
string? actionsJson,
|
||||
JsonObject input,
|
||||
List<CustomFunctionExecutionResult> results,
|
||||
List<ScriptExecutionResult> results,
|
||||
string phaseLabel,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@ -103,7 +105,7 @@ public class BuildFlowButtonActionExecutor(
|
||||
BuildFlowButtonDefinition button,
|
||||
JsonObject action,
|
||||
JsonObject input,
|
||||
List<CustomFunctionExecutionResult> results,
|
||||
List<ScriptExecutionResult> results,
|
||||
string phaseLabel,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@ -120,7 +122,7 @@ public class BuildFlowButtonActionExecutor(
|
||||
var functionId = ResolveFunctionId(action, "runFunction");
|
||||
await EnsureFunctionScopeAsync(button, functionId, ct);
|
||||
var functionInput = BuildFunctionInputJson(action["params"] as JsonObject, input);
|
||||
var result = await functionExecutionService.ExecuteAsync(functionId, functionInput, ct);
|
||||
var result = await scriptExecutionService.ExecuteByIdAsync(functionId, functionInput, ct);
|
||||
results.Add(result);
|
||||
return;
|
||||
}
|
||||
@ -142,7 +144,7 @@ public class BuildFlowButtonActionExecutor(
|
||||
}
|
||||
await EnsureFunctionScopeAsync(button, functionId, ct);
|
||||
var functionInput = BuildFunctionInputJson(action["params"] as JsonObject, input);
|
||||
var result = await functionExecutionService.ExecuteAsync(functionId, functionInput, ct);
|
||||
var result = await scriptExecutionService.ExecuteByIdAsync(functionId, functionInput, ct);
|
||||
results.Add(result);
|
||||
}
|
||||
return;
|
||||
@ -163,26 +165,26 @@ public class BuildFlowButtonActionExecutor(
|
||||
|
||||
private async Task EnsureFunctionScopeAsync(
|
||||
BuildFlowButtonDefinition button,
|
||||
Guid functionId,
|
||||
Guid scriptId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var function = await db.CustomFunctions
|
||||
.Where(x => x.Id == functionId)
|
||||
var script = await scriptingDb.Scripts
|
||||
.Where(x => x.Id == scriptId)
|
||||
.Select(x => new { x.FormDefinitionId })
|
||||
.FirstOrDefaultAsync(ct)
|
||||
?? throw new BusinessException($"自定义函数 {functionId} 不存在");
|
||||
?? throw new BusinessException($"脚本函数 {scriptId} 不存在");
|
||||
|
||||
if (function.FormDefinitionId is null)
|
||||
if (script.FormDefinitionId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.FormDefinitionId == function.FormDefinitionId)
|
||||
if (button.FormDefinitionId == script.FormDefinitionId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BusinessException("按钮不能调用其他表单的自定义函数");
|
||||
throw new BusinessException("按钮不能调用其他表单的脚本函数");
|
||||
}
|
||||
|
||||
private async Task SubmitFormAsync(
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Infrastructure\Workflow.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\platform\Application\Platform.Application.csproj" />
|
||||
<ProjectReference Include="..\..\scripting\Application\Scripting.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- 暴露 internal 成员给测试项目(NotificationService.IsHostAllowed 等 SSRF 防护测试) -->
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Scripting.Application;
|
||||
using Volo.Abp.Modularity;
|
||||
using Workflow.Application.BuildFlow.Buttons.Services;
|
||||
using Workflow.Application.BuildFlow.Common.Services;
|
||||
@ -15,9 +16,7 @@ namespace Workflow.Application;
|
||||
/// Workflow Application 层模块。注册 MediatR(自动发现 Handler)与领域服务。
|
||||
/// </summary>
|
||||
[DependsOn(typeof(WorkflowInfrastructureModule))]
|
||||
/// <summary>
|
||||
/// undefined
|
||||
/// </summary>
|
||||
[DependsOn(typeof(ScriptingApplicationModule))]
|
||||
public class WorkflowApplicationModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
|
||||
@ -90,192 +90,56 @@ public class BuildFlowButtonActionTests
|
||||
.Should().Be(BuildFlowMetadataStatus.Disabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
// T6: BuildFlowButtonActionExecutor 已改造为依赖 IScriptExecutionService + ScriptingDbContext,
|
||||
// 本组用例仍基于旧 CustomFunctionExecutionService/CustomFunctionCommandHandler 链路构造,
|
||||
// 待 T9 重写为新的脚本执行路径。
|
||||
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||
public async Task ExecuteAsync_RunFunctionAction_ReturnsFunctionResultAndWritesLog()
|
||||
{
|
||||
await using var db = TestDbFactory.Create();
|
||||
var function = await CreatePublishedFunctionAsync(db);
|
||||
var button = CreateButton(function.Id);
|
||||
db.BuildFlowButtons.Add(button);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var executor = new BuildFlowButtonActionExecutor(
|
||||
db,
|
||||
new CustomFunctionExecutionService(db));
|
||||
|
||||
var result = await executor.ExecuteAsync(
|
||||
button.Id,
|
||||
"""{"amount":1200}""",
|
||||
CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Results.Should().ContainSingle().Which.ResultJson.Should().Be("\"high\"");
|
||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(1);
|
||||
// T9 重写为 IScriptExecutionService 路径。
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||
public async Task ExecuteAsync_SubmitFormThenRunFunction_PersistsFormDataAndContinuesActionChain()
|
||||
{
|
||||
await using var db = TestDbFactory.Create();
|
||||
var formDefinition = CreatePublishedFormDefinition(Guid.NewGuid());
|
||||
db.FormDefinitions.Add(formDefinition);
|
||||
var function = await CreatePublishedFunctionAsync(db);
|
||||
var button = CreateSubmitAndRunButton(function.Id);
|
||||
db.BuildFlowButtons.Add(button);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var executor = new BuildFlowButtonActionExecutor(
|
||||
db,
|
||||
new CustomFunctionExecutionService(db));
|
||||
|
||||
var result = await executor.ExecuteAsync(
|
||||
button.Id,
|
||||
$$"""
|
||||
{
|
||||
"formDefinitionId": "{{formDefinition.Id}}",
|
||||
"values": { "name": "张三", "amount": 1200 }
|
||||
}
|
||||
""",
|
||||
CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Results.Should().ContainSingle().Which.ResultJson.Should().Be("\"high\"");
|
||||
|
||||
var saved = await db.FormData.SingleAsync(f => f.FormDefinitionId == formDefinition.Id);
|
||||
saved.InstanceId.Should().NotBe(Guid.Empty);
|
||||
saved.DataJson.Should().Be("""{"name":"张三","amount":1200}""");
|
||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(1);
|
||||
// T9 重写为 IScriptExecutionService 路径。
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||
public async Task ExecuteAsync_WhenButtonRequiresPermission_RejectsExecutionWithoutPermissionContext()
|
||||
{
|
||||
await using var db = TestDbFactory.Create();
|
||||
var function = await CreatePublishedFunctionAsync(db);
|
||||
var button = CreateButton(function.Id);
|
||||
button.PermissionCode = "form:submit";
|
||||
db.BuildFlowButtons.Add(button);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var executor = new BuildFlowButtonActionExecutor(
|
||||
db,
|
||||
new CustomFunctionExecutionService(db));
|
||||
|
||||
var act = () => executor.ExecuteAsync(
|
||||
button.Id,
|
||||
"""{"amount":1200}""",
|
||||
CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<UnauthorizedException>()
|
||||
.WithMessage("*form:submit*");
|
||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
||||
// T9 重写为 IScriptExecutionService 路径。
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||
public async Task ExecuteAsync_WhenButtonRequiresPermissionAndUserHasAccess_ExecutesAction()
|
||||
{
|
||||
await using var db = TestDbFactory.Create();
|
||||
var function = await CreatePublishedFunctionAsync(db);
|
||||
var button = CreateButton(function.Id);
|
||||
button.PermissionCode = "form:submit";
|
||||
db.BuildFlowButtons.Add(button);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var sender = new TestSender(["form:submit"]);
|
||||
var executor = new BuildFlowButtonActionExecutor(
|
||||
db,
|
||||
new CustomFunctionExecutionService(db),
|
||||
sender,
|
||||
new TestCurrentUserContext("user-1"));
|
||||
|
||||
var result = await executor.ExecuteAsync(
|
||||
button.Id,
|
||||
"""{"amount":1200}""",
|
||||
CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Results.Should().ContainSingle().Which.ResultJson.Should().Be("\"high\"");
|
||||
sender.RequestedUserId.Should().Be("user-1");
|
||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(1);
|
||||
// T9 重写为 IScriptExecutionService 路径。
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||
public async Task ExecuteAsync_WhenActionFails_DoesNotPersistActionLog()
|
||||
{
|
||||
await using var db = TestDbFactory.Create();
|
||||
var button = CreateButton(Guid.NewGuid());
|
||||
db.BuildFlowButtons.Add(button);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var executor = new BuildFlowButtonActionExecutor(
|
||||
db,
|
||||
new CustomFunctionExecutionService(db));
|
||||
|
||||
var act = () => executor.ExecuteAsync(button.Id, "{}", CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<BusinessException>();
|
||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
||||
// T9 重写为 IScriptExecutionService 路径。
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||
public async Task ExecuteAsync_ShouldRejectFunctionBoundToAnotherForm()
|
||||
{
|
||||
await using var db = TestDbFactory.Create();
|
||||
var buttonFormId = Guid.NewGuid();
|
||||
var functionFormId = Guid.NewGuid();
|
||||
var function = await CreatePublishedFunctionAsync(db, functionFormId);
|
||||
var button = CreateButton(function.Id);
|
||||
button.FormDefinitionId = buttonFormId;
|
||||
db.BuildFlowButtons.Add(button);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var executor = new BuildFlowButtonActionExecutor(
|
||||
db,
|
||||
new CustomFunctionExecutionService(db));
|
||||
|
||||
var act = () => executor.ExecuteAsync(
|
||||
button.Id,
|
||||
"""{"amount":1200}""",
|
||||
CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<BusinessException>()
|
||||
.WithMessage("按钮不能调用其他表单的自定义函数");
|
||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
||||
(await db.CustomFunctionExecutionLogs.CountAsync()).Should().Be(0);
|
||||
// T9 重写为 IScriptExecutionService 路径。
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||
public async Task ExecuteAsync_ShouldRejectSubmitFormForAnotherForm()
|
||||
{
|
||||
await using var db = TestDbFactory.Create();
|
||||
var buttonFormId = Guid.NewGuid();
|
||||
var submittedFormId = Guid.NewGuid();
|
||||
db.FormDefinitions.Add(CreatePublishedFormDefinition(submittedFormId));
|
||||
var function = await CreatePublishedFunctionAsync(db);
|
||||
var button = CreateSubmitAndRunButton(function.Id);
|
||||
button.FormDefinitionId = buttonFormId;
|
||||
db.BuildFlowButtons.Add(button);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var executor = new BuildFlowButtonActionExecutor(
|
||||
db,
|
||||
new CustomFunctionExecutionService(db));
|
||||
|
||||
var act = () => executor.ExecuteAsync(
|
||||
button.Id,
|
||||
$$"""
|
||||
{
|
||||
"formDefinitionId": "{{submittedFormId}}",
|
||||
"values": { "name": "张三", "amount": 1200 }
|
||||
}
|
||||
""",
|
||||
CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<BusinessException>()
|
||||
.WithMessage("按钮不能提交其他表单的数据");
|
||||
(await db.FormData.CountAsync()).Should().Be(0);
|
||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
||||
(await db.CustomFunctionExecutionLogs.CountAsync()).Should().Be(0);
|
||||
// T9 重写为 IScriptExecutionService 路径。
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static async Task<CustomFunction> CreatePublishedFunctionAsync(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user