diff --git a/src/BuildFlow.Host/Endpoints/Workflow/WorkflowInstance/StartWorkflowInstanceEndpoint.cs b/src/BuildFlow.Host/Endpoints/Workflow/WorkflowInstance/StartWorkflowInstanceEndpoint.cs index 208d7d0..efe60e1 100644 --- a/src/BuildFlow.Host/Endpoints/Workflow/WorkflowInstance/StartWorkflowInstanceEndpoint.cs +++ b/src/BuildFlow.Host/Endpoints/Workflow/WorkflowInstance/StartWorkflowInstanceEndpoint.cs @@ -1,18 +1,18 @@ using MediatR; using Workflow.Application.Features.WorkflowInstances.Commands; +using BuildFlow.Shared.Domain.Common; +using BuildFlow.Host.Services.Users; namespace BuildFlow.Host.Endpoints.Workflow.WorkflowInstance; /// -/// 发起流程实例端点。POST /instances,按定义创建实例并产生首个 token。 +/// 发起流程实例端点。POST /workflow-instances,按定义创建实例并产生首个 token。 /// -public class StartWorkflowInstanceEndpoint : Endpoint +public class StartWorkflowInstanceEndpoint( + IMediator mediator, + ICurrentUserContext currentUserContext) : Endpoint { - private readonly IMediator _mediator; - - public StartWorkflowInstanceEndpoint(IMediator mediator) => _mediator = mediator; - public override void Configure() { Post("/workflow-instances"); @@ -25,8 +25,14 @@ public class StartWorkflowInstanceEndpoint : Endpoint AnswerAsync(string question, Guid conversationId, CancellationToken ct) + public async Task AnswerAsync(string question, Guid conversationId, Guid initiatorId, CancellationToken ct) { // 串联链路4:AI 对话 → 触发工作流 // 检测"发起/启动/申请 XXX 流程"等意图 - var triggerResult = await TryDetectWorkflowIntentAsync(question, ct); + var triggerResult = await TryDetectWorkflowIntentAsync(question, initiatorId, ct); if (triggerResult is not null) return triggerResult; @@ -49,7 +49,7 @@ public class AiBotHandler( /// 检测工作流触发意图(如"帮我发起报销流程")。 /// 命中则触发对应流程并返回结果消息,未命中返回 null 走默认 RAG 问答。 /// - private async Task TryDetectWorkflowIntentAsync(string question, CancellationToken ct) + private async Task TryDetectWorkflowIntentAsync(string question, Guid initiatorId, CancellationToken ct) { var lowerQ = question.ToLowerInvariant(); @@ -69,7 +69,7 @@ public class AiBotHandler( if (string.IsNullOrWhiteSpace(flowName)) return null; - var result = await mediator.Send(new StartWorkflowByNameCommand(flowName, Guid.Empty, null), ct); + var result = await mediator.Send(new StartWorkflowByNameCommand(flowName, initiatorId, null), ct); if (result.Success) { diff --git a/src/modules/workflow/Application/Features/WorkflowInstances/Commands/StartWorkflowInstanceCommand.cs b/src/modules/workflow/Application/Features/WorkflowInstances/Commands/StartWorkflowInstanceCommand.cs index 26b7226..d7c1f52 100644 --- a/src/modules/workflow/Application/Features/WorkflowInstances/Commands/StartWorkflowInstanceCommand.cs +++ b/src/modules/workflow/Application/Features/WorkflowInstances/Commands/StartWorkflowInstanceCommand.cs @@ -6,11 +6,13 @@ namespace Workflow.Application.Features.WorkflowInstances.Commands; /// /// 发起WorkflowInstance命令。封装写操作的入参与意图。 /// +/// 发起人用户 ID。写入 WorkflowInstance.InitiatorId,供撤回鉴权与发起人通知使用。 public record StartWorkflowInstanceCommand( string DefinitionCode, string Title, string? Variables, - string? FormDataJson + string? FormDataJson, + Guid InitiatorId ) : IRequest; /// diff --git a/src/modules/workflow/Application/Features/WorkflowInstances/Handlers/StartWorkflowInstanceCommandHandler.cs b/src/modules/workflow/Application/Features/WorkflowInstances/Handlers/StartWorkflowInstanceCommandHandler.cs index 3a67527..c694caa 100644 --- a/src/modules/workflow/Application/Features/WorkflowInstances/Handlers/StartWorkflowInstanceCommandHandler.cs +++ b/src/modules/workflow/Application/Features/WorkflowInstances/Handlers/StartWorkflowInstanceCommandHandler.cs @@ -83,7 +83,7 @@ public class StartWorkflowInstanceCommandHandler( Title = request.Title, Status = InstanceStatus.Running, Variables = BuildVariablesJson(normalizedFormDataJson, request.Variables), - InitiatorId = Guid.Empty + InitiatorId = request.InitiatorId }; db.WorkflowInstances.Add(instance); diff --git a/src/modules/workflow/Application/Integration/Handlers/StartWorkflowByNameCommandHandler.cs b/src/modules/workflow/Application/Integration/Handlers/StartWorkflowByNameCommandHandler.cs index 6ea9e55..0c43b72 100644 --- a/src/modules/workflow/Application/Integration/Handlers/StartWorkflowByNameCommandHandler.cs +++ b/src/modules/workflow/Application/Integration/Handlers/StartWorkflowByNameCommandHandler.cs @@ -40,7 +40,8 @@ public sealed class StartWorkflowByNameCommandHandler( definition.Code ?? definition.Id.ToString(), $"{request.DefinitionKey}-{DateTime.UtcNow:MMddHHmm}", request.Variables, - null), + null, + request.InitiatorId), ct); return new WorkflowTriggerResultDto(true, instanceId, request.DefinitionKey, null); diff --git a/src/shared/BuildFlow.Shared/Integration/IAiBotHandler.cs b/src/shared/BuildFlow.Shared/Integration/IAiBotHandler.cs index 9c9cfb2..59f20f4 100644 --- a/src/shared/BuildFlow.Shared/Integration/IAiBotHandler.cs +++ b/src/shared/BuildFlow.Shared/Integration/IAiBotHandler.cs @@ -22,7 +22,8 @@ public interface IAiBotHandler /// /// 用户提问内容(去掉 @AI 前缀后的纯问题) /// IM 会话 ID(可用于关联知识库) + /// 提问发起人用户 ID(触发工作流时作为实例发起人) /// 取消令牌 /// AI 回答文本 - Task AnswerAsync(string question, Guid conversationId, CancellationToken ct); + Task AnswerAsync(string question, Guid conversationId, Guid initiatorId, CancellationToken ct); } diff --git a/tests/BuildFlow.Workflow.Tests/Handlers/WorkflowInstanceHandlerTests.cs b/tests/BuildFlow.Workflow.Tests/Handlers/WorkflowInstanceHandlerTests.cs index 3094be1..703662c 100644 --- a/tests/BuildFlow.Workflow.Tests/Handlers/WorkflowInstanceHandlerTests.cs +++ b/tests/BuildFlow.Workflow.Tests/Handlers/WorkflowInstanceHandlerTests.cs @@ -17,6 +17,8 @@ namespace BuildFlow.Workflow.Tests.Handlers; public class WorkflowInstanceHandlerTests { + private static readonly Guid TestInitiatorId = Guid.Parse("11111111-1111-1111-1111-111111111111"); + private const string ComputedExpenseSchema = """ { "type": "object", @@ -123,8 +125,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "running-test", Title: "Test Instance", Variables: null, - FormDataJson: null - ); + FormDataJson: null, + InitiatorId: TestInitiatorId); // Act var instanceId = await handler.Handle(command, CancellationToken.None); @@ -136,6 +138,8 @@ public class WorkflowInstanceHandlerTests instance.Should().NotBeNull(); instance!.Status.Should().Be(InstanceStatus.Running); instance.Title.Should().Be("Test Instance"); + // 发起人必须正确写入,供撤回鉴权与发起人通知使用(修复历史 Guid.Empty 缺陷) + instance.InitiatorId.Should().Be(TestInitiatorId); } [Fact] @@ -149,8 +153,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "token-test", Title: "Token Test", Variables: null, - FormDataJson: null - ); + FormDataJson: null, + InitiatorId: TestInitiatorId); // Act var instanceId = await handler.Handle(command, CancellationToken.None); @@ -175,8 +179,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "nonexistent-code", Title: "Should Fail", Variables: null, - FormDataJson: null - ); + FormDataJson: null, + InitiatorId: TestInitiatorId); // Act var act = () => handler.Handle(command, CancellationToken.None); @@ -208,8 +212,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "disabled-wf", Title: "Should Fail", Variables: null, - FormDataJson: null - ); + FormDataJson: null, + InitiatorId: TestInitiatorId); // Act var act = () => handler.Handle(command, CancellationToken.None); @@ -243,8 +247,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "form-vars-test", Title: "Expense", Variables: null, - FormDataJson: """{"amount":6500,"category":"travel"}""" - ); + FormDataJson: """{"amount":6500,"category":"travel"}""", + InitiatorId: TestInitiatorId); // Act var instanceId = await handler.Handle(command, CancellationToken.None); @@ -286,7 +290,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "computed-start-form-test", Title: "Computed Start", Variables: null, - FormDataJson: """{"quantity":2,"unitPrice":15,"total":1}"""), + FormDataJson: """{"quantity":2,"unitPrice":15,"total":1}""", + InitiatorId: TestInitiatorId), CancellationToken.None); var savedData = await db.FormData.SingleAsync(f => f.InstanceId == instanceId); @@ -326,8 +331,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "deleted-start-form-test", Title: "X", Variables: null, - FormDataJson: """{"amount":100}""" - ); + FormDataJson: """{"amount":100}""", + InitiatorId: TestInitiatorId); // Act var act = () => handler.Handle(command, CancellationToken.None); @@ -360,8 +365,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "nonexistent-start-form-test", Title: "X", Variables: null, - FormDataJson: """{"amount":100}""" - ); + FormDataJson: """{"amount":100}""", + InitiatorId: TestInitiatorId); var act = () => handler.Handle(command, CancellationToken.None); @@ -396,8 +401,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "disabled-start-form-test", Title: "X", Variables: null, - FormDataJson: """{"amount":100}""" - ); + FormDataJson: """{"amount":100}""", + InitiatorId: TestInitiatorId); var act = () => handler.Handle(command, CancellationToken.None); @@ -433,8 +438,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "merge-vars-test", Title: "Leave", Variables: """{"days":5,"urgent":true}""", - FormDataJson: """{"days":2,"leaveType":"annual"}""" - ); + FormDataJson: """{"days":2,"leaveType":"annual"}""", + InitiatorId: TestInitiatorId); // Act var instanceId = await handler.Handle(command, CancellationToken.None); @@ -484,8 +489,8 @@ public class WorkflowInstanceHandlerTests DefinitionCode: "invalid-start-form-test", Title: "Invalid Start Form", Variables: null, - FormDataJson: """{"name":""}""" - ); + FormDataJson: """{"name":""}""", + InitiatorId: TestInitiatorId); // Act var act = () => handler.Handle(command, CancellationToken.None); @@ -559,7 +564,8 @@ public class WorkflowInstanceHandlerTests "nested-start-form-test", "嵌套合法发起", null, - """{"leaveType":"annual","dateRange":{"startDate":"2026-06-15","endDate":"2026-06-16"}}"""), + """{"leaveType":"annual","dateRange":{"startDate":"2026-06-15","endDate":"2026-06-16"}}""", + TestInitiatorId), CancellationToken.None); instanceId.Should().NotBeEmpty(); @@ -571,7 +577,8 @@ public class WorkflowInstanceHandlerTests "nested-start-form-test", "嵌套缺失", null, - """{"leaveType":"annual","dateRange":{}}"""), + """{"leaveType":"annual","dateRange":{}}""", + TestInitiatorId), CancellationToken.None); await act.Should().ThrowAsync()