65.9K
CodeProject 正在变化。 阅读更多。
Home

构建用于 Microsoft Teams 的 Java 消息扩展和连接器,第 1 部分:构建搜索消息扩展

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2022 年 1 月 28 日

CPOL

8分钟阅读

viewsIcon

4104

本教程演示了构建 Microsoft Teams 消息传递扩展的步骤。

当开发人员为任何应用程序构建扩展时,他们都希望将自定义功能嵌入到宿主应用程序的本机用户界面 (UI) 中。Microsoft Teams 也是如此,您的组织每天都在这里工作和协作。您可以确保向您的有用生产力应用程序的过渡尽可能无缝。

具体来说,您可以创建看起来像是宿主应用程序一部分的按钮或其他控件,用户可以通过这些宿主控件与您现有的或新的 Web 服务进行交互。然后,您的 Web 服务将处理用户通过这些控件发送的请求,并为给定的宿主应用程序生成自然响应。您可以使用消息传递扩展在 Microsoft Teams 中实现此嵌入式功能。

在本系列三篇文章中,我们将探讨将基于 Java 的应用程序嵌入 Microsoft Teams。首先,我将重点介绍消息传递扩展在 Microsoft Teams 中的工作原理,并构建一个用于搜索的消息传递扩展。稍后,我将演示构建一个链接展开消息传递扩展和一个通知连接器

探索 Microsoft Teams 中的消息传递扩展

与任何其他 Microsoft Teams 应用一样,消息传递扩展包含两个元素:现有的或新的 Web 服务以及应用清单。您可以使用您喜欢的工具来实现 Web 服务。

您可以在应用清单中指定最多十个命令,它们充当您的 Web 服务的快捷方式。您可以为用户可以调用命令的每个命令在 Microsoft Teams 中设置类型和位置,例如撰写消息区域、命令栏或消息。

当用户调用命令时,Teams 会将 JSON 负载发送到您的 Web 服务。然后,您可以实现自定义逻辑来处理请求并生成响应。您可以使用卡片向用户显示信息,例如视觉效果、文本或音频,以加速对话和用户操作。

Microsoft Teams 使用 Bot Framework 与您的 Web 服务进行通信。因此,从我们的开发人员角度来看,实现消息传递扩展涉及使用 Bot Framework SDK 为 Web 服务实现其他包装器。

我将向您展示如何使用 Java Spring MVC 框架为 Web 服务实现 Microsoft Teams 的消息传递扩展。Web 服务将以卡片的形式返回响应。

在本教程中,我将使用本地环境测试整个解决方案。但是,您也可以将 Web 服务部署到 Azure Spring Cloud

要重现该示例,您应该安装ngrok准备您的环境

您可以在GitHub上找到配套代码。

创建项目

首先,使用 Bot Framework Echo 项目模板创建项目。使用Yeoman 生成器

yo botbuilder-java

然后,按如下方式配置您的机器人

  • 名称msg-extension-search
  • 程序包名称db.teamsext.search
  • 模板:Echo Bot

上面的命令生成项目模板。此模板具有可用于开发机器人的结构。具体来说,EchoBot类扩展了ActivityHandler。Echo Bot 会回显您发送给机器人的每个消息。使用此模板用于 Echo Bot

public class EchoBot extends ActivityHandler {

    @Override
    protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
        return turnContext.sendActivity(
            MessageFactory.text("Echo: " + turnContext.getActivity().getText())
        ).thenApply(sendResult -> null);
    }
 
    @Override
    protected CompletableFuture<Void> onMembersAdded(
        List<ChannelAccount> membersAdded,
        TurnContext turnContext
    ) {
        return membersAdded.stream()
            .filter(
                member -> !StringUtils
                    .equals(member.getId(), turnContext.getActivity().getRecipient().getId())
            ).map(channel -> turnContext.sendActivity
                 (MessageFactory.text("Hello and welcome!")))
            .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null);
    }
}

在继续之前,我们需要注册机器人并准备应用清单。

注册和配置机器人

我们注册机器人以确保安全通信。您可以使用 Microsoft Teams 中的 App Studio 或开发者门户来注册机器人。如果您还没有开发者帐户,请免费注册一个。

在这里,我使用 App Studio 注册机器人。首先,单击Bot Management

