Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Шаблон взаимодействия с человеком описывает рабочие процессы, которые приостанавливаются и ожидают ввода от человека, прежде чем продолжить. Шаблон полезен для рабочих процессов утверждения, многофакторной проверки подлинности и любого сценария, в котором пользователь отвечает в течение определенного времени.
В этом примере показано, как создать оркестрацию Durable Functions, которая включает взаимодействие с человеком. В этом примере реализована система проверки телефонов на основе SMS. В процессах проверки номера телефона и многофакторной аутентификации (MFA) это встречается часто.
Замечание
Общедоступна версия 4 модели программирования Node.js для функций Azure. Модель версии 4 предназначена для обеспечения более гибкого и интуитивно понятного интерфейса для разработчиков JavaScript и TypeScript. Дополнительные сведения о различиях между версиями 3 и 4 см. в руководстве по миграции.
В следующих фрагментах кода JavaScript (PM4) обозначает модель программирования версии 4, новый интерфейс.
Необходимые условия
- Завершите чтение быстрой статьи.
- Клонировать или скачать проект с примерами из GitHub (использует внутрипроцессную модель)
В этой статье показано, как реализовать шаблон взаимодействия с человеком с помощью пакетов SDK для устойчивых задач. В этом примере реализуется рабочий процесс утверждения, в котором оркестрация ожидает, пока человек утвердит или отклонит запрос, прежде чем продолжить.
Обзор сценария
Проверка телефона помогает подтвердить, что пользователи, использующие ваше приложение, не являются спамерами, и что они контролируют номер телефона, который они предоставляют. Многофакторная проверка подлинности — это распространенный способ защиты учетных записей. Для создания собственной верификации телефона требуется взаимодействие с отслеживанием состояния с человеком. Обычно пользователь получает код (например, четырехзначное число) и должен отвечать в разумный период времени.
Стандартные Azure Functions являются без состояния (как и многие другие конечные точки в облаке), поэтому для такого типа взаимодействия необходимо хранить состояние в базе данных или другом постоянном хранилище. Вы также разделяете взаимодействие между несколькими функциями и координируете их. Например, одна функция создает код, сохраняет его и отправляет его на телефон пользователя. Другая функция получает ответ пользователя и сопоставляет его с исходным запросом для проверки кода. Добавьте время ожидания для защиты безопасности. Этот рабочий процесс быстро становится сложным.
Durable Functions снижает сложность этого сценария. В этом примере функция оркестратора управляет состоянием взаимодействия без использования внешней базы данных. Поскольку функции оркестратора устойчивы, эти интерактивные потоки являются высоконадежными.
Рабочие процессы утверждения распространены в бизнес-приложениях, где запрос должен быть проверен человеком перед продолжением. Требования к рабочему процессу:
- Ожидайте неопределённого времени для реагирования человека или до тайм-аута
- Обработка результатов утверждения и отклонения
- Время ожидания поддержки при отсутствии ответа
- Отслеживание состояния , чтобы запрашиватель смог проверить ход выполнения
Пакеты SDK для устойчивых задач упрощают этот сценарий:
- Внешние события: оркестрация может приостановить и ждать события, вызываемого внешней системой или пользователем.
- Устойчивые таймеры: задайте время ожидания, которое запускается, если ответ не получен.
- Настраиваемое состояние: отслеживание и предоставление текущего состояния рабочего процесса клиентам
Настройка интеграции Twilio
Для отправки SMS-сообщений на мобильный телефон в этом примере используется служба Twilio. Azure Functions уже поддерживает Twilio с помощью привязки Twilio, и этот пример использует эту возможность.
Первое, что вам нужно — это учетная запись Twilio. Вы можете создать бесплатную учетную запись здесь: https://www.twilio.com/try-twilio. Получив аккаунт, добавьте следующие три параметра приложения в функциональное приложение.
| Имя параметра приложения | Описание значения |
|---|---|
| TwilioAccountSid | Идентификатор безопасности вашей учетной записи в Twilio. |
| TwilioAuthToken | Токен аутентификации для вашей учетной записи Twilio. |
| TwilioPhoneNumber | Номер телефона, связанный с вашей учетной записью в Twilio. Используется для отправки SMS-сообщений. |
Оркестратор и действия
В этой статье рассматриваются следующие функции в примере приложения:
-
E4_SmsPhoneVerification: функция оркестратора , которая запускает процесс проверки телефона и управляет временем ожидания и повторными попытками. -
E4_SendSmsChallenge: функция действия , которая отправляет код по текстовому сообщению.
Замечание
Функция HttpStart в приложении-примере и руководстве быстрого старта выполняет роль клиента оркестрации и запускает функцию оркестратора.
В этой статье описываются следующие компоненты в примере приложения:
-
ApprovalOrchestration/approvalOrchestrator/human_interaction_orchestrator: оркестратор, который отправляет запрос на согласование и ожидает ответа человека или тайм-аута. -
SubmitApprovalRequestActivity/submitRequest/submit_approval_request: действие, которое уведомляет утверждающего человека, например по электронной почте или сообщению чата. -
ProcessApprovalActivity/processApproval/process_approval: действие, обрабатывающее решение об утверждении.
Orchestrator
Функция оркестратора 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;
}
}
Замечание
Это может быть не очевидно сначала, но этот оркестратор не нарушает детерминированное ограничение оркестрации. Это детерминировано, так как CurrentUtcDateTime свойство вычисляет время истечения срока действия таймера, и оно возвращает то же значение для каждого воспроизведения на этом этапе в коде оркестратора. Это поведение обеспечивает, что winner остается одинаковым при каждом повторяющемся вызове Task.WhenAny.
После запуска функция оркестратора выполняет следующие задачи:
- Получает номер телефона для отправки SMS-уведомления.
- Вызывает E4_SendSmsChallenge для отправки SMS-сообщения пользователю и возвращает ожидаемый четырехзначный код вызова.
- Создает устойчивый таймер, который срабатывает через 90 секунд после текущего времени.
- Параллельно с таймером ожидается событие SmsChallengeResponse от пользователя.
Пользователи получают SMS-сообщение с 4-значным кодом. У них есть 90 секунд для отправки того же кода экземпляру оркестратора для завершения проверки. Если они отправят неправильный код, они получают три дополнительных попытки в том же 90-секундном окне.
Предупреждение
Отмените таймеры, которые вам больше не нужны. В приведенном выше примере оркестрация отменяет таймер при принятии ответа на вызов.
Оркестратор отправляет запрос на утверждение, а затем ожидает ответа человека или истечения времени ожидания.
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;
}
}
Этот оркестратор выполняет следующие действия:
- Отправляет запрос на утверждение, вызвав действие, которое уведомляет утверждающего.
- Задает настраиваемое состояние, чтобы клиенты могли отслеживать ход выполнения.
- Создает надёжный таймер для дедлайна ожидания.
- Ожидает внешнего события (
approval_response), которое инициирует утверждающий. - Использует
WhenAny,when_any, илиanyOfчтобы дождаться того, что завершится первым: утверждение или тайм-аут. - Обрабатывает результат в зависимости от того, какая задача завершится.
Предупреждение
Отмените таймеры, которые вам больше не нужны. В примере C# оркестрация отменяет таймер ожидания при получении утверждения.
Действия
Активная функция E4_SendSmsChallenge
Функция E4_SendSmsChallenge использует привязку Twilio для отправки SMS-сообщения, включающего четырехзначный код пользователю.
[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;
}
Замечание
Чтобы запустить пример, установите пакет NuGet Microsoft.Azure.WebJobs.Extensions.Twilio. Не устанавливайте основной пакет NuGet Twilio , так как он может вызвать конфликты версий и ошибки сборки.
Действия передают запрос на утверждение и обрабатывают ответ.
Отправка запроса на утверждение задачи
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);
}
}
Действие утверждения процесса
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; }
}
Запустите пример
Используйте функции, запускаемые HTTP в примере, чтобы начать оркестрацию, отправив следующий HTTP-запрос POST:
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}"}
Функция оркестратора получает номер телефона и немедленно отправляет SMS-сообщение в этот номер со случайным образом созданным 4-цифрным кодом проверки, например 2168. Функция ожидает ответ в течение 90 секунд.
Чтобы ответить с помощью кода, используйте RaiseEventAsync (.NET) или raiseEvent (JavaScript и TypeScript) в другой функции, либо вызовите конечную точку HTTP POST sendEventPostUri в ответе с кодом 202. Замените {eventName} на 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
Если событие отправляется до истечения таймера, оркестрация завершается, а output поле имеет true значение, указывающее на успешную проверку.
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"}
Если срок действия таймера истек или вы ввели неправильный код четыре раза, проверьте состояние, чтобы увидеть, что output установлено в false, что указывает на сбой проверки телефона.
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"}
Чтобы запустить пример, выполните следующие действия:
Запустите эмулятор планировщика устойчивых задач для локальной разработки.
docker run -d -p 8080:8080 -p 8082:8082 --name dts-emulator mcr.microsoft.com/dts/dts-emulator:latestЗапустите рабочий процесс , чтобы зарегистрировать оркестратор и операции.
Запустите клиент , чтобы запланировать рабочий процесс утверждения и отправить события.
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}");
Дальнейшие действия
В этом примере показаны расширенные возможности Durable Functions, включая API WaitForExternalEvent и CreateTimer. В нем показано, как объединить Task.WhenAny (C#), context.df.Task.any (JavaScript и TypeScript) или context.task_any (Python) для реализации надежного шаблона времени ожидания для рабочих процессов, ожидающих реагирования пользователей. Узнайте больше о Durable Functions в серии статей, посвященных конкретным темам.
В этом примере показано, как использовать пакеты SDK для устойчивых задач для реализации рабочих процессов, ожидающих реагирования пользователей с настраиваемым временем ожидания. Основные понятия:
Внешние события: использование
WaitForExternalEventдля ожидания входных данныхУстойчивые таймеры: использование
CreateTimerдля реализации времени ожиданияГоночные задачи: использование
WhenAny,when_anyилиanyOfдля обработки той задачи, которая завершится первой