使用无服务器 Python 构建 Microsoft Teams 应用(第三部分):使用 Python 和 Azure Functions 构建具有自适应卡片的选项卡






4.45/5 (3投票s)
本文应演示如何创建 Python 无服务器 Azure Functions Web 应用。
在本系列三篇文章的前两篇中,我们探讨了如何使用 Python 和 Azure Functions 构建 Microsoft Teams 个人选项卡应用 和 具有单一登录 (SSO) 的频道或群组应用。在这里,我们将演示如何使用 Python 创建一个 Azure Function 应用,该应用将复制 GitHub 上 具有自适应卡片的选项卡 示例 C# 应用的功能。
Microsoft 提供了关于 Microsoft Teams 自适应卡片的精彩概述。自适应卡片是基于 JSON 的交互式内容片段,嵌入在各种托管应用程序中,包括 Microsoft Teams 和 Outlook。
自适应卡片的美妙之处在于其简洁性。您无需将外部 Web 应用程序嵌入应用程序的 iframe 中,而是可以开发一个响应各种活动的机器人,提供纯 JSON 格式的静态内容和操作,并由托管应用控制样式和安全性。
如果您了解一些 Python 并且拥有您的 免费 Azure 帐户,您就可以通过本教程逐步理解并实现自适应卡片应用。或者,您也可以 从 GitHub 下载源代码 并直接跳转到配置步骤来启动和运行应用。
检查应用程序架构
与本系列文章中我们之前介绍的个人选项卡和频道选项卡应用一样,这里我们正在使用 Python 和 Flask 构建一个无服务器应用,在 Microsoft Teams 选项卡中提供信息。但是,与其它应用不同的是,我们正在构建一个无服务器函数应用,它提供一个具有基于 JSON 的自适应卡片的 Teams 选项卡,而不是完整的 HTML 内容。
这次,我们正在创建一个单一的 az-function-messages
HTTP 触发器,为 Teams 提供逻辑和 JSON 自适应卡片。
注册 Azure Bot
在向 Teams 发送访问请求之前,我们必须在 Azure Active Directory (AAD) 应用注册 门户中注册新应用程序,并为应用创建密钥以进行 Azure 身份验证。请按照以下步骤设置您新的 Azure Bot 注册。
请遵循 Tab with Adaptive Cards README 中的说明。您可以使用相同的步骤将您的 Teams Python 应用程序注册到 Azure AD。但是,这次我们将创建一个名为 adaptive-cards-bot
的应用注册。
然后,在 Azure 门户的应用注册中,更改消息传递终结点链接,使其在配置中指向您的函数应用网站的 /api/az-function-messages
终结点(例如,*.azurewebsites.net)。
使用 Visual Studio Code 创建应用程序
本节将指导您构建一个在 Python 项目之上运行 Azure 函数的基本项目。
首先,创建一个新的本地目录结构,如下所示:
\PythonTeamsApps
+-- \AdaptiveCards
在终端中键入 code.
,在 Visual Studio Code 中打开选定的文件夹。
\AdaptiveCards>code .
现在是创建第一个 Azure 函数的时候了。选择 Azure 选项卡,然后在函数部分内单击“创建函数”图标。
然而,我们仍然没有 Azure 上的函数项目。我们现在是在本地创建它,稍后上传到云端。当对话框询问您是否要创建新项目时,请单击是。
正如出现的列表所示,Azure 函数支持许多编程语言,包括 Python、JavaScript、TypeScript、C#、PowerShell 和 Java。选择Python。
现在我们必须为我们的函数项目选择模板。Azure Functions 触发器有多种形式,以适应各种情况,取决于函数应何时运行。在我们的自适应卡片应用中,我们需要一个 HTTP 触发器,它将响应我们的 Azure Bot 通过 Teams 选项卡发出的 HTTP 请求。
现在给 Azure 函数起一个名字:az-function-messages。
然后,提供授权级别。选择匿名,使其公开。
就是这样!您现在拥有了一个新的 Azure 函数。
现在选择函数组,然后单击部署到函数应用按钮。
我们还没有为我们的自适应卡片应用创建 Azure 中的函数应用。通过单击第一个选项+ 在 Azure 中创建新的函数应用来创建它。
然后,将项目命名为 adaptive-cards-function-app
,或类似的名称。
观察 VS Code 现在如何显示一个新文件夹,您可以在其中方便地浏览存储在 Azure 云中的函数应用数据,包括文件、日志和设置。
接下来,右键单击应用程序设置节点,然后选择下载远程设置。
然后按 F5 运行函数应用。
正如屏幕截图所示,HTTP 触发器函数正在本地的 7071 端口运行。
az-function-messages: [GET,POST] https://:7071/api/az-function- messages
您可以通过在浏览器中打开 HTTP 触发器来对其进行测试。
观察 HTTP 响应仅仅是纯文本。现在,让我们让 Azure 函数响应基于 JSON 的自适应卡片,以在 Microsoft Teams 选项卡中渲染。
首先,打开 自适应卡片设计器。然后,单击新建卡片按钮。接下来单击空白卡片,并将选择主机应用值更改为Microsoft Teams - 浅色。
然后,从元素部分拖动文本块并将其放到空白的自适应卡片上。
接下来,双击文本块并输入文本:“欢迎使用 Python 和 Azure Functions 构建的 MS Teams 自适应卡片”
然后,单击+ 展开元素属性面板,并将字体粗细值更改为粗体,并将大小更改为特大。
现在单击+ 展开卡片载荷编辑器。
让我们在我们的应用中使用这个 JSON 卡片定义。在您的项目中创建一个新的 Models 文件夹,并添加一个 AdaptiveCards.py 文件,内容如下:
def get_sample_card():
return
"""
"""
然后,复制自适应卡片设计器中的 JSON 载荷,并将其粘贴到 get_sample_card
函数的三重引号内。
def get_sample_card():
return
"""{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "WELCOME TO MS TEAMS ADAPTIVE CARDS WITH PYTHON AND AZURE FUNCTIONS",
"wrap": true,
"size": "ExtraLarge",
"weight": "Bolder"
}
]
}
"""
然后,打开 az-function-messages\__init__.py 文件,并用以下代码片段替换其内容:
import azure.functions as func
from Models.AdaptiveCards import get_sample_card
def main(req: func.HttpRequest) -> func.HttpResponse:
return func.HttpResponse(
"""{
"tab":{
"type":"continue",
"value":{
"cards":[
{
"card":""" + get_sample_card() + """}
]
}
}
}
""",
status_code=200
)
安装 Teams 应用
现在,让我们将基础 Python 应用上传到 Microsoft Teams。
首先,从原始 C# 项目的 appPackage 文件夹 下载文件。然后,在项目根目录中创建一个 appPackage 文件夹。将下载的文件移动到那里。
接下来,打开 manifest.json 文件。以下是 Microsoft Teams 需要的集成我们 Python 应用的配置。将 {{BOT-ID}}
占位符替换为您在 Azure 中注册的应用的 MicrosoftAppId。
<<some JSON code here>>
"staticTabs": [
{
"entityId": "homeTab",
"name": "Home",
"contentBotId": "{{BOT-ID}}",
"scopes": [ "personal" ]
}
<<some JSON code here>>
接下来,将这三个文件压缩。或者,您可以自定义 color.png 和 outline.png 图像以更好地识别您的应用。
接下来,打开 Microsoft Teams,选择应用选项卡。单击上传自定义应用。
然后,选择您刚刚创建的 .zip 文件。
接下来,在为您组织构建部分选择新的 TabAdaptiveCard 应用。
现在,回到 VS Code 项目,按 F5 运行应用程序。打开应用。
观察 Teams 如何显示您之前在自定义 Python 应用中创建的基础自适应卡片。
为什么使用自适应卡片?
自适应卡片解决的问题并非新问题。您希望通过显示另一个应用程序的数据和功能来扩展现有应用程序的功能。
解决这个问题的传统方法比较笨重。例如,Microsoft Teams 选项卡在 iframe 中显示嵌入的应用程序。这种方法使托管应用程序能够完全控制数据的呈现和安全性。我们在本系列的前两篇文章中体验了这种方法。
自适应卡片提供了一种轻量级的替代方案。这种方法需要嵌入式应用程序和托管应用程序之间的协调更少,并且它也适用于不同类型的应用程序。自适应卡片足够灵活,可以满足大多数需求,同时让托管应用程序能够牢牢控制样式和安全性。
在您的 Bot 中使用自适应卡片
Microsoft 将自适应卡片技术嵌入到包括 Teams 在内的许多产品中。由于这里的空间不允许我们讨论这项技术的许多方面,您可能需要查阅其他材料。如果您想深入了解如何有效地设计自适应卡片,请参考 Microsoft Docs。
下面的图表和表格说明了我们在 Bot 应用程序的上下文中如何使用自适应卡片。
步骤 | 类型 | 描述 |
1 | tab/fetch | 当用户打开自适应卡片选项卡时,您的 Bot 收到的第一个调用请求是 tab/fetch。当您的 Bot 收到请求时,它会发送一个 tab continue 响应或一个 tab auth 响应。 |
2 | 决策 | 当用户令牌不可用时,我们需要发送 auth 响应。 |
3 | auth | 用于身份验证的卡片响应。 |
4 | openUrl | 在默认浏览器中打开一个 URL。 |
5 | continue | 具有用户图片、姓名和任务模块调用操作的自适应卡片。 |
6 | Action.Submit | 收集输入字段,与可选的 data 字段合并,并将事件发送到客户端。 |
7 | continue | task/fetch:将在任务模块中显示的自适应卡片。 |
8 | Action.Submit | task/submit:收集输入字段,与可选的 data 字段合并,并将事件发送到客户端。 |
9 | continue | 用于显示 task/submit 操作的自适应卡片。 |
10 | continue | 显示示例文本和提交操作的自适应卡片。 |
11 | Action.Submit | tab/submit:收集输入字段,与可选的 data 字段合并,并将事件发送到客户端。 |
12 | continue | 用于显示登出操作的自适应卡片。 |
在实现上述卡片代码之前,请安装以下 Python 包:
- Microsoft Bot Framework 允许您使用 Python 开发与 MS Teams 交互的 Bot。
- Microsoft Graph 将使我们能够获取有关已登录用户的信息。
- Flask 是一个用于 Python 的微型 Web 框架,它将在 MS Teams 与我们的 Bot 的 Azure 函数交互时处理 HTTP 请求/响应。
pip install botbuilder-core
pip install botbuilder-schema
pip install botbuilder-integration-aiohttp
pip install microsoftgraph-python
pip install flask[async]
现在,打开 Models 文件夹中的 AdaptiveCards.py 文件,并用以下代码替换其内容:
from http import HTTPStatus
from botbuilder.core import (
CardFactory
)
# Card response for authentication
def createAuthResponse (signInLink):
adaptive_card = {
"status": HTTPStatus.OK,
"body": {
"tab": {
"type": "auth",
"suggestedActions": {
"actions": [
{
"type": "openUrl",
"value": signInLink,
"title": "Sign in to this app"
}
]
}
},
}
}
return CardFactory.adaptive_card(adaptive_card)
def createFetchResponse(userImage, displayName):
adaptive_card = {
"status": HTTPStatus.OK,
"body": {
"tab": {
"type": "continue",
"value": {
"cards": [
{
"card": getAdaptiveCardUserDetails(userImage, displayName),
},
{
"card": getAdaptiveCardSubmitAction(),
}
]
},
},
}
}
return CardFactory.adaptive_card(adaptive_card)
# Adaptive Card with user image, name and Task Module invoke action
def getAdaptiveCardUserDetails(image, name):
if (image and image != ''):
image = f"data:image/png;base64, {image}"
else:
image = "https://cdn.vox-cdn.com/thumbor/Ndb49Uk3hjiquS041NDD0tPDPAs=/0x169:1423x914/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/7342855/microsoftteams.0.jpg"
adaptive_card = {
"$schema": 'http://adaptivecards.io/schemas/adaptive-card.json',
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": image,
"size": "Medium"
}
],
"width": "auto"
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"weight": "Bolder",
"text": 'Hello: ' + name,
"wrap": True
},
],
"width": "stretch"
}
]
},
{
"type": 'ActionSet',
"actions": [
{
"type": "Action.Submit",
"title": "Show Task Module",
"data": {
"msteams": {
"type": "task/fetch"
}
}
}
]
}
],
"type": 'AdaptiveCard',
"version": '1.4'
}
return adaptive_card
# Adaptive Card showing sample text and Submit Action
def getAdaptiveCardSubmitAction():
return {
"$schema": 'http://adaptivecards.io/schemas/adaptive-card.json',
"body": [
{
"type": 'Image',
"height": '157px',
"width": '300px',
"url": 'https://cdn.vox-cdn.com/thumbor/Ndb49Uk3hjiquS041NDD0tPDPAs=/0x169:1423x914/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/7342855/microsoftteams.0.jpg',
},
{
"type": 'TextBlock',
"size": 'Medium',
"weight": 'Bolder',
"text": 'tab/fetch is the first invoke request that your bot receives when a user opens an Adaptive Card tab. When your bot receives the request, it either sends a tab continue response or a tab auth response',
"wrap": True,
},
{
"type": 'TextBlock',
"size": 'Medium',
"weight": 'Bolder',
"text": 'tab/submit request is triggered to your bot with the corresponding data through the Action.Submit function of Adaptive Card',
"wrap": True,
},
{
"type": 'ActionSet',
"actions": [
{
"type": 'Action.Submit',
"title": 'Sign Out',
}
],
}
],
"type": 'AdaptiveCard',
"version": '1.4'
};
def invokeTaskResponse():
adaptiveCard = {
"status": HTTPStatus.OK,
"body": {
"task": {
"type": 'continue',
"value": {
"card": {
"contentType": "application/vnd.microsoft.card.adaptive",
"content": adaptiveCardTaskModule()
},
"heigth": 250,
"width": 400,
"title": 'Sample Adaptive Card'
}
}
}
}
return CardFactory.adaptive_card(adaptiveCard)
# Adaptive Card to show in task module
def adaptiveCardTaskModule():
return {
'$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
'body': [
{
'type': 'TextBlock',
'size': 'Medium',
'weight': 'Bolder',
'text': 'Sample task module flow for tab'
},
{
'type': 'Image',
'height': '50px',
'width': '50px',
'url': 'https://cdn.vox-cdn.com/thumbor/Ndb49Uk3hjiquS041NDD0tPDPAs=/0x169:1423x914/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/7342855/microsoftteams.0.jpg',
},
{
'type': 'ActionSet',
'actions': [
{
'type': "Action.Submit",
'title': "Close",
'data': {
'msteams': {
'type': "task/submit"
}
}
}
]
}
],
'type': 'AdaptiveCard',
'version': '1.4'}
# Card response for tab submit request
def taskSubmitResponse():
adaptive_card = {
'status': HTTPStatus.OK,
'body': {
'task': {
'value': {
'tab': {
'type': "continue",
'value': {
'cards': [
{
'card': taskSubmitCard()
}
]
}
}
},
'type': "continue"
},
'responseType': "task"
}
}
return CardFactory.adaptive_card(adaptive_card)
# Adaptive Card to show task/submit action
def taskSubmitCard():
return {
'$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
'body': [
{
'type': 'TextBlock',
'size': 'Medium',
'weight': 'Bolder',
'text': 'The action called task/submit. Please refresh to load contents again.',
'wrap': True,
}
],
'type': 'AdaptiveCard',
'version': '1.4'
}
# Card response for tab submit request
def createSubmitResponse():
adaptive_card = {
'status': HTTPStatus.OK,
'body': {
'tab': {
'type': "continue",
'value': {
'cards': [
{
'card': signOutCard(),
}
]
},
},
}
}
return CardFactory.adaptive_card(adaptive_card)
# Adaptive Card to show sign out action
def signOutCard():
return {
'$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
'body': [
{
'type': 'TextBlock',
'size': 'Medium',
'weight': 'Bolder',
'text': 'Sign out successful. Please refresh to Sign in again.',
'wrap': True,
}
],
'type': 'AdaptiveCard',
'version': '1.4'
}
现在,创建一个名为 bots 的文件夹,并在其中创建两个文件:__init__.py 和 teams_task_module_bot.py。
\bots
|---- __init__.py
+---- teams_task_module_bot.py
将此代码片段添加到 __init__.py 文件中。
from .teams_task_module_bot import TeamsTaskModuleBot
__all__ = ["TeamsTaskModuleBot"]
然后,将此代码块添加到 teams_task_module_bot.py 中。
import os
from botbuilder.core import (
TurnContext,
)
from botbuilder.schema.teams import (
TaskModuleRequest,
TaskModuleResponse,
TabRequest,
TabSubmit
)
from botbuilder.core.teams import TeamsActivityHandler
from Models.AdaptiveCards import createAuthResponse, createSubmitResponse, invokeTaskResponse, taskSubmitResponse
from graphClient import GraphClient
class TeamsTaskModuleBot(TeamsActivityHandler):
async def on_teams_tab_fetch( # pylint: disable=unused-argument
self, turn_context: TurnContext, tab_request: TabRequest
):
# When the Bot Service Auth flow completes, turn_context will contain a magic code used for verification.
magicCode = ''
if turn_context.activity.value is not None and 'state' in turn_context.activity.value is not None:
magicCode = turn_context.activity.value['state']
# Getting the tokenResponse for the user
tokenResponse = await turn_context.adapter.get_user_token(turn_context, os.environ.get("ConnectionName"), magicCode)
if (not tokenResponse) or (not tokenResponse.token):
# Token is not available, hence we need to send back the auth response
# Retrieve the OAuth Sign in Link.
signInLink = await turn_context.adapter.get_oauth_sign_in_link(turn_context, os.environ.get("ConnectionName"))
# Generating and returning auth response.
return createAuthResponse(signInLink)
graphClient = GraphClient(tokenResponse.token);
profile = graphClient.GetUserProfile()
userImage = graphClient.GetUserPhoto(profile["id"])
return createAuthResponse(userImage, profile["displayName"])
async def on_teams_tab_submit( # pylint: disable=unused-argument
self, turn_context: TurnContext, tab_submit: TabSubmit
):
adapter = turn_context.adapter
await adapter.sign_out_user(turn_context, os.environ.get("ConnectionName"))
# Generating and returning submit response.
return createSubmitResponse();
async def on_teams_task_module_fetch(
self, turn_context: TurnContext, task_module_request: TaskModuleRequest
) -> TaskModuleResponse:
return invokeTaskResponse()
async def on_teams_task_module_submit(
self, turn_context: TurnContext, task_module_request: TaskModuleRequest
) -> TaskModuleResponse:
return taskSubmitResponse()
将 az-function-messages 文件夹内的 __ini__.py 文件中的代码替换为以下内容:
from flask import Flask,request,Response
import sys
import azure.functions as func
import os
from botbuilder.schema import Activity
from botbuilder.core import(
BotFrameworkAdapterSettings,
BotFrameworkAdapter
)
from http import HTTPStatus
from bots.teams_task_module_bot import TeamsTaskModuleBot
from config import DefaultConfig
CONFIG = DefaultConfig()
app = Flask(__name__)
this = sys.modules[__name__]
this.cacheHelper = None
SETTINGS = BotFrameworkAdapterSettings(os.environ.get("MicrosoftAppId"), os.environ.get("MicrosoftAppPassword"))
ADAPTER = BotFrameworkAdapter(SETTINGS)
BOT = TeamsTaskModuleBot()
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
response = func.WsgiMiddleware(app).handle(req, context)
return response
@app.route("/api/az-function-messages",methods=["POST"])
async def messages():
if "application/json" in request.headers["content-type"]:
jsonmessage = request.json
else:
return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
activity = Activity().deserialize(jsonmessage)
auth_header = request.headers["Authorization"] if "Authorization" in request.headers else ""
response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
if response:
return response.body["content"]["body"]
return func.HttpResponse(status_code=HTTPStatus.OK)
然后,打开 local.settings.json 文件,并添加以下键:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "***********",
"FUNCTIONS_WORKER_RUNTIME": "python",
"FUNCTIONS_EXTENSION_VERSION": "~4",
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": ""***********",
"WEBSITE_CONTENTSHARE": ""***********",
"APPINSIGHTS_INSTRUMENTATIONKEY": ""***********",
"MicrosoftAppId": "<<YOUR-MICROSOFT-APP-ID>>",
"MicrosoftAppPassword": "<<YOUR-MICROSOFT-APP-PASSWORD>>",
"ConnectionName": "<<YOUR-CONNECTION-NAME>>",,
"ApplicationBaseUrl": "<<YOUR-APPLICATION-BASE-URL>>"
}
}
接下来,创建一个名为 config.py 的文件,内容如下:
import os
class DefaultConfig:
PORT = 3978
APP_ID = os.environ.get("MicrosoftAppId", "")
APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
CONNECTION_NAME = os.environ.get("ConnectionName", "")
最后,创建一个名为 graphClient.py 的文件,内容如下:
import requests
import os
import sys
import base64
from microsoftgraph.client import Client
this = sys.modules[__name__]
this.cache = None
graph_url = 'https://graph.microsoft.com/v1.0'
class GraphClient():
def __init__(self, token):
if ((token == None) or (token.strip() == "")):
raise Exception("SimpleGraphClient: Invalid token received.");
self._token = token;
if this.cache is None:
this.cache = dict()
# Get an Authenticated Microsoft Graph client using the token issued to the user.
self.graphClient = Client(os.environ.get("MicrosoftAppId"), os.environ.get("MicrosoftAppPassword"))
def GetUserProfile(self):
# Send GET to /me
user = requests.get(
'{0}/me'.format(graph_url),
headers={
'Authorization': 'Bearer {0}'.format(self._token)
})
# Return the JSON result
return user.json()
def GetUserPhoto(self, user_id):
if user_id not in this.cache:
photo_response = requests.get(
'{0}/me/photo/$value'.format(graph_url),
headers={
'Authorization': 'Bearer {0}'.format(self._token)
}, stream=True)
photo = photo_response.raw.read()
this.cache[user_id] = base64.b64encode(photo).decode('utf-8')
return this.cache[user_id]
测试完整应用程序
现在,您已经准备好启动和运行您的应用程序了。
按 F5 键启动应用程序,然后在 Microsoft Teams 中打开 TabAdaptiveCard 应用。
当打开新的 Microsoft Teams 选项卡时,它会调用您的 Bot 上的 on_teams_tab_fetch
函数。如果用户已通过身份验证,该函数将调用 createFetchResponse
函数。否则,它将调用 createAuthResponse 函数。
由于用户尚未登录,您将在此处看到由 createAuthResponse
函数生成的身份验证卡片。
当用户登录时,Microsoft Teams 会调用您的 Bot 生成的 signInLink
URL。此链接将用户重定向到 Microsoft 登录网站,该网站使用 OAuth 2 协议通过 Microsoft 帐户对用户进行身份验证。
现在是您的 Bot 的 on_teams_tab_fetch
函数调用 createFetchResponse
函数并显示两张卡片的时候了:一张用于显示用户详细信息,另一张用于显示登出操作。
在 Microsoft 对用户进行身份验证后,即使用户关闭并重新打开 Microsoft Teams,页面也会记住它。因此,您的 Azure Function Bot 应用与本系列文章中先前的应用程序一样,享有单点登录 (SSO) 的相同好处。
当用户单击显示任务模块按钮时,Microsoft Teams 会调用您的 Bot 的 on_teams_task_module_submit
函数,并显示 taskSubmitResponse。
当用户单击关闭按钮时,taskSubmitResponse
函数将显示 taskSubmitCard
卡片。
当用户单击登出按钮时,createSubmitResponse
函数将显示 signOutCard
卡片。
后续步骤
在本系列“使用无服务器 Python 构建 Microsoft Teams 应用”的最后一篇文章中,我们讨论了如何将 Microsoft Teams 与 Azure 无服务器函数集成以创建自适应卡片应用。
这个第三个应用在如何使用 Azure Functions 方面与前两个应用存在一些根本性差异。它没有在 Teams 选项卡中渲染 HTML 内容,而是使用了自适应卡片提供纯 JSON 内容,并允许客户端 Microsoft Teams 提供样式以无缝渲染 HTML。
在构建我们的新应用时,我们学习了如何创建和配置 Azure Bot,以便在响应不同的 Microsoft Teams 活动请求时返回各种自适应卡片。借助 Microsoft Graph,我们代表 Teams 访问了 Azure Active Directory (AD) 中的安全用户配置文件信息,并使用自定义数据填充了自适应卡片。
您已经学会了如何使用 Microsoft Teams、Python 和 Visual Studio Code,轻松创建一个托管在 Azure 云上的、功能齐全的频道选项卡应用的最小化项目。您现在拥有一个功能性应用程序,可以作为您未来自适应卡片应用的模板。
在本系列中,我们已经探索了使用 Python 和无服务器 Azure 函数构建有用应用程序的简便性。当您将这些应用程序集成到 Teams 的个人、频道和群组选项卡中时,您的同事就可以轻松地从他们每天都在使用的应用程序中访问它们。将自适应卡片集成到您的应用程序中,可以实现 Teams 与其他服务之间的内容无缝共享。
既然您已经知道如何做到这一切,请注册 Azure 免费试用版,以扩展您在此处创建的应用,或创建一个独特的应用来提高您组织的生产力。
要了解更多关于如何为个人、您的团队、您的组织或所有 Microsoft Teams 用户构建应用的信息,请参阅 为 Microsoft Teams 开发应用。