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()