Udostępnij przez


Integrowanie własnego kanału niestandardowego za pomocą usługi Direct Line

Usługa Dynamics 365 Contact Center umożliwia zaimplementowanie łącznika w celu zintegrowania niestandardowych kanałów obsługi komunikatów przy użyciu interfejsu API direct line 3.0, który jest częścią zestawu .NET SDK. Kompletny przykładowy kod ilustruje, jak można utworzyć własny łącznik. Aby dowiedzieć się więcej na temat interfejsu API linii bezpośredniej 3.0, zobacz Kluczowe pojęcia w interfejsie API linii bezpośredniej 3.0.

W tym artykule wyjaśniono, w jaki sposób kanał jest połączony z platformą Microsoft Direct Line Bot Framework, która jest wewnętrznie dołączona do centrum kontaktów usługi Dynamics 365. Poniższa sekcja zawiera fragmenty kodu, które używają interfejsu API Direct Line 3.0 do tworzenia klienta Direct Line i IChannelAdapter interfejsu do tworzenia przykładowego łącznika.

Uwaga / Notatka

Kod źródłowy i dokumentacja opisują ogólny przepływ sposobu łączenia kanału z Centrum kontaktów dynamics 365 za pośrednictwem linii bezpośredniej i nie skupiają się na aspektach niezawodności i skalowalności.

Komponenty

Usługa interfejsu API elementu webhook adaptera

Gdy użytkownik wprowadza komunikat, interfejs API adaptera jest wywoływany z kanału. Przetwarza żądanie przychodzące i wysyła stan powodzenia lub niepowodzenia jako odpowiedź. Usługa adapter API musi zaimplementować interfejs IChannelAdapter i wysyła żądanie przychodzące do odpowiedniego adaptera kanału w celu przetwarzania żądania.

/// <summary>
/// Accept an incoming web-hook request from MessageBird Channel
/// </summary>
/// <param name="requestPayload">Inbound request Object</param>
/// <returns>Executes the result operation of the action method asynchronously.</returns>
    [HttpPost("postactivityasync")]
    public async Task<IActionResult> PostActivityAsync(JToken requestPayload)
    {
        if (requestPayload == null)
        {
            return BadRequest("Request payload is invalid.");
        }

        try
        {
            await _messageBirdAdapter.ProcessInboundActivitiesAsync(requestPayload, Request).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _logger.LogError($"postactivityasync: {ex}");
            return StatusCode(500, "An error occured while handling your request.");
        }

        return StatusCode(200);
    }

Adaptery kanałów

Adapter kanałowy przetwarza działania przychodzące oraz wychodzące i musi implementować interfejs IAdapterBuilder.

Przetwarzanie działań przychodzących

Adapter kanału wykonuje następujące działania przychodzące:

  1. Sprawdzanie poprawności podpisu żądania wiadomości przychodzącej.

Żądanie przychodzące z kanału jest weryfikowane na podstawie klucza podpisywania. Jeśli żądanie jest nieprawidłowe, zostanie zgłoszony wyjątek z komunikatem "nieprawidłowy podpis". Jeśli żądanie jest prawidłowe, przebiega w następujący sposób:

  /// <summary>
  /// Validate Message Bird Request
  /// </summary>
  /// <param name="content">Request Content</param>
  /// <param name="request">HTTP Request</param>
  /// <param name="messageBirdSigningKey">Message Bird Signing Key</param>
  /// <returns>True if there request is valid, false if there aren't.</returns>
  public static bool ValidateMessageBirdRequest(string content, HttpRequest request, string messageBirdSigningKey)
  {
      if (string.IsNullOrWhiteSpace(messageBirdSigningKey))
      {
          throw new ArgumentNullException(nameof(messageBirdSigningKey));
      }
      if (request == null)
      {
          throw new ArgumentNullException(nameof(request));
      }
      if (string.IsNullOrWhiteSpace(content))
      {
          throw new ArgumentNullException(nameof(content));
      }
      var messageBirdRequest = new MessageBirdRequest(
          request.Headers?["Messagebird-Request-Timestamp"],
          request.QueryString.Value?.Equals("?",
              StringComparison.CurrentCulture) != null
              ? string.Empty
              : request.QueryString.Value,
          GetBytes(content));

      var messageBirdRequestSigner = new MessageBirdRequestSigner(GetBytes(messageBirdSigningKey));
      string expectedSignature = request.Headers?["Messagebird-Signature"];
      return messageBirdRequestSigner.IsMatch(expectedSignature, messageBirdRequest);
  }
  1. Konwertuj żądanie przychodzące na działanie bota.

