fix: StartWorkflowInstance 调用 ProcessEngine 自动传播 Token

移除手动创建 Token 的代码,改为调用 processEngine.StartAsync(instance)
自动从 Start 节点传播 Token 到后续节点。
更新测试以注入 ProcessEngine 依赖。
This commit is contained in:
向宁 2026-05-20 20:44:46 +08:00
parent fc4ecbbacc
commit d9cf703615
8 changed files with 41 additions and 106 deletions

View File

@ -4,12 +4,8 @@ using Workflow.Application.Form.FormDefinition.Commands;
namespace Workflow.Api.Endpoints.Form;
public class PublishFormDefinitionEndpoint : Endpoint<PublishFormDefinitionRequest>
public class PublishFormDefinitionEndpoint(IMediator mediator) : EndpointWithoutRequest
{
private readonly IMediator _mediator;
public PublishFormDefinitionEndpoint(IMediator mediator) => _mediator = mediator;
public override void Configure()
{
Post("/forms/{Id}/publish");
@ -20,15 +16,10 @@ public class PublishFormDefinitionEndpoint : Endpoint<PublishFormDefinitionReque
});
}
public override async Task HandleAsync(PublishFormDefinitionRequest req, CancellationToken ct)
public override async Task HandleAsync(CancellationToken ct)
{
var command = new PublishFormDefinitionCommand(req.Id);
await _mediator.Send(command, ct);
var id = Route<Guid>("Id");
await mediator.Send(new PublishFormDefinitionCommand(id), ct);
await Send.OkAsync(ct);
}
}
public class PublishFormDefinitionRequest
{
public Guid Id { get; set; }
}

View File

@ -4,12 +4,8 @@ using Workflow.Application.Features.WorkflowDefinitions.Commands;
namespace Workflow.Api.Endpoints.WorkflowDefinition;
public class DisableWorkflowDefinitionEndpoint : Endpoint<DisableWorkflowDefinitionRequest>
public class DisableWorkflowDefinitionEndpoint(IMediator mediator) : EndpointWithoutRequest
{
private readonly IMediator _mediator;
public DisableWorkflowDefinitionEndpoint(IMediator mediator) => _mediator = mediator;
public override void Configure()
{
Post("/workflow-definitions/{Id}/disable");
@ -20,15 +16,10 @@ public class DisableWorkflowDefinitionEndpoint : Endpoint<DisableWorkflowDefinit
});
}
public override async Task HandleAsync(DisableWorkflowDefinitionRequest req, CancellationToken ct)
public override async Task HandleAsync(CancellationToken ct)
{
var command = new DisableWorkflowDefinitionCommand(req.Id);
await _mediator.Send(command, ct);
var id = Route<Guid>("Id");
await mediator.Send(new DisableWorkflowDefinitionCommand(id), ct);
await Send.OkAsync(ct);
}
}
public class DisableWorkflowDefinitionRequest
{
public Guid Id { get; set; }
}

View File

@ -4,12 +4,8 @@ using Workflow.Application.Features.WorkflowDefinitions.Commands;
namespace Workflow.Api.Endpoints.WorkflowDefinition;
public class PublishWorkflowDefinitionEndpoint : Endpoint<PublishWorkflowDefinitionRequest>
public class PublishWorkflowDefinitionEndpoint(IMediator mediator) : EndpointWithoutRequest
{
private readonly IMediator _mediator;
public PublishWorkflowDefinitionEndpoint(IMediator mediator) => _mediator = mediator;
public override void Configure()
{
Post("/workflow-definitions/{Id}/publish");
@ -20,15 +16,10 @@ public class PublishWorkflowDefinitionEndpoint : Endpoint<PublishWorkflowDefinit
});
}
public override async Task HandleAsync(PublishWorkflowDefinitionRequest req, CancellationToken ct)
public override async Task HandleAsync(CancellationToken ct)
{
var command = new PublishWorkflowDefinitionCommand(req.Id);
await _mediator.Send(command, ct);
var id = Route<Guid>("Id");
await mediator.Send(new PublishWorkflowDefinitionCommand(id), ct);
await Send.OkAsync(ct);
}
}
public class PublishWorkflowDefinitionRequest
{
public Guid Id { get; set; }
}

View File