接下来,键入msg-extension-bot,然后单击Create。此操作会创建应用程序 ID 和密码。

现在,将这些凭据粘贴到您在上一步中创建的项目 (msg-extension-search) 的 resources/application.json 文件中

接下来,运行ngrok

ngrok http -host-header=rewrite 3978

请注意,端口应与 application.properties.json 中的 server.port 属性匹配。这里,我使用默认值 3978。ngrok应报告转发 URL。

在我的例子中,值为https://e1fa-46-229-154-234.ngrok.io。我们使用此值来配置机器人。因此,让我们回到 Microsoft Teams 清单,并将机器人终结点地址更新为:https://e1fa-46-229-154-234.ngrok.io/api/messages。按Enter保存更改。

制作应用程序清单

注册机器人后,我们可以创建应用程序清单。因此,在 App Studio 中,单击Create a new application。然后,按如下方式填充应用详细信息

  • 简称msg-extension-search
  • 全称:Messaging Extension Search
  • 应用 ID:单击Identification组中的Generate以创建新的 ID
  • 程序包名称db.teams.msgextensionsearch
  • 版本: 1.0.0
  • 描述:键入任何值
  • 开发者信息:键入任何值,例如 Contoso, Inc.,以及 https://www.teams.com
  • 应用 URL:
    • 隐私声明:https://example.azurewebsites.net 或任何其他有效 URL
    • 使用条款:https://example.azurewebsites.net 或任何有效 URL

然后,转到Messaging Extensions并单击Set Up。此操作会激活弹出窗口,您可以在其中单击Use existing bot,单击Select from one of my existing bots单选按钮,然后从下拉列表中选择msg-extension-bot。对于机器人名称,键入:msg-extension-bot

单击Save。然后将显示以下屏幕

现在,单击Command下的Add。App Studio 将显示新的弹出窗口

接下来,选择第一个选项:Allow users to query your service for information and insert that into a message。此选项使用户能够键入将发送到机器人的搜索查询。

之后,应用清单将显示New command屏幕,您可以在其中提供以下内容

  • Command IdsearchQuery
  • Title:Search
  • Context:Command Box, Compose Box
  • 参数:
    • NamesearchQuery
    • Title:Search Query
    • Description:Your search query
    • Type:Text

最后,单击Save。等待弹出窗口关闭,然后转到Finish选项卡中的Test and distribute。单击Install并将应用添加到 Microsoft Teams。

添加 Echo 消息传递扩展

现在,让我们实现 Web 服务。它将检索消息传递扩展的搜索查询,然后生成一个标题设置为“search query”的卡片。