Ładunek żądania przychodzącego jest konwertowany na działanie zrozumiałe dla platformy Bot Framework.

Uwaga / Notatka

Ładunek działania nie może przekraczać limitu rozmiaru wiadomości wynoszącego 28 KB.

Ten obiekt Activity zawiera następujące atrybuty:

Atrybut Opis
z Przechowuje informacje o koncie kanału, które składają się z unikalnego identyfikatora użytkownika i nazwy (kombinacji imienia i nazwiska oddzielonych ogranicznikiem spacji).
Identyfikator kanału Wskazuje identyfikator kanału. W przypadku żądań przychodzących identyfikator kanału to directline.
serviceUrl (adres url usługi) Wskazuje adres URL usługi. W przypadku żądań przychodzących adres URL usługi to https://directline.botframework.com/.
typ Wskazuje typ działania. W przypadku działań wiadomości, ten typ to message.
tekst Przechowuje treść wiadomości.
id Wskazuje identyfikator używany przez adapter do odpowiadania na wiadomości wychodzące.
Dane kanału Wskazuje dane kanału, które składają się z channelType, conversationcontext, i customercontext.
typKanału Wskazuje nazwę kanału, za pośrednictwem którego klient wysyła wiadomości. Na przykład MessageBird, KakaoTalk, Snapchat
Kontekst konwersacyjny Odwołuje się do obiektu słownika, który przechowuje zmienne kontekstowe zdefiniowane w strumieniu pracy. Usługa Dynamics 365 Contact Center używa tych informacji do kierowania konwersacji do odpowiedniego przedstawiciela działu obsługi klienta (przedstawiciela obsługi lub przedstawiciela). Przykład:
"conversationcontext ":{ "ProductName": "Xbox", "Issue":"Installation" }
W tym przykładzie kontekst kieruje konwersację do przedstawiciela serwisu, który zajmuje się instalacją konsoli Xbox.
Kontekst klienta Odwołuje się do obiektu słownika, który zawiera szczegółowe informacje o kliencie, takie jak numer telefonu i adres e-mail. Usługa Dynamics 365 Contact Center używa tych informacji do identyfikowania rekordu kontaktu użytkownika.
"customercontext":{ "email":email@email.com, "numer telefonu":"1234567890" }
  /// <summary>
  /// Build Bot Activity type from the inbound MessageBird request payload<see cref="Activity"/>
  /// </summary>
  /// <param name = "messagePayload"> Message Bird Activity Payload</param>
  /// <returns>Direct Line Activity</returns>
  public static Activity PayloadToActivity(MessageBirdRequestModel messagePayload)
  {
  if (messagePayload == null)
  {
      throw new ArgumentNullException(nameof(messagePayload));
  }
  if (messagePayload.Message?.Direction == ConversationMessageDirection.Sent ||
  messagePayload.Type == ConversationWebhookMessageType.MessageUpdated)
  {
      return null;
  }
  var channelData = new ActivityExtension
  {
      ChannelType = ChannelType.MessageBird,
      // Add Conversation Context in below dictionary object. Please refer the document for more information.
      ConversationContext = new Dictionary<string, string>(),
      // Add Customer Context in below dictionary object. Please refer the document for more information.
      CustomerContext = new Dictionary<string, string>()
  };
  var activity = new Activity
      {
          From = new ChannelAccount(messagePayload.Message?.From, messagePayload.Contact?.DisplayName),
          Text = messagePayload.Message?.Content?.Text,
          Type = ActivityTypes.Message,
          Id = messagePayload.Message?.ChannelId,
          ServiceUrl = Constant.DirectLineBotServiceUrl,
          ChannelData = channelData
      };

      return activity;
  }

