Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Das Menschliche Interaktionsmuster beschreibt Workflows, die anhalten und warten, bis die Eingaben einer Person ausgeführt werden, bevor sie fortfahren. Das Muster ist nützlich für Genehmigungsworkflows, die mehrstufige Authentifizierung und jedes Szenario, in dem eine Person innerhalb eines Zeitlimits reagiert.
In diesem Beispiel wird gezeigt, wie Sie eine Durable Functions-Orchestrierung erstellen, die menschliche Interaktion umfasst. Im Beispiel wird ein SMS-basiertes Telefonüberprüfungssystem implementiert. Es ist üblich in Abläufen zur Telefonnummernverifizierung und zur mehrstufigen Authentifizierung (MFA).
Hinweis
Version 4 des Node.js Programmiermodells für Azure Functions ist allgemein verfügbar. Das v4-Modell wurde entwickelt, um eine flexiblere und intuitivere Oberfläche für JavaScript- und TypeScript-Entwickler bereitzustellen. Weitere Informationen zu den Unterschieden zwischen v3 und v4 finden Sie im Migrationshandbuch.
In den folgenden Codeausschnitten bezeichnet JavaScript (PM4) das Programmiermodell v4, die neue Oberfläche.
Voraussetzungen
In diesem Artikel wird gezeigt, wie Sie das menschliche Interaktionsmuster mithilfe der SdKs für dauerhafte Aufgaben implementieren. Im Beispiel wird ein Genehmigungsworkflow implementiert, bei dem eine Orchestrierung darauf wartet, dass eine Person eine Anfrage genehmigt oder ablehnt, bevor der Prozess fortgesetzt wird.
Beschreibung des Szenarios
Die Telefonüberprüfung hilft zu bestätigen, dass Personen, die Ihre App verwenden, keine Spammer sind und dass sie die von ihnen bereitgestellte Telefonnummer steuern. Die mehrstufige Authentifizierung ist eine gängige Methode zum Schutz von Konten. Das Erstellen einer eigenen Telefonüberprüfung erfordert eine zustandsbehaftete Interaktion mit einer Person. Ein Benutzer erhält in der Regel einen Code (z. B. eine vierstellige Zahl) und muss in angemessener Zeit reagieren.
Standard-Azure Functions sind zustandslos (wie viele andere Cloud-Endpunkte), daher müssen Sie den Zustand in einer Datenbank oder einem anderen persistenten Speicher speichern. Sie teilen die Interaktion auch über mehrere Funktionen auf und koordinieren sie. Beispielsweise generiert eine Funktion einen Code, speichert ihn und sendet ihn an das Telefon des Benutzers. Eine andere Funktion empfängt die Antwort des Benutzers und ordnet sie der ursprünglichen Anforderung zu, um den Code zu überprüfen. Fügen Sie ein Timeout hinzu, um die Sicherheit zu schützen. Dieser Workflow wird schnell komplex.
Durable Functions verringert die Komplexität dieses Szenarios. In diesem Beispiel verwaltet eine Orchestratorfunktion die zustandsbehaftete Interaktion ohne einen externen Datenspeicher. Da Orchestratorfunktionen dauerhaft sind, sind diese interaktiven Flüsse sehr zuverlässig.
Genehmigungsworkflows sind in Geschäftsanwendungen üblich, bei denen eine Anforderung von einem Menschen überprüft werden muss, bevor sie fortfahren. Die Workflowanforderungen sind:
- Warten Sie unbegrenzt auf eine menschliche Antwort oder bis zu einem Timeout
- Behandeln von Genehmigungs- und Ablehnungsergebnissen
- Support-Timeouts wenn keine Antwort eingeht
- Nachverfolgen des Status , damit der Antragsteller den Fortschritt überprüfen kann
Die SDKs für dauerhafte Aufgaben vereinfachen dieses Szenario mit:
- Externe Ereignisse: Die Orchestrierung kann anhalten und auf ein Ereignis warten, das von einem externen System oder Benutzer ausgelöst wurde.
- Dauerhafte Timer: Festlegen eines Timeouts, das ausgelöst wird, wenn keine Antwort empfangen wird
- Benutzerdefinierter Status: Nachverfolgen und Verfügbarmachen des aktuellen Workflowstatus für Clients
Konfigurieren der Twilio-Integration
Dieses Beispiel beinhaltet die Verwendung des Twilio-Diensts zum Senden von SMS-Nachrichten an ein Mobiltelefon. Azure Functions bietet bereits Unterstützung für Twilio über die Twilio binding und das Beispiel verwendet dieses Feature.
Zunächst benötigen Sie ein Twilio-Konto. Unter https://www.twilio.com/try-twilio können Sie kostenlos eines erstellen. Fügen Sie nach der Einrichtung Ihres Kontos die folgenden drei App-Einstellungen zu Ihrer Funktions-App hinzu.
| Name der App-Einstellung | Wertbeschreibung |
|---|---|
| TwilioAccountSid | Die SID für Ihr Twilio-Konto |
| TwilioAuthToken | Das Authentifizierungstoken für Ihr Twilio-Konto |
| TwilioPhoneNumber | Die Ihrem Twilio-Konto zugeordnete Telefonnummer. Diese wird für das Senden von SMS-Nachrichten verwendet. |
Der Orchestrator und die Aktivitäten
In diesem Artikel werden die folgenden Funktionen in der Beispiel-App behandelt:
-
E4_SmsPhoneVerification: Eine Orchestrator-Funktion, die den Telefonverifizierungsprozess ausführt und Timeouts sowie Wiederholungen verwaltet. -
E4_SendSmsChallenge: Eine Aktivitätsfunktion , die einen Code nach Textnachricht sendet.
Hinweis
Die HttpStart Funktion in der Beispiel-App und die Schnellstartanleitung fungiert als Orchestrierungsclient und löst die Orchestratorfunktion aus.
In diesem Artikel werden die folgenden Komponenten in der Beispiel-App erläutert:
-
ApprovalOrchestration/approvalOrchestrator/human_interaction_orchestrator: Ein Orchestrator, der eine Genehmigungsanforderung übermittelt und auf eine menschliche Antwort oder ein Timeout wartet. -
SubmitApprovalRequestActivity/submitRequest/submit_approval_request: Eine Aktivität, die einen menschlichen Genehmiger benachrichtigt, z. B. per E-Mail oder Chatnachricht. -
ProcessApprovalActivity/processApproval/process_approval: Eine Aktivität, die die Genehmigungsentscheidung verarbeitet.
Orchestrator
Orchestrator-Funktion „E4_SmsPhoneVerification“
[FunctionName("E4_SmsPhoneVerification")]
public static async Task<bool> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
string phoneNumber = context.GetInput<string>();
if (string.IsNullOrEmpty(phoneNumber))
{
throw new ArgumentNullException(
nameof(phoneNumber),
"A phone number input is required.");
}
int challengeCode = await context.CallActivityAsync<int>(
"E4_SendSmsChallenge",
phoneNumber);
using (var timeoutCts = new CancellationTokenSource())
{
// The user has 90 seconds to respond with the code they received in the SMS message.
DateTime expiration = context.CurrentUtcDateTime.AddSeconds(90);
Task timeoutTask = context.CreateTimer(expiration, timeoutCts.Token);
bool authorized = false;
for (int retryCount = 0; retryCount <= 3; retryCount++)
{
Task<int> challengeResponseTask =
context.WaitForExternalEvent<int>("SmsChallengeResponse");
Task winner = await Task.WhenAny(challengeResponseTask, timeoutTask);
if (winner == challengeResponseTask)
{
// We got back a response! Compare it to the challenge code.
if (challengeResponseTask.Result == challengeCode)
{
authorized = true;
break;
}
}
else
{
// Timeout expired
break;
}
}
if (!timeoutTask.IsCompleted)
{
// All pending timers must be complete or canceled before the function exits.
timeoutCts.Cancel();
}
return authorized;
}
}
Hinweis
Es kann zunächst nicht offensichtlich sein, aber dieser Orchestrator verstößt nicht gegen die deterministische Orchestrierungseinschränkung. Es ist deterministisch, da die CurrentUtcDateTime Eigenschaft die Ablaufzeit des Timers berechnet und an diesem Punkt im Orchestratorcode denselben Wert für jede Wiedergabe zurückgibt. Dieses Verhalten stellt sicher, dass winner für jeden wiederholten Aufruf von Task.WhenAny gleich ist.
Nach dem Start führt diese Orchestratorfunktion Folgendes aus:
- Ermittelt eine Telefonnummer, an die die SMS-Benachrichtigung gesendet werden soll.
- Ruft E4_SendSmsChallenge auf, eine SMS-Nachricht an den Benutzer zu senden und den erwarteten vierstelligen Abfragecode zurückzugeben.
- Erstellt einen beständigen Timer, der 90 Sekunden nach der aktuellen Zeit ausgelöst wird.
- Parallel zum Timer wartet sie auf ein SmsChallengeResponse-Ereignis des Benutzers.
Der Benutzer erhält eine SMS-Nachricht mit einem vierstelligen Code. Sie haben 90 Sekunden Zeit, um denselben Code an die Orchestratorinstanz zu senden, um die Überprüfung abzuschließen. Wenn sie den falschen Code übermitteln, erhalten sie drei weitere Versuche innerhalb des gleichen 90-Sekunden-Fensters.
Warnung
Timer abbrechen, die Sie nicht mehr benötigen. Im obigen Beispiel bricht die Orchestrierung den Timer ab, wenn sie eine Herausforderungsantwort akzeptiert.
Der Orchestrator sendet eine Genehmigungsanforderung und wartet dann auf eine menschliche Antwort oder ein Timeout.
using Microsoft.DurableTask;
using System;
using System.Threading;
using System.Threading.Tasks;
[DurableTask(nameof(ApprovalOrchestration))]
public class ApprovalOrchestration : TaskOrchestrator<ApprovalRequestData, ApprovalResult>
{
public override async Task<ApprovalResult> RunAsync(
TaskOrchestrationContext context, ApprovalRequestData input)
{
string requestId = input.RequestId;
double timeoutHours = input.TimeoutHours;
// Step 1: Submit the approval request (notify approver)
SubmissionResult submissionResult = await context.CallActivityAsync<SubmissionResult>(
nameof(SubmitApprovalRequestActivity), input);
// Make the status available via custom status
context.SetCustomStatus(submissionResult);
// Step 2: Create a durable timer for the timeout
DateTime timeoutDeadline = context.CurrentUtcDateTime.AddHours(timeoutHours);
using var timeoutCts = new CancellationTokenSource();
Task timeoutTask = context.CreateTimer(timeoutDeadline, timeoutCts.Token);
// Step 3: Wait for an external event (approval/rejection)
Task<ApprovalResponseData> approvalTask = context.WaitForExternalEvent<ApprovalResponseData>(
"approval_response");
// Step 4: Wait for either the timeout or the approval response
Task completedTask = await Task.WhenAny(approvalTask, timeoutTask);
// Step 5: Process based on which task completed
ApprovalResult result;
if (completedTask == approvalTask)
{
// Human responded in time - cancel the timeout timer
timeoutCts.Cancel();
ApprovalResponseData approvalData = approvalTask.Result;
// Process the approval
result = await context.CallActivityAsync<ApprovalResult>(
nameof(ProcessApprovalActivity),
new ProcessApprovalInput
{
RequestId = requestId,
IsApproved = approvalData.IsApproved,
Approver = approvalData.Approver
});
}
else
{
// Timeout occurred
result = new ApprovalResult
{
RequestId = requestId,
Status = "Timeout",
ProcessedAt = context.CurrentUtcDateTime.ToString("o")
};
}
return result;
}
}
Dieser Orchestrator führt die folgenden Aktionen aus:
- Sendet die Genehmigungsanforderung durch Aufrufen einer Aktivität, die den Genehmigenden benachrichtigt.
- Legt den benutzerdefinierten Status fest, damit Clients den Fortschritt nachverfolgen können.
- Erstellt einen dauerhaften Timer für den Timeouttermin.
- Wartet auf ein externes Ereignis (
approval_response), das vom Genehmiger ausgelöst wird. - Verwenden Sie
WhenAny,when_anyoderanyOf, um abzuwarten, welcher Vorgang zuerst abgeschlossen wird: die Genehmigung oder das Timeout. - Verarbeitet das Ergebnis basierend darauf, welche Aufgabe abgeschlossen wird.
Warnung
Brechen Sie Timer ab, die Sie nicht mehr benötigen. Im C#-Beispiel bricht die Orchestrierung den Timeout-Timer ab, wenn die Genehmigung eingeht.
Aktivitäten
Aktivitätsfunktion „E4_SendSmsChallenge“
Die E4_SendSmsChallenge-Funktion verwendet die Twilio-Bindung, um eine SMS-Nachricht zu senden, die einen vierstelligen Code an den Benutzer enthält.
[FunctionName("E4_SendSmsChallenge")]
public static int SendSmsChallenge(
[ActivityTrigger] string phoneNumber,
ILogger log,
[TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "%TwilioPhoneNumber%")]
out CreateMessageOptions message)
{
// Get a random number generator with a random seed (not time-based)
var rand = new Random(Guid.NewGuid().GetHashCode());
int challengeCode = rand.Next(10000);
log.LogInformation($"Sending verification code {challengeCode} to {phoneNumber}.");
message = new CreateMessageOptions(new PhoneNumber(phoneNumber));
message.Body = $"Your verification code is {challengeCode:0000}";
return challengeCode;
}
Hinweis
Um das Beispiel auszuführen, installieren Sie das Microsoft.Azure.WebJobs.Extensions.Twilio NuGet-Paket. Installieren Sie das Hauptpaket Twilio NuGet nicht, da es Versionskonflikte und Buildfehler verursachen kann.
Die Aktivitäten übermitteln die Genehmigungsanforderung und verarbeiten die Antwort.
Übermittlung der Genehmigungsanforderungsaktivität
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
[DurableTask(nameof(SubmitApprovalRequestActivity))]
public class SubmitApprovalRequestActivity : TaskActivity<ApprovalRequestData, SubmissionResult>
{
private readonly ILogger<SubmitApprovalRequestActivity> _logger;
public SubmitApprovalRequestActivity(ILogger<SubmitApprovalRequestActivity> logger)
{
_logger = logger;
}
public override Task<SubmissionResult> RunAsync(
TaskActivityContext context, ApprovalRequestData input)
{
_logger.LogInformation(
"Submitting approval request {RequestId} from {Requester} for {Item}",
input.RequestId, input.Requester, input.Item);
// In a real system, this would send an email, notification, or update a database
var result = new SubmissionResult
{
RequestId = input.RequestId,
Status = "Pending",
SubmittedAt = DateTime.UtcNow.ToString("o"),
ApprovalUrl = $"http://localhost:8000/api/approvals/{input.RequestId}"
};
return Task.FromResult(result);
}
}
Prozessgenehmigungsaktivität
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
[DurableTask(nameof(ProcessApprovalActivity))]
public class ProcessApprovalActivity : TaskActivity<ProcessApprovalInput, ApprovalResult>
{
private readonly ILogger<ProcessApprovalActivity> _logger;
public ProcessApprovalActivity(ILogger<ProcessApprovalActivity> logger)
{
_logger = logger;
}
public override Task<ApprovalResult> RunAsync(
TaskActivityContext context, ProcessApprovalInput input)
{
string status = input.IsApproved ? "Approved" : "Rejected";
_logger.LogInformation(
"Processing {Status} request {RequestId} by {Approver}",
status, input.RequestId, input.Approver);
// In a real system, this would update a database, trigger workflows, etc.
var result = new ApprovalResult
{
RequestId = input.RequestId,
Status = status,
ProcessedAt = DateTime.UtcNow.ToString("o"),
Approver = input.Approver
};
return Task.FromResult(result);
}
}
// Data classes
public class ApprovalRequestData
{
public string RequestId { get; set; } = string.Empty;
public string Requester { get; set; } = string.Empty;
public string Item { get; set; } = string.Empty;
public double TimeoutHours { get; set; } = 24.0;
}
public class ApprovalResponseData
{
public bool IsApproved { get; set; }
public string Approver { get; set; } = string.Empty;
}
public class SubmissionResult
{
public string RequestId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string SubmittedAt { get; set; } = string.Empty;
public string ApprovalUrl { get; set; } = string.Empty;
}
public class ProcessApprovalInput
{
public string RequestId { get; set; } = string.Empty;
public bool IsApproved { get; set; }
public string Approver { get; set; } = string.Empty;
}
public class ApprovalResult
{
public string RequestId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string ProcessedAt { get; set; } = string.Empty;
public string? Approver { get; set; }
}
Beispiel ausführen
Verwenden Sie die http-ausgelösten Funktionen im Beispiel, um die Orchestrierung zu starten, indem Sie die folgende HTTP POST-Anforderung senden:
POST http://{host}/orchestrators/E4_SmsPhoneVerification
Content-Length: 14
Content-Type: application/json
"+1425XXXXXXX"
HTTP/1.1 202 Accepted
Content-Length: 695
Content-Type: application/json; charset=utf-8
Location: http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
{"id":"741c65651d4c40cea29acdd5bb47baf1","statusQueryGetUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","sendEventPostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","terminatePostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/terminate?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}"}
Die Orchestratorfunktion empfängt die Telefonnummer und sendet sofort eine SMS-Nachricht an diese Nummer mit einem zufällig generierten 4-stelligen Überprüfungscode , 2168z. B. . Anschließend wartet die Funktion 90 Sekunden auf eine Antwort.
Um mit dem Code zu antworten, verwenden Sie RaiseEventAsync (.NET) oder raiseEvent (JavaScript und TypeScript) in einer anderen Funktion, oder rufen Sie den sendEventPostUri HTTP POST-Endpunkt in der Antwort 202 auf. Ersetzen Sie {eventName} durch SmsChallengeResponse:
POST http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/SmsChallengeResponse?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
Content-Length: 4
Content-Type: application/json
2168
Wenn Sie das Ereignis vor Ablauf des Timers senden, ist die Orchestrierung abgeschlossen, und das Feld output wird auf true festgelegt, was auf eine erfolgreiche Überprüfung hinweist.
GET http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
HTTP/1.1 200 OK
Content-Length: 144
Content-Type: application/json; charset=utf-8
{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":true,"createdTime":"2017-06-29T19:10:49Z","lastUpdatedTime":"2017-06-29T19:12:23Z"}
Wenn der Timer abläuft oder Sie den falschen Code viermal eingeben, überprüfen Sie den Status, um zu sehen, ob output auf false gesetzt ist, was darauf hinweist, dass die Telefonüberprüfung fehlgeschlagen ist.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 145
{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":false,"createdTime":"2017-06-29T19:20:49Z","lastUpdatedTime":"2017-06-29T19:22:23Z"}
So führen Sie das Beispiel aus:
Starten Sie den Emulator "Durable Task Scheduler " für die lokale Entwicklung.
docker run -d -p 8080:8080 -p 8082:8082 --name dts-emulator mcr.microsoft.com/dts/dts-emulator:latestStarten Sie den Worker , um den Orchestrator und die Aktivitäten zu registrieren.
Führen Sie den Client aus, um einen Genehmigungsworkflow zu planen und Ereignisse zu senden.
using System;
using System.Threading.Tasks;
var client = DurableTaskClientBuilder.UseDurableTaskScheduler(connectionString).Build();
// Schedule the approval workflow
var input = new ApprovalRequestData
{
RequestId = "request-" + Guid.NewGuid().ToString(),
Requester = "john.doe@example.com",
Item = "Vacation Request - 5 days",
TimeoutHours = 24
};
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(ApprovalOrchestration), input);
Console.WriteLine($"Started approval workflow: {instanceId}");
// Simulate human approving the request
Console.WriteLine("Simulating approval...");
await Task.Delay(2000);
// Raise the approval event
var approvalResponse = new ApprovalResponseData
{
IsApproved = true,
Approver = "manager@example.com"
};
await client.RaiseEventAsync(instanceId, "approval_response", approvalResponse);
// Wait for completion
var result = await client.WaitForInstanceCompletionAsync(instanceId, getInputsAndOutputs: true);
Console.WriteLine($"Result: {result.ReadOutputAs<ApprovalResult>().Status}");
Nächste Schritte
Dieses Beispiel veranschaulicht erweiterte Durable Functions-Funktionen, einschließlich der APIs WaitForExternalEvent und CreateTimer. Es zeigt, wie sie Task.WhenAny (C#), context.df.Task.any (JavaScript und TypeScript) oder context.task_any (Python) kombinieren, um ein zuverlässiges Timeoutmuster für Workflows zu implementieren, die darauf warten, dass Personen reagieren. Erfahren Sie mehr über Durable Functions in einer Reihe von Artikeln, die bestimmte Themen abdecken.
In diesem Beispiel wird gezeigt, wie Sie die sdKs für dauerhafte Aufgaben verwenden, um Workflows zu implementieren, die darauf warten, dass Personen reagieren, mit konfigurierbaren Timeouts. Wichtige Konzepte:
Externe Ereignisse: Verwenden von
WaitForExternalEvent, um auf Eingaben zu wartenDauerhafte Zeitgeber: Verwenden
CreateTimerzum Implementieren von TimeoutsRennaufgaben: Verwenden
WhenAny,when_anyoderanyOfum zu verarbeiten, welche Aufgabe zuerst abgeschlossen wird