首先,打开 EchoBot.java 文件,并按如下方式修改类声明(将 ActivityHandler 替换为 TeamsActivityHandler

public class EchoBot extends TeamsActivityHandler

然后,添加以下方法

@Override
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionQuery(
        TurnContext turnContext,
        MessagingExtensionQuery query
) {
    // Get query text
    String queryText = GetQueryText(query);
   
    // Create a hero card
    HeroCard card = new HeroCard();
    card.setTitle("Echo");
    card.setSubtitle(queryText);
    card.setText("This sample is a sample hero card");
 
    // Create attachment
    MessagingExtensionAttachment attachment = new MessagingExtensionAttachment();
    attachment.setContent(card);
    attachment.setContentType(HeroCard.CONTENTTYPE);
    attachment.setPreview(card.toAttachment());
 
    // Prepare result
    MessagingExtensionResult result = new MessagingExtensionResult();
    result.setAttachmentLayout("list");
    result.setType("result");
    result.setAttachment(attachment);
 
    // Return the response
    return CompletableFuture.completedFuture(

        new MessagingExtensionResponse(result));      
}

上面的方法读取搜索查询(Microsoft Teams 发送到消息传递扩展的值)并将其存储在 queryText 局部变量下。然后,此值填充 Hero Card,显示一个项目。卡片将具有 Echo 标题,并将 queryText 变量的值显示为副标题。此外,“This is a sample hero card”字符串将显示,如下面的屏幕截图所示

要获取查询文本,请使用辅助方法 GetQueryText

private String GetQueryText(MessagingExtensionQuery query) {
    String queryText = "Empty query";
   
    if (query != null && query.getParameters() != null) {
        List<MessagingExtensionParameter> queryParams = query.getParameters();
 
        if (!queryParams.isEmpty()) {
            MessagingExtensionParameter firstParam = queryParams.get(0);
 
            if(firstParam.getName().equals("searchQuery")) {
                queryText = (String) queryParams.get(0).getValue();
            }
        }
    }
   
    return queryText;
}

此方法接受 MessagingExtensionQuery 类的实例。此类表示 Microsoft Teams 通过消息传递扩展发送到您的机器人的负载。特别是,此负载包括以下内容

  • commandId – 命令的标识符。此标识符对应于我们在应用程序清单中先前声明的。
  • parameters – 查询参数。
  • queryOptions – 查询选项。
  • state – 身份验证或配置流后,Teams 将此参数传递回机器人。

我们配置了消息传递扩展,使其仅处理一个命令。此命令只有一个参数(searchQuery)。在验证输入后,GetQueryText方法查找searchQuery参数,然后读取其值。我们检查参数名称是否与预期的字符串匹配,因为当您第一次打开消息传递扩展时,您会获得带有布尔值的 initialRun 参数。

最后,请注意,onTeamsMessagingExtensionQuery返回一个带有 MessagingExtensionResponseCompletableFuture 实例。我们使用默认构造函数创建 MessagingExtensionResponse

然后,我们通过附加 Hero Card 来补充 MessagingExtensionResponse 实例。我们使用 MessagingExtensionAttachment 类的实例抽象了附件。我们根据创建的 HeroCard 类的实例设置内容和预览。

要测试上述解决方案,请启动 Web 服务。然后,打开 Microsoft Teams。在撰写区域中,单击三个点图标,然后搜索 msg-extension-search

单击msg-extension-search并键入您的搜索查询。请注意,搜索查询会传递到您的 Web 服务,您可以在其中进行处理并生成结果。这里,我们只读取搜索查询并创建 Hero Card。因此,最终结果将如下面的屏幕截图所示

创建带有搜索的消息传递扩展

最后,我们重现带有搜索的 C# 消息传递扩展。该代码获取搜索查询以查找 NuGet 包。为此,消息传递扩展通过以下链接发送 HTTP GET 请求

https://azuresearch-usnc.nuget.org/query?q=<search_query>&prerelease=true

上述 Azure 搜索响应是一个 JSON 格式的文档,包含 NuGet 包列表。例如,查询https://azuresearch-usnc.nuget.org/query?q=sqlite&prerelease=true将返回下面屏幕截图中的 JSON

我们可以使用 findPackage 方法在 Java 中执行相同的操作

private CompletableFuture<List<String[]>> findPackages(String text) {
        return CompletableFuture.supplyAsync(() -> {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .url(String
                         .format(
                             "https://azuresearch-usnc.nuget.org/query?q=id:%s&prerelease=true",
                              text
                            ))
                    .build();
 
            List<String[]> filteredItems = new ArrayList<>();
            try {
                Response response = client.newCall(request).execute();
                JsonNode obj = Serialization.jsonToTree(response.body().string());
                ArrayNode dataArray = (ArrayNode) obj.get("data");
 
                for (int i = 0; i < dataArray.size(); i++) {
                    JsonNode item = dataArray.get(i);
                    filteredItems.add(new String[] {
                            item.get("id").asText(),
                            item.get("version").asText(),
                            item.get("description").asText(),
                            item.has("projectUrl") ? item.get("projectUrl").asText() : "",
                            item.has("iconUrl") ? item.get("iconUrl").asText() : ""
                    });
                }
            } catch (IOException e) {
                LoggerFactory.getLogger(EchoBot.class)
                        .error("findPackages", e);
                throw new CompletionException(e);
            }
            return filteredItems;
        });
    }

然后,我们在 onTeamsMessagingExtensionQuery 中调用上述方法,如下所示

@Override
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionQuery(
        TurnContext turnContext,
        MessagingExtensionQuery query
) {
    String text = "";
    if (query != null && query.getParameters() != null) {
        List<MessagingExtensionParameter> queryParams = query.getParameters();
        if (!queryParams.isEmpty()) {
            text = (String) queryParams.get(0).getValue();
        }
    }
 
    return findPackages(text)
            .thenApply(packages -> {
                // We take every row of the results
                // and wrap them in cards wrapped in MessagingExtensionAttachment objects.
                // The Preview is optional, if it includes a Tap
                // that will trigger the onTeamsMessagingExtensionSelectItem event 
                // back on this bot.
                List<MessagingExtensionAttachment> attachments = new ArrayList<>();
                for (String[] item : packages) {
                    ObjectNode data = Serialization.createObjectNode();
                    data.set("data", Serialization.objectToTree(item));
 
                    CardAction cardAction = new CardAction();
                    cardAction.setType(ActionTypes.INVOKE);
                    cardAction.setValue(Serialization.toStringSilent(data));
                    ThumbnailCard previewCard = new ThumbnailCard();
                    previewCard.setTitle(item[0]);
                    previewCard.setTap(cardAction);
 
                    if (!StringUtils.isEmpty(item[4])) {
                        CardImage cardImage = new CardImage();
                        cardImage.setUrl(item[4]);
                        cardImage.setAlt("Icon");
                        previewCard.setImages(Collections.singletonList(cardImage));
                    }
 
                    HeroCard heroCard = new HeroCard();
                    heroCard.setTitle(item[0]);
 
                    MessagingExtensionAttachment attachment = 
                                                 new MessagingExtensionAttachment();
                    attachment.setContentType(HeroCard.CONTENTTYPE);
                    attachment.setContent(heroCard);
                    attachment.setPreview(previewCard.toAttachment());
 
                    attachments.add(attachment);
                }
 
                MessagingExtensionResult composeExtension = new MessagingExtensionResult();
                composeExtension.setType("result");
                composeExtension.setAttachmentLayout("list");
                composeExtension.setAttachments(attachments);
 
                // The list of MessagingExtensionAttachments 
                // must be wrapped in a MessagingExtensionResult
                // wrapped in a MessagingExtensionResponse.
                return new MessagingExtensionResponse(composeExtension);
            });
}

此外,我们可以使用 onTeamsMessagingExtensionSelectItem 来响应用户操作

@Override
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionSelectItem(
        TurnContext turnContext,
        Object query
) { 
    // The Preview card's Tap should have a Value property assigned, 
    // this will be returned to the bot in this event.
    Map cardValue = (Map) query;
    List<String> data = (ArrayList) cardValue.get("data");
    CardAction cardAction = new CardAction();
    cardAction.setType(ActionTypes.OPEN_URL);
    cardAction.setTitle("Project");
    cardAction.setValue(data.get(3));
 
    // We take every row of the results
    // and wrap them in cards wrapped in MessagingExtensionAttachment objects.
    // The Preview is optional, if it includes a Tap
    // that will trigger the onTeamsMessagingExtensionSelectItem event back on this bot.
    ThumbnailCard card = new ThumbnailCard();
    card.setTitle(data.get(0));
    card.setSubtitle(data.get(2));
    card.setButtons(Arrays.asList(cardAction));
 
    if (StringUtils.isNotBlank(data.get(4))) {
        CardImage cardImage = new CardImage();
        cardImage.setUrl(data.get(4));
        cardImage.setAlt("Icon");
        card.setImages(Collections.singletonList(cardImage));
    }
 
    MessagingExtensionAttachment attachment = new MessagingExtensionAttachment();
    attachment.setContentType(ThumbnailCard.CONTENTTYPE);
    attachment.setContent(card);
 
    MessagingExtensionResult composeExtension = new MessagingExtensionResult();
    composeExtension.setType("result");
    composeExtension.setAttachmentLayout("list");
    composeExtension.setAttachments(Collections.singletonList(attachment));
    return CompletableFuture.completedFuture(new MessagingExtensionResponse(composeExtension));
}

运行上述代码后,您将看到 NuGet 包列表

您可以单击任何您想要的项。消息传递扩展会将所选 NuGet 包的详细信息填充到消息撰写区域中

后续步骤

在本教程中,我演示了构建 Microsoft Teams 消息传递扩展的步骤。我们了解到消息传递扩展使用 Microsoft Bot Framework 来保护与外部 Web 服务的通信。机器人生成的响应可以由卡片组成。

请继续本系列三篇文章的第二部分,了解如何构建链接展开消息传递扩展

要了解如何使用 Azure 服务构建、迁移和扩展 Azure 上的 Java 应用程序,请查看Azure Java 入门

© . All rights reserved.