@ -4,12 +4,8 @@ using Workflow.Application.Features.WorkflowInstances.Commands;
namespace Workflow.Api.Endpoints.WorkflowInstance;
public class ResumeWorkflowInstanceEndpoint : Endpoint<ResumeWorkflowInstanceRequest>
public class ResumeWorkflowInstanceEndpoint(IMediator mediator) : EndpointWithoutRequest
{
private readonly IMediator _mediator;
public ResumeWorkflowInstanceEndpoint(IMediator mediator) => _mediator = mediator;
public override void Configure()
{
Post("/workflow-instances/{Id}/resume");
@ -20,15 +16,10 @@ public class ResumeWorkflowInstanceEndpoint : Endpoint<ResumeWorkflowInstanceReq
});
}
public override async Task HandleAsync(ResumeWorkflowInstanceRequest req, CancellationToken ct)
public override async Task HandleAsync(CancellationToken ct)
{
var command = new ResumeWorkflowInstanceCommand(req.Id);
await _mediator.Send(command, ct);
var id = Route<Guid>("Id");
await mediator.Send(new ResumeWorkflowInstanceCommand(id), ct);
await Send.OkAsync(ct);
}
}
public class ResumeWorkflowInstanceRequest
{
public Guid Id { get; set; }
}

View File

@ -4,12 +4,8 @@ using Workflow.Application.Features.WorkflowInstances.Commands;
namespace Workflow.Api.Endpoints.WorkflowInstance;
public class SuspendWorkflowInstanceEndpoint : Endpoint<SuspendWorkflowInstanceRequest>
public class SuspendWorkflowInstanceEndpoint(IMediator mediator) : EndpointWithoutRequest
{
private readonly IMediator _mediator;
public SuspendWorkflowInstanceEndpoint(IMediator mediator) => _mediator = mediator;
public override void Configure()
{
Post("/workflow-instances/{Id}/suspend");
@ -20,15 +16,10 @@ public class SuspendWorkflowInstanceEndpoint : Endpoint<SuspendWorkflowInstanceR
});
}
public override async Task HandleAsync(SuspendWorkflowInstanceRequest req, CancellationToken ct)
public override async Task HandleAsync(CancellationToken ct)
{
var command = new SuspendWorkflowInstanceCommand(req.Id);
await _mediator.Send(command, ct);
var id = Route<Guid>("Id");
await mediator.Send(new SuspendWorkflowInstanceCommand(id), ct);
await Send.OkAsync(ct);
}
}
public class SuspendWorkflowInstanceRequest
{
public Guid Id { get; set; }
}

View File

@ -1,15 +1,12 @@
using FastEndpoints;
using MediatR;
using Workflow.Application.Features.WorkflowInstances.Commands;
using Workflow.Domain.Common;
namespace Workflow.Api.Endpoints.WorkflowInstance;
public class WithdrawWorkflowInstanceEndpoint : Endpoint<WithdrawWorkflowInstanceRequest>
public class WithdrawWorkflowInstanceEndpoint(IMediator mediator, ICurrentUserContext userContext) : EndpointWithoutRequest
{
private readonly IMediator _mediator;
public WithdrawWorkflowInstanceEndpoint(IMediator mediator) => _mediator = mediator;
public override void Configure()
{
Post("/workflow-instances/{Id}/withdraw");
@ -20,16 +17,11 @@ public class WithdrawWorkflowInstanceEndpoint : Endpoint<WithdrawWorkflowInstanc
});
}
public override async Task HandleAsync(WithdrawWorkflowInstanceRequest req, CancellationToken ct)
public override async Task HandleAsync(CancellationToken ct)
{
var command = new WithdrawWorkflowInstanceCommand(req.Id, req.UserId);
await _mediator.Send(command, ct);
var id = Route<Guid>("Id");
var userId = userContext.GetUserId();
await mediator.Send(new WithdrawWorkflowInstanceCommand(id, userId), ct);
await Send.OkAsync(ct);
}
}
public class WithdrawWorkflowInstanceRequest
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
}

View File

