Шаблон взаимодействия с человеком

Шаблон взаимодействия с человеком описывает рабочие процессы, которые приостанавливаются и ожидают ввода от человека, прежде чем продолжить. Шаблон полезен для рабочих процессов утверждения, многофакторной проверки подлинности и любого сценария, в котором пользователь отвечает в течение определенного времени.

На высоком уровне шаблон работает следующим образом:

  1. Оркестратор вызывает действие, чтобы уведомить человека (отправить смс-код, сообщение электронной почты утверждающему и т. д.).
  2. Оркестратор запускает устойчивый таймер и одновременно ожидает внешнего события от человека.
  3. Если пользователь отвечает перед запуском таймера, оркестратор обрабатывает ответ.
  4. Если таймер срабатывает первым, оркестратор обрабатывает тайм-аут (например, отклоняя запрос).

Содержание этой статьи

Замечание

Точки поворота в пакете SDK для Устойчивые функции и Durable Task демонстрируют один и тот же шаблон в различных сценариях: Устойчивые функции использует пример проверки телефона через SMS, тогда как SDK для Durable Task используют пример рабочего процесса утверждения.

В этом примере показано, как создать оркестрацию Устойчивые функции, которая включает взаимодействие с человеком. В этом примере реализована система проверки телефонов на основе SMS. В процессах проверки номера телефона и многофакторной аутентификации (MFA) это встречается часто.

Замечание

Полные примеры кода доступны для C#, JavaScript и Python. В настоящее время примеры PowerShell и Java недоступны.

Замечание

Общедоступна версия 4 модели программирования Node.js для функций Azure. Модель версии 4 предназначена для обеспечения более гибкого и интуитивно понятного интерфейса для разработчиков JavaScript и TypeScript. Дополнительные сведения о различиях между версиями 3 и 4 см. в руководстве по миграции.

В следующих фрагментах кода JavaScript (PM4) обозначает модель программирования версии 4, новый интерфейс.

Необходимые условия

В этой статье показано, как реализовать шаблон взаимодействия с человеком с помощью пакетов SDK для устойчивых задач. В этом примере реализуется рабочий процесс утверждения, в котором оркестрация ожидает, пока человек утвердит или отклонит запрос, прежде чем продолжить.

Обзор сценария взаимодействия с человеком

Проверка телефона помогает подтвердить, что пользователи, использующие ваше приложение, не являются спамерами, и что они контролируют номер телефона, который они предоставляют. Многофакторная проверка подлинности — это распространенный способ защиты учетных записей. Для создания собственной верификации телефона требуется взаимодействие с отслеживанием состояния с человеком. Обычно пользователь получает код (например, четырехзначное число) и должен отвечать в разумный период времени.

Стандартные Функции Azure являются без состояния (как и многие другие конечные точки в облаке), поэтому для такого типа взаимодействия необходимо хранить состояние в базе данных или другом постоянном хранилище. Вы также разделяете взаимодействие между несколькими функциями и координируете их. Например, одна функция создает код, сохраняет его и отправляет его на телефон пользователя. Другая функция получает ответ пользователя и сопоставляет его с исходным запросом для проверки кода. Добавьте время ожидания для защиты безопасности. Этот рабочий процесс быстро становится сложным.

Устойчивые функции снижает сложность этого сценария. В этом примере функция оркестратора управляет состоянием взаимодействия без использования внешней базы данных. Поскольку функции оркестратора устойчивы, эти интерактивные потоки являются высоконадежными.

Рабочие процессы утверждения распространены в бизнес-приложениях, где запрос должен быть проверен человеком перед продолжением. Требования к рабочему процессу:

  • Ожидайте неопределённого времени для реагирования человека или до тайм-аута
  • Обработка результатов утверждения и отклонения
  • Время ожидания поддержки при отсутствии ответа
  • Отслеживание состояния , чтобы запрашиватель смог проверить ход выполнения

Пакеты SDK для устойчивых задач упрощают этот сценарий:

  • Внешние события: оркестрация может приостановить и ждать события, вызываемого внешней системой или пользователем.
  • Устойчивые таймеры: задайте время ожидания, которое запускается, если ответ не получен.
  • Настраиваемое состояние: отслеживание и предоставление текущего состояния рабочего процесса клиентам

Настройка интеграции Twilio

