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





0/5 (0投票)
本教程演示了构建 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。
您可以在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 Id:
searchQuery
- Title:Search
- Context:Command Box, Compose Box
- 参数:
- Name:
searchQuery
- Title:Search Query
- Description:Your search query
- Type:Text
- Name:
最后,单击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
返回一个带有 MessagingExtensionResponse
的 CompletableFuture
实例。我们使用默认构造函数创建 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 入门。