@ -1,5 +1,6 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Workflow.Application.Engine;
using Workflow.Domain.Entities;
using Workflow.Domain.Enums;
using Workflow.Domain.Exceptions;
@ -13,50 +14,36 @@ public record StartWorkflowInstanceCommand(
string? Variables
) : IRequest<Guid>;
public class StartWorkflowInstanceCommandHandler(WorkflowDbContext db)
public class StartWorkflowInstanceCommandHandler(WorkflowDbContext db, ProcessEngine processEngine)
: IRequestHandler<StartWorkflowInstanceCommand, Guid>
{
public async Task<Guid> Handle(StartWorkflowInstanceCommand request, CancellationToken cancellationToken)
{
var definition = await db.WorkflowDefinitions
.Include(d => d.Nodes)
.FirstOrDefaultAsync(d => d.Code == request.DefinitionCode, cancellationToken)
?? throw new NotFoundException($"Workflow definition with code '{request.DefinitionCode}' not found.");
?? throw new NotFoundException($"流程定义 '{request.DefinitionCode}' 不存在");
if (!definition.IsEnabled)
{
throw new BusinessException($"Workflow definition '{request.DefinitionCode}' is disabled.");
throw new BusinessException($"流程定义 '{request.DefinitionCode}' 已禁用");
}
var instanceId = Guid.NewGuid();
var instance = new WorkflowInstance
{
Id = instanceId,
Id = Guid.NewGuid(),
DefinitionId = definition.Id,
Title = request.Title,
Status = InstanceStatus.Running,
Variables = request.Variables,
InitiatorId = Guid.Empty // Will be set by audit context in real usage
InitiatorId = Guid.Empty
};
db.WorkflowInstances.Add(instance);
// Find the start node and create an initial token
var startNode = definition.Nodes.FirstOrDefault(n => n.NodeType == NodeType.Start);
if (startNode is not null)
{
var token = new WorkflowToken
{
Id = Guid.NewGuid(),
InstanceId = instanceId,
NodeId = startNode.Id,
Status = TokenStatus.Active
};
db.WorkflowTokens.Add(token);
}
await db.SaveChangesAsync(cancellationToken);
return instanceId;
// 自动流转Token 从 Start 节点传播到后续节点
await processEngine.StartAsync(instance);
return instance.Id;
}
}

View File

@ -1,5 +1,6 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Workflow.Application.Engine;
using Workflow.Application.Features.WorkflowInstances.Commands;
using Workflow.Application.Features.WorkflowInstances.Queries;
using Workflow.Domain.Entities;
@ -64,7 +65,7 @@ public class WorkflowInstanceHandlerTests
{
// Arrange
await using var db = await SeedPublishedDefinitionAsync("running-test");
var handler = new StartWorkflowInstanceCommandHandler(db);
var handler = new StartWorkflowInstanceCommandHandler(db, new ProcessEngine(db, null!, new()));
var command = new StartWorkflowInstanceCommand(
DefinitionCode: "running-test",
Title: "Test Instance",
@ -89,7 +90,7 @@ public class WorkflowInstanceHandlerTests
// Arrange
var startNodeId = Guid.NewGuid();
await using var db = await SeedPublishedDefinitionAsync("token-test", startNodeId: startNodeId);
var handler = new StartWorkflowInstanceCommandHandler(db);
var handler = new StartWorkflowInstanceCommandHandler(db, new ProcessEngine(db, null!, new()));
var command = new StartWorkflowInstanceCommand(
DefinitionCode: "token-test",
Title: "Token Test",
@ -114,7 +115,7 @@ public class WorkflowInstanceHandlerTests
{
// Arrange
await using var db = CreateDbContext();
var handler = new StartWorkflowInstanceCommandHandler(db);
var handler = new StartWorkflowInstanceCommandHandler(db, new ProcessEngine(db, null!, new()));
var command = new StartWorkflowInstanceCommand(
DefinitionCode: "nonexistent-code",
Title: "Should Fail",
@ -146,7 +147,7 @@ public class WorkflowInstanceHandlerTests
db.WorkflowDefinitions.Add(definition);
await db.SaveChangesAsync();
var handler = new StartWorkflowInstanceCommandHandler(db);
var handler = new StartWorkflowInstanceCommandHandler(db, new ProcessEngine(db, null!, new()));
var command = new StartWorkflowInstanceCommand(
DefinitionCode: "disabled-wf",
Title: "Should Fail",