Для отправки SMS-сообщений на мобильный телефон в этом примере используется служба Twilio. Функции Azure уже поддерживает Twilio с помощью привязки Twilio, и этот пример использует эту возможность.

Первое, что вам нужно — это учетная запись Twilio. Вы можете создать бесплатную учетную запись здесь: https://www.twilio.com/try-twilio. Получив аккаунт, добавьте следующие три параметра приложения в функциональное приложение.

Имя параметра приложения Описание значения
TwilioAccountSid Идентификатор безопасности вашей учетной записи в Twilio.
TwilioAuthToken Токен аутентификации для вашей учетной записи Twilio.
TwilioPhoneNumber Номер телефона, связанный с вашей учетной записью в Twilio. Используется для отправки SMS-сообщений.

Определение оркестратора

Функция оркестратора E4_SmsPhoneVerification

Замечание

Это может быть не очевидно сначала, но этот оркестратор не нарушает детерминированное ограничение оркестрации. Это детерминировано, так как CurrentUtcDateTime свойство вычисляет время истечения срока действия таймера, и оно возвращает то же значение для каждого воспроизведения на этом этапе в коде оркестратора. Это поведение обеспечивает, что winner остается одинаковым при каждом повторяющемся вызове Task.WhenAny.

После запуска функция оркестратора выполняет следующие задачи:

  1. Получает номер телефона для отправки SMS-уведомления.
  2. Вызывает E4_SendSmsChallenge для отправки SMS-сообщения пользователю и возвращает ожидаемый четырехзначный код вызова.
  3. Создает устойчивый таймер, который срабатывает через 90 секунд после текущего времени.
  4. Параллельно с таймером ожидается событие 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;
    }
}

Этот оркестратор выполняет следующие действия:

  1. Отправляет запрос на утверждение, вызвав действие, которое уведомляет утверждающего.
  2. Задает настраиваемое состояние, чтобы клиенты могли отслеживать ход выполнения.
  3. Создает надёжный таймер для дедлайна ожидания.
  4. Ожидает внешнего события (approval_response), которое инициирует утверждающий.
  5. Использует WhenAny, when_any, или anyOf чтобы дождаться того, что завершится первым: утверждение или тайм-аут.
  6. Обрабатывает результат в зависимости от того, какая задача завершится.

Предупреждение

Отмените таймеры, которые вам больше не нужны. В примере C# оркестрация отменяет таймер ожидания при получении утверждения.

Определение действий

Активная функция E4_SendSmsChallenge

Функция E4_SendSmsChallenge использует привязку Twilio для отправки SMS-сообщения, включающего четырехзначный код пользователю.

Замечание

Чтобы запустить пример, установите пакет 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-Type: application/json; charset=utf-8

{"id":"741c65651d4c40cea29acdd5bb47baf1",
 "sendEventPostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}",
 "statusQueryGetUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=...&code={systemKey}",
 "terminatePostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/terminate?reason={text}&taskHub=...&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":"2026-04-23T19:10:49Z","lastUpdatedTime":"2026-04-23T19: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":"2026-04-23T19:20:49Z","lastUpdatedTime":"2026-04-23T19:22:23Z"}

Чтобы запустить пример, выполните следующие действия:

  1. Запустите эмулятор планировщика устойчивых задач для локальной разработки. Необходимо установить Docker .

    docker run -d -p 8080:8080 -p 8082:8082 --name dts-emulator mcr.microsoft.com/dts/dts-emulator:latest
    
  2. Запустите рабочий процесс , чтобы зарегистрировать оркестратор и операции.

  3. Запустите клиент , чтобы запланировать рабочий процесс утверждения и отправить события.

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}");

Дальнейшие действия

В этом примере показаны расширенные возможности Устойчивые функции, включая API WaitForExternalEvent и CreateTimer. В нем показано, как объединить Task.WhenAny (C#), context.df.Task.any (JavaScript и TypeScript) или context.task_any (Python) для реализации надежного шаблона времени ожидания для рабочих процессов, ожидающих реагирования пользователей.

В этом примере показано, как использовать пакеты SDK для устойчивых задач для реализации рабочих процессов, ожидающих реагирования пользователей с настраиваемым временем ожидания. Основные понятия:

  • Внешние события: использование WaitForExternalEvent для ожидания входных данных
  • Устойчивые таймеры: использование CreateTimer для реализации времени ожидания
  • Гоночные задачи: использование WhenAny, when_any или anyOf для обработки той задачи, которая завершится первой