Przykładowy ładunek JSON jest następujący:

{
    "type": "message",
    "id": "bf3cc9a2f5de...",    
    "serviceUrl": https://directline.botframework.com/,
    "channelId": "directline",
    "from": {
        "id": "1234abcd",// userid which uniquely identify the user
        "name": "customer name" // customer name as First Name <space> Last Name
    },
    "text": "Hi,how are you today.",
    "channeldata":{
        "channeltype":"messageBird",
        "conversationcontext ":{ // this holds context variables defined in Workstream
            "ProductName" : "XBox",
            "Issue":"Installation"
        },
        "customercontext":{            
            "email":email@email.com,
            "phonenumber":"1234567890"           
        }
    }
}

  1. Wyślij działanie do procesora przekazywania komunikatów.

Po zbudowaniu ładunku działania, wywołuje wiadomość PostActivityAsync procesora przekazywania wiadomości, aby wysłać działanie do Direct Line. Adapter kanału powinien również przekazać program obsługi zdarzeń, który procesor przekazywania wywoła, gdy odbierze komunikat wychodzący z usługi Dynamics 365 Contact Center za pośrednictwem Direct Line.

Proces działań wychodzących

Procesor przekaźnika wywołuje procedurę obsługi zdarzeń w celu wysłania działań wychodzących do odpowiedniego adaptera kanału, a następnie adapter przetwarza działania wychodzące. Adapter kanału wykonuje następujące działania wychodzące:

  1. Konwertuj działania wychodzące na model odpowiedzi kanału.

Działania Direct Line są konwertowane na model odpowiedzi specyficzny dla kanału.

  /// <summary>
  /// Creates MessageBird response object from a Bot Framework <see cref="Activity"/>.
  /// </summary>
  /// <param name="activities">The outbound activities.</param>
  /// <param name="replyToId">Reply Id of Message Bird user.</param>
  /// <returns>List of MessageBird Responses.</returns>
  public static List<MessageBirdResponseModel> ActivityToMessageBird(IList<Activity> activities, string replyToId)
  {
      if (string.IsNullOrWhiteSpace(replyToId))
      {
          throw new ArgumentNullException(nameof(replyToId));
      }

      if (activities == null)
      {
          throw new ArgumentNullException(nameof(activities));
      }

      return activities.Select(activity => new MessageBirdResponseModel
      {
          To = replyToId,
          From = activity.ChannelId,
          Type = "text",
          Content = new Content
          {
              Text = activity.Text
          }
      }).ToList();
  }
  1. Wysyłaj odpowiedzi przez interfejs API REST kanału.

Adapter kanału wywołuje API REST w celu wysłania odpowiedzi wychodzącej do kanału, która jest następnie wysyłana do użytkownika.

  /// <summary>
  /// Send Outbound Messages to Message Bird
  /// </summary>
  /// <param name="messageBirdResponses">Message Bird Response object</param>
  /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
  public async Task SendMessagesToMessageBird(IList<MessageBirdResponseModel> messageBirdResponses)
  {
      if (messageBirdResponses == null)
      {
          throw new ArgumentNullException(nameof(messageBirdResponses));
      }

      foreach (var messageBirdResponse in messageBirdResponses)
      {
          using (var request = new HttpRequestMessage(HttpMethod.Post, $"{MessageBirdDefaultApi}/send"))
          {
              var content = JsonConvert.SerializeObject(messageBirdResponse);
              request.Content = new StringContent(content, Encoding.UTF8, "application/json");
              await _httpClient.SendAsync(request).ConfigureAwait(false);
          }
      }
  }

Procesor przekazywania wiadomości

Procesor przekaźnikowy komunikatów odbiera aktywność przychodzącą od adaptera kanału i przeprowadza walidację modelu aktywności. Procesor przekazywania sprawdza, czy konwersacja jest aktywna dla określonego działania, przed wysłaniem tego działania do Direct Line

Aby sprawdzić, czy konwersacja jest aktywna, procesor przekazywania przechowuje kolekcję aktywnych konwersacji w słowniku. Ten słownik zawiera klucz jako identyfikator użytkownika, który jednoznacznie identyfikuje użytkownika i wartość jako obiekt następującej klasy:

 /// <summary>
/// Direct Line Conversation to store as an Active Conversation
/// </summary>
public class DirectLineConversation
{
    /// <summary>
    /// .NET SDK Client to connect to Direct Line Bot
    /// </summary>
    public DirectLineClient DirectLineClient { get; set; }

    /// <summary>
    /// Direct Line response after start a new conversation
    /// </summary>
    public Conversation Conversation { get; set; }

    /// <summary>
    /// Watermark to guarantee that no messages are lost
    /// </summary>
    public string WaterMark { get; set; }
}

Jeśli rozmowa nie jest aktywna dla działania odebranego przez procesor przekazywania, wykonuje następujące kroki:

  1. Inicjuj konwersację za pomocą Direct Line i przechowuj obiekt konwersacji wysłany przez Direct Line względem identyfikatora użytkownika w słowniku.
 /// <summary>
 /// Initiate Conversation with Direct Line Bot
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <param name="adapterCallBackHandler">Call Back to send activities to Messaging API</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private async Task InitiateConversation(Activity inboundActivity, EventHandler<IList<Activity>> adapterCallBackHandler)
 {
     var directLineConversation = new DirectLineConversation
     {
         DirectLineClient = new DirectLineClient(_relayProcessorConfiguration.Value.DirectLineSecret)
     };
     // Start a conversation with Direct Line Bot
     directLineConversation.Conversation = await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);

     await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);
     if (directLineConversation.Conversation == null)
     {
         throw new Exception(
             "An error occurred while starting the Conversation with direct line. Please validate the direct line secret in the configuration file.");
     }

     // Adding the Direct Line Conversation object to the lookup dictionary and starting a thread to poll the activities from the direct line bot.
     if (ActiveConversationCache.ActiveConversations.TryAdd(inboundActivity.From.Id, directLineConversation))
     {
         // Starts a new thread to poll the activities from Direct Line Bot
         new Thread(async () => await PollActivitiesFromBotAsync(
             directLineConversation.Conversation.ConversationId, inboundActivity, adapterCallBackHandler).ConfigureAwait(false))
         .Start();
     }
 }
  1. Uruchamia nowy wątek w celu sondowania działań wychodzących z bota Direct Line na podstawie interwału sondowania skonfigurowanego w pliku konfiguracyjnym. Wątek sondowania jest aktywny do chwili, gdy koniec działania konwersacji jest odbierany z Direct Line.
/// <summary>
/// Polling the activities from BOT for the active conversation
/// </summary>
/// <param name="conversationId">Direct Line Conversation Id</param>
/// <param name="inboundActivity">Inbound Activity from Channel/Aggregator</param>
/// <param name="lineActivitiesReceived">Call Back to send activities to Messaging API</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task PollActivitiesFromBotAsync(string conversationId, Activity inboundActivity, EventHandler<IList<Activity>> lineActivitiesReceived)
{
    if (!int.TryParse(_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds, out var pollingInterval))
    {
        throw new FormatException($"Invalid Configuration value of PollingIntervalInMilliseconds: {_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds}");
    }
    if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
        out var conversationContext))
    {
        throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
    }
    while (true)
    {
        var watermark = conversationContext.WaterMark;
        // Retrieve the activity set from the bot.
        var activitySet = await conversationContext.DirectLineClient.Conversations.
            GetActivitiesAsync(conversationId, watermark).ConfigureAwait(false);
        // Set the watermark to the message received
        watermark = activitySet?.Watermark;

        // Extract the activities sent from our bot.
        if (activitySet != null)
        {
            var activities = (from activity in activitySet.Activities
                              where activity.From.Id == _relayProcessorConfiguration.Value.BotHandle
                              select activity).ToList();
            if (activities.Count > 0)
            {
                SendReplyActivity(activities, inboundActivity, lineActivitiesReceived);
            }
            // Update Watermark
            ActiveConversationCache.ActiveConversations[inboundActivity.From.Id].WaterMark = watermark;
            if (activities.Exists(a => a.Type.Equals("endOfConversation", StringComparison.InvariantCulture)))
            {
                if (ActiveConversationCache.ActiveConversations.TryRemove(inboundActivity.From.Id, out _))
                {
                    Thread.CurrentThread.Abort();
                }
            }
        }
        await Task.Delay(TimeSpan.FromMilliseconds(pollingInterval)).ConfigureAwait(false);
    }
}

Uwaga / Notatka

Sercem kodu, który odbiera komunikat, jest metoda GetActivitiesAsync, która przyjmuje ConversationId i watermark jako parametry. Celem parametru watermark jest pobranie tylko wiadomości, które nie są jeszcze dostarczane przez Direct Line. Jeśli parametr znaku wodnego został określony, konwersacja jest odtwarzana ze znaku wodnego, więc żadne wiadomości nie zostaną utracone.

Wyślij działanie do Direct Line

 /// <summary>
 /// Send the activity to the bot using Direct Line client
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private static async Task SendActivityToBotAsync(Activity inboundActivity)
 {
     if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
         out var conversationContext))
     {
         throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
     }
     await conversationContext.DirectLineClient.Conversations.PostActivityAsync(
         conversationContext.Conversation.ConversationId, inboundActivity).ConfigureAwait(false);
 }

Jeśli konwersacja jest aktywna dla działania odebranego przez procesor przekazywania, wysyła działanie do procesora przekazywania komunikatów.

Zakończ konwersację

Aby zakończyć rozmowę, zobacz Kończenie konwersacji w Direct Line.

Formaty znaczników w kanałach niestandardowych

Można wysyłać i odbierać wiadomości sformatowane przy użyciu języka Markdown w standardowych kanałach wiadomości za pomocą interfejsu Direct Line API 3.0. Zrozumienie sposobu przekazywania formatu Markdown przez kanał i znajomość szczegółów formatu pomoże Ci zaktualizować styl HTML i tagi we własnym interfejsie użytkownika.

W kanale Direct Line, gdy przedstawiciel działu obsługi klienta (przedstawiciel usługi lub przedstawiciel) wysyła (wychodzący) wiadomość sformatowany przy użyciu języka Markdown do bota Direct Line, bot otrzymuje wiadomość w określonym formacie. Teraz, jeśli bot otrzyma (przychodzące) sformatowaną wiadomość od klienta, musi być w stanie poprawnie zinterpretować wiadomość, która jest sformatowana za pomocą Markdown. Jako deweloper musisz odpowiednio użyć języka Markdown, aby wiadomość była poprawnie sformatowana dla przedstawicieli działu obsługi i klientów.

Dowiedz się więcej o formatach języka Markdown w temacie Formaty języka Markdown dla wiadomości czatu.

Uwaga / Notatka

  • Obecnie nie obsługujemy dodawania kilku przerw w wierszu za pomocą kombinacji klawiszy <Shift + Enter>.
  • W przypadku wiadomości przychodzących ustaw tekst Markdown na Działanie właściwość text obiektu.
  • W przypadku wiadomości wychodzących tekst Markdown jest odbierany we właściwości obiektu Działanie o nazwie text (podobnie jak zwykła wiadomość).

Dalsze kroki

Obsługa czatu na żywo i kanałów asynchronicznych

Konfiguruj kanał wiadomości niestandardowych
Dokumentacja interfejsu API MessageBird
Najlepsze wskazówki dotyczące konfigurowania botów