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

使用无服务器 Python 构建 Microsoft Teams 应用(第一部分):使用 Python 和 Azure Functions 构建个人选项卡 Teams 应用

2022年1月17日

CPOL

14分钟阅读

viewsIcon

15146

downloadIcon

147

在本文中,我们将演示如何使用 Python 创建一个无服务器的 Azure Functions Web 应用。

企业越来越信任流行的 Python 语言来处理各种工作负载。然而,使用 Python 构建企业应用程序并不意味着使用老式的单体框架。我们将通过编写无服务器的 Python Azure Functions,在这一系列三篇文章中构建 Teams 应用。

在第一篇文章中,我们将创建一个 Microsoft Teams 应用并将其放置在个人标签中。个人标签向当前用户展示一个包含其信息的页面。这种类型的 Teams 应用使用户能够组织他们的个人日历、待办事项列表、便签等等。

我们将第一个应用基于一个现有的 C# 示例,该示例说明了如何实现一个带有单一登录 (SSO) 身份验证的个人标签。我们不是要重新发明轮子。相反,我们正在使 Teams 开发对云原生 Python 开发人员更加易于访问。

在此过程中,我们将重点介绍 Microsoft 提供的整个工具生态系统,这些工具可以轻松构建和部署 Python 应用:支持 Python 扩展的Visual Studio Code (VS Code)、Azure Functions 扩展以及Microsoft Authentication Library for Python (MSAL)。

使用本文作为教程,您可以从头开始一步一步地实现整个应用程序。或者,您可以从 GitHub 下载源代码并跳转到配置步骤,以快速启动并运行。您应该了解 Python 并拥有一个免费的 Azure 账户才能遵循本教程,我们将向您展示如何使用 Azure Functions、Teams 和其他 Microsoft 解决方案。

在 Teams 应用程序中实现 SSO

我们的 Teams 应用使用Flask,一个基于 Python 的微 Web 框架,它使用Jinja Web 模板引擎渲染 HTML 内容。 

当无服务器函数首次加载时,它们会从磁盘读取 HTML 模板并将其缓存到变量中,以便快速渲染网页 HTML。

我们正在使用 Python 和 Flask 构建一个渲染 HTML 内容的应用,但我们构建的是一个无服务器应用,而不是创建一个服务器端后端。无服务器意味着我们不依赖传统的 Web 服务器。相反,我们使用HTTP 触发器函数来执行带有 Flask 和 Jinja 的 Python 代码。这些函数接收静态 HTML 和 JavaScript,并提供一个客户端 Teams 标签,其中包含包含网页和令牌的 HTTP 响应。

此应用程序仅包含四个Azure Functions:get-user-access-token、index、auth-start 和 auth-end。Azure Functions 非常适合我们正在构建的 Teams 个人标签这样的小型应用程序。与传统的服务器为中心的基础设施相比,无服务器函数为您提供了简化的代码、更高的可伸缩性、更快的发布时间和更低的成本。

使用 Visual Studio Code 创建应用程序

本节将指导您构建一个在 Python 项目之上运行 Azure Functions 的基本项目。

首先,下载并安装Visual Studio Code。然后,创建一个如下所示的新本地目录结构

\PythonTeamsApps
     +-- \PersonalTabSSO

接下来,在终端中键入 code. 以在 Visual Studio Code 中打开选定的文件夹

\PersonalTabSSO>code .

然后,点击 **Extensions** 选项卡搜索并安装“Azure Functions”

Azure Functions 扩展使您能够直接从 VS Code 快速创建、调试、管理和部署无服务器应用。这些工具与您的 Azure 账户无缝集成,可实现快速开发和直接部署。

现在,搜索并安装 Python 扩展

Microsoft 的 VS Code Python 扩展为该语言提供了丰富的支持,包括 IntelliSense、调试、代码导航和格式化以及重构。

现在是时候创建我们的第一个 Azure 函数了。选择 **Azure** 选项卡,然后在 **Functions** 部分中点击 **Create Function** 图标

 

但是等等,我们还没有在 Azure 上创建函数项目。我们现在在本地创建它,稍后将其上传到云端。当弹出窗口询问您是否要创建新项目时,请点击 **Yes**。

从出现的列表中可以看出,Azure Functions 支持许多编程语言,包括 Python、JavaScript、TypeScript、C#、PowerShell 和 Java。选择 **Python**

然后,选择最新的 Python 解释器版本(在撰写本文时为 **3.9.7**)。

现在我们选择我们函数项目的模板。Azure Functions 触发器有多种形式,以适应各种场景,具体取决于函数应该何时运行。例如,您可能希望一个函数每天晚上 10 点运行一次。或者,您可能希望它在消息队列处理项目时运行,或者当有人上传新图像到 Blob 存储时运行。

在我们的应用中,我们需要 HTTP 触发器,它将响应来自 Teams 标签的 HTTP 请求: 

现在我们为 Azure 函数命名:az-function-index

然后,我们提供授权级别。我们通过选择 **Anonymous** 使其公开

就这样!我们现在有了第一个 Azure 函数

 

我们现在重复此过程,直到我们有四个 HTTP 触发器函数

  • az-function-get-user-access-token
  • az-function-index
  • az-function-auth-start
  • az-function-auth-end

现在,我们选择 **Functions** 组,然后点击 **Deploy to the Function App** 按钮: 

我们仍然没有 Azure 中的 Function App,所以我们通过点击第一个选项 **+ Create new Function App in Azure** 来创建它。

我们将项目命名为 personal-tab-sso-function-app

观察 VS Code 现在如何显示一个新文件夹,您可以在其中方便地浏览存储在 Azure 云中的 Function App 数据,包括文件、日志和设置

点击 **Explorer** 选项卡查看您的源代码。请注意,(1) 每个 Azure Function 都是一个不同的 Python 模块,(2) host.json 元数据文件包含 Function App 实例中所有 Functions 的配置,(3) local.settings.json 文件在应用本地运行时提供设置。 

现在,按 F5 运行函数应用: 

如屏幕截图所示,所有四个 HTTP 触发器都将在本地 7071 端口上运行

  • az-function-auth-end: [GET,POST] https://:7071/api/az-function-auth-end
  • az-function-auth-start: [GET,POST] https://:7071/api/az-function-auth-start
  • az-function-get-user-access-token: [GET,POST] https://:7071/api/az-function-get-user-access-token
  • az-function-index: [GET,POST] https://:7071/api/az-function-index

您可以通过在浏览器中打开任何 HTTP 触发器来测试它们: 

观察 HTTP 响应仅仅是纯文本。稍后,我们将探讨如何让 Azure Functions 响应包含在 Microsoft Teams 标签中渲染的完整 HTML 内容。

注册 Azure 上的应用程序

在向 Teams 发送访问请求之前,我们必须在Azure Active Directory (AAD) 应用注册门户中注册一个新应用程序,并创建一个应用用于向 Azure 进行身份验证的密钥。请按照以下步骤设置您的新应用注册。

首先,安装ngrok。Ngrok 是一个反向代理,它创建一个安全的“隧道”,从公共终结点到本地 Web 服务。Microsoft Teams 不允许标签使用本地 URL,但我们可以通过使用 ngrok 并公开我们本地运行的 HTTP 触发器 Azure Functions 来克服此限制。

因此,我们在终端中运行以下命令

\ngrok.exe http -host-header=rewrite 7071

每次运行 ngrok 时,它都会生成随机 URL。在此示例中,localhost 的 7071 端口会隧道到一个 ngrok 公共 URL

https://6081-2804-14c-bf2f-a532-f10d-9764-5d93-b171.ngrok.io to
https://:7071

在不关闭 ngrok 窗口的情况下继续本教程的其余部分。

此注册还必须允许 Teams 访问并向应用程序终结点返回数据。使用上面的 ngrok URL 并将 3978 端口替换为 7071 端口,请在Teams Tab SSO Authentication README中按照此过程的说明步骤进行操作。您可以使用相同的步骤将您的 Teams Python 应用程序注册到 Azure AD。

如果一切顺利,您在 Azure 门户中的 **App Registration** 将显示 **Redirect URI** 链接指向 ngrok URI,并勾选 Implicit grant 和 hybrid flows 选项

另外,请确保在 **Expose an API** 选项卡中的 **App ID URI** 指向 ngrok URI。 

在 Teams 中测试应用程序

现在,我们将上传我们的基本 Python 应用到 Microsoft Teams。

首先,从原始 C# 项目 appPackage文件夹下载文件。然后,在项目根目录中创建一个 appPackage 文件夹。将下载的文件移到那里。

 

接下来,打开 manifest.json 文件。以下是必需的配置,以便 Microsoft Teams 知道如何与我们的 Python 应用集成。用您在 Azure 中注册的应用的数据替换缺失的信息: 

{
  "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
  "manifestVersion": "1.5",
  "version": "1.0.0",
  "id": "<<MICROSOFT-APP-ID>>",
  "packageName": "com.contoso.teamsauthsso",
    "developer": {
    "name": "Microsoft",
    "websiteUrl": "https://www.microsoft.com",
    "privacyUrl": "https://www.microsoft.com/privacy",
    "termsOfUseUrl": "https://www.microsoft.com/termsofuse"
    },
    "name": {
      "short": "Python Personal Tab SSO",
      "full": "Python Personal Tab SSO"
    },
    "description": {
      "short": "Python Personal Tab SSO",
      "full": "Python Personal Tab SSO"
    },
  "icons": {
    "outline": "outline.png",
    "color": "color.png"
  },
    "accentColor": "#FFFFFF",
    "staticTabs": [
        {
            "entityId": "index",
            "name": "Personal Tab",
            "contentUrl": "https://****-****-***-****-****-****-****-****-****.ngrok.io/api/az-function-index",
              "scopes": [
                "personal"
            ]
        }
    ],
    "permissions": [
        "identity",
        "messageTeamMembers"
    ],
    "validDomains": [
        "*.ngrok.io",
        "<<AZURE-FUNCTION-APP>>.azurewebsites.net"
    ],
    "webApplicationInfo": {
        "id": "<<MICROSOFT-APP-ID>>",
        "resource": "api://****-****-***-****-****-****-****-****-****.ngrok.io/<<MICROSOFT-APP-ID>>"
    }
}

接下来,压缩这三个文件。或者,您可以自定义 color.pngoutline.png 图像以更好地识别您的应用。

现在,打开 Microsoft Teams 并选择 **Apps** 选项卡。点击 **Upload a custom app**

 

然后,选择您刚刚创建的 .zip 文件

 

在 **Built for your org** 部分中选择新的 **Python Personal Tabs** 应用

接下来,打开应用: 

请注意 Teams 如何显示您的自定义 Python 应用的特定内容

准备 Python 代码

我们是从 Microsoft 的Teams Tab SSO Authentication 应用的 C# 示例创建此应用的。让我们看看如何将 C# 应用控制器操作移植到我们新 Python 应用中的 HTTP 触发器函数

C# 应用控制器操作

Python 应用 HTTP 触发器函数

结果

HomeController/Index

az-function-index

HTML 内容

HomeController/GetUserAccessToken

az-function-get-user-access-token

令牌字符串

AuthController/Start

az-function-auth-start

HTML 内容

AuthController/End

az-function-auth-end

HTML 内容

正如我们在文章开头所述,HTTP 触发器函数执行的 Python 和 Flask 代码将服务 HTML 内容。根据缓存设置是否启用,Flask 代码从文件提供静态内容或直接从缓存提供。

缓存

为了实现缓存功能,请创建一个 cacheHelper.py 文件,其中包含以下内容

from flask import render_template
import os
 
class CacheHelper:
    function_directory = ''
    cache = dict()
    cacheDisabled = False
    def __init__(self, function_directory):
        self.cacheDisabled = (os.environ.get("CacheEnabled") == "false")
        self.function_directory = function_directory
 
    def get_file(self, file):
        base_path = os.path.dirname(f"{self.function_directory}function.json")
        file_path = f"{base_path}{file}"
        with open(file_path, 'r') as f:
            return f.read()
 
    def render_cached_page(self, app, template):
        if self.cacheDisabled or template not in self.cache:
            app.root_path = os.path.dirname(app.root_path)
            auth_js = self.get_file("/static/js/auth.js")
            self.cache[template] = render_template(template, context = { "AzureClientId": os.environ.get("ClientId"), "auth_js": auth_js })
        return self.cache[template]

如上所示,如果 CacheEnabled 设置为 true,则代码会尝试绕过文件系统并从缓存提供内容。

安装 Flask 并添加静态文件

Flask 渲染引擎需要一些静态文件才能将它们呈现为 Teams 标签内的网页。

所以,首先,安装 Flask

pip install Flask

然后,从 GitHub 下载并将 auth.js JavaScript 文件复制到 [project root]\static\js\auth.js 路径。我们已从C# 示例应用的原 auth.js 文件中改编了此文件。

接下来,从 GitHub 下载并将 templates 文件夹复制并粘贴到项目根目录。

确保您的本地项目现在包含这些静态文件

PersonalTabSSO
    +-- static
          +-- js
            +-- auth.js
    +-- templates
          |-- base.html
          |-- index.html
          |-- auth_start.html
          +-- auth_end.html

实现基于 Flask 的 Azure Functions

现在,用以下代码替换 \az-function-index\__init__.py 文件的内容

import azure.functions as func 
from flask import Flask
import sys
from cacheHelper import CacheHelper
 
app = Flask(__name__)
 
this = sys.modules[__name__]
this.cacheHelper = None
 
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    if this.cacheHelper is None:
        this.cacheHelper = CacheHelper(context.function_directory)
    return func.WsgiMiddleware(app).handle(req, context)
 
@app.route("/api/az-function-index")
def index():
    return this.cacheHelper.render_cached_page(app, "index.html")

这段代码的作用是什么?如我们所见,main 函数是处理 HTTP 触发器调用的入口点。main 函数然后将控制权委托给 Flask 应用路由,该路由收集 HTML 和 JavaScript 字符串并将它们渲染成一个最终字符串以作为 HttpResponse 返回。 

接下来,用以下代码替换 \az-function-auth-start\__init__.py 文件的内容

import azure.functions as func 
from flask import Flask, render_template_string
import sys
import os
from cacheHelper import CacheHelper
 
app = Flask(__name__)
 
this = sys.modules[__name__]
this.cacheHelper = None
 
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    if this.cacheHelper is None:
        this.cacheHelper = CacheHelper(context.function_directory)
    return func.WsgiMiddleware(app).handle(req, context)
 
@app.route("/api/az-function-auth-start")
def auth_start():
    return this.cacheHelper.render_cached_page(app, "auth_start.html")

然后,用以下代码替换 \az-function-auth-end\__init__.py 文件的内容

import azure.functions as func 
from flask import Flask
import sys
from cacheHelper import CacheHelper
 
app = Flask(__name__)
 
this = sys.modules[__name__]
this.cacheHelper = None
 
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    if this.cacheHelper is None:
        this.cacheHelper = CacheHelper(context.function_directory)
    return func.WsgiMiddleware(app).handle(req, context)
 
@app.route("/api/az-function-auth-end")
def index():
    return this.cacheHelper.render_cached_page(app, "auth_end.html")

实现 az-function-get-user-access-token 函数

现在,我们用以下代码替换 \az-function-get-user-access-token\__init__.py 文件的内容

from flask import Flask
import azure.functions as func 
from flask import Flask
import sys
from ssoAuthHelper import GetAccessTokenOnBehalfUser

app = Flask(__name__)
this = sys.modules[__name__]
this.function_directory = None

def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    this.function_directory = context.function_directory
    return func.WsgiMiddleware(app).handle(req, context)

@app.route("/api/az-function-get-user-access-token")
def GetUserAccessToken():
    return GetAccessTokenOnBehalfUser()

然后,在 ssoAuthHelper.py 创建一个新的 Python 文件

from flask import request
import msal
import os

app = msal.ConfidentialClientApplication(
    client_id=os.environ.get("ClientId"),
    authority="https://login.microsoftonline.com/" + os.environ.get("TenantId"),
    client_credential=os.environ.get("AppSecret"))

class AuthError(Exception):
    def __init__(self, error, status_code):
        self.error = error
        self.status_code = status_code

def GetAccessTokenOnBehalfUser():
    idToken = get_token_auth_header()
    dic = app.acquire_token_on_behalf_of(user_assertion=idToken,
        scopes=["https://graph.microsoft.com/User.Read"])
    if "error" in dic.keys():
        return json.dumps(dic)
    else:
        return dic["access_token"]

def get_token_auth_header():
  """Obtains the Access Token from the Authorization Header
  """
  auth = request.headers.get("Authorization", None)
  if not auth:
      raise AuthError({"code": "authorization_header_missing",
                        "description":
                        "Authorization header is expected"}, 401)

  parts = auth.split()

  if parts[0].lower() != "bearer":
      raise AuthError({"code": "invalid_header",
                        "description":
                        "Authorization header must start with"
                        " Bearer"}, 401)
  elif len(parts) == 1:
      raise AuthError({"code": "invalid_header",
                        "description": "Token not found"}, 401)
  elif len(parts) > 2:
      raise AuthError({"code": "invalid_header",
                        "description":
                        "Authorization header must be"
                        " Bearer token"}, 401)

  token = parts[1]
  return token

将 Python 应用程序部署到 Microsoft Teams

我们的代码已经到位,离测试应用只有几步之遥了。请按照以下说明为将应用集成到您的 Azure 账户并将其运行在 Microsoft Teams 中提供适当的配置。

首先,打开 local.appsettings.json 文件。

 

然后,点击 **Azure** 选项卡并选择 **Download Remote Settings**。此操作将使用您 Azure Functions 应用中存储的配置填充 local.settings.json 文件。

接下来,将以下配置包含在 local.settings.json 文件中:InstanceTenantIdClientIdAppSecretAuthUrlCacheEnabled

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=personaltabssofunctionap;AccountKey=*************************************;EndpointSuffix=core.windows.net",
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "FUNCTIONS_EXTENSION_VERSION": "~4",
    "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "DefaultEndpointsProtocol=https;AccountName=personaltabssofunctionap;AccountKey=********************************************;EndpointSuffix=core.windows.net",
    "WEBSITE_CONTENTSHARE": "personal-tab-sso-function-app45e3c0",
    "APPINSIGHTS_INSTRUMENTATIONKEY": "********-****-****-****-************",
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "********-****-****-****-************",
    "ClientId": "********-****-****-****-************",
    "AppSecret": "***************************************************************",
    "AuthUrl": "/oauth2/v2.0/token",
    "CacheEnabled": "false"
  }
}

然后,将本地设置上传到云端

现在,您的 Python 应用可以访问包含您的 Azure 应用注册的环境变量,以及用于启用或禁用缓存 HTML 内容的 CacheEnabled 配置。

回到 Teams,打开或刷新您的自定义应用,查看 az-function-index HTTP 触发器函数如何在您的个人标签中渲染起始页

如屏幕截图所示,Teams 可能会显示一个 **Authenticate** 按钮。如果是这样,请点击该按钮继续进行登录对话框: 

此对话框调用 az-function-auth-startaz-function-auth-end HTTP 触发器函数来完成 SSO 流所需的身份验证。

接下来确认您的权限同意。现在 auth.js 文件中的 JavaScript 代码将在个人标签应用中渲染您的个人数据: 

现在打开终端,查看 ngrok 如何响应 Teams 标签应用的 HTTP 请求

Teams 标签身份验证说明

您可能想知道我们是如何为我们的 Microsoft Teams 应用提供 SSO 的。

此应用使用基于 Web 的 Azure AD 身份验证方法来获取 Graph 范围,其中包括显示 Azure AD 同意对话框。让我们通过一些代码片段来探讨这种方法。

Teams 标签调用 manifest.json 文件中配置的应用的 /api/az-function-index HTTP 触发器 Azure 函数终结点

"staticTabs": [
    {
        "entityId": "index",
        "name": "Personal Tab",
        "contentUrl": "https://*********/api/az-function-index",
        "scopes": [
            "personal"
        ]
    }

在标签中,auth.js 中的 getClientSideToken 函数调用 microsoftTeams.authentication.getAuthToken 方法

function getClientSideToken() {
  // some code
  microsoftTeams.authentication.getAuthToken({
  // some code
}

getAuthToken 函数指示 Teams 为标签应用程序获取客户端访问令牌。

Teams 请求 /api/az-function-get-user-access-token 的 HTTP 触发器 Azure 函数终结点,该终结点又通过Web 服务器网关接口 (WSGI) 中间件将控制权委托给 GetUserAccessToken 中的 Flask 函数

def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    this.function_directory = context.function_directory

    return func.WsgiMiddleware(app).handle(req, context)

@app.route("/api/az-function-get-user-access-token")
def GetUserAccessToken():
    return GetAccessTokenOnBehalfUser()

GetUserAccessToken 函数调用 ssoAuthHelper,使用 Azure Active Directory (AAD) 的代表流将客户端令牌与服务器端令牌进行交换,以访问 graph.microsoft.com 上的 Microsoft Graph API

app = msal.ConfidentialClientApplication(
    client_id=os.environ.get("ClientId"),
    authority="https://login.microsoftonline.com/" + os.environ.get("TenantId"),
    client_credential=os.environ.get("AppSecret"))

def GetAccessTokenOnBehalfUser():
    idToken = get_token_auth_header()
    dic = app.acquire_token_on_behalf_of(user_assertion=idToken,
        scopes=["https://graph.microsoft.com/User.Read"])
    if "error" in dic.keys():
        return json.dumps(dic)
    else:
        return dic["access_token"]

如果交换失败(例如,当前用户第一次使用您的标签应用程序),则 /api/az-function-index 会显示一个 **Authenticate** 按钮,该按钮使用我们的 AAD 身份验证 API 触发 AAD 同意对话框。此对话框调用 /api/az-function-auth-start/api/az-function-auth-end HTTP 触发器 Azure Functions 来请求用户同意。 

function requestConsent() {
  getToken()
    .then(data => {
    $("#consent").hide();
    $("#divError").hide();
    accessToken = data.accessToken;
    microsoftTeams.getContext((context) => {
      getUserInfo(context.userPrincipalName);
      getPhotoAsync(accessToken);
    });
  });
}

function getToken() {
  return new Promise((resolve, reject) => {
    microsoftTeams.authentication.authenticate({
      url: window.location.origin + "/api/az-function-auth-start",
      //some code here

如果授予有效,AAD 会将标签访问令牌发送到 Teams 应用程序。

然后,Teams 将标签访问令牌作为 getAuthToken() 调用返回的结果对象的一部分发送。 

在标签应用程序中运行的 auth.js 解析服务器端响应。如果响应包含错误,则表示授予无效,代码会提示用户授予应用同意。否则,JavaScript 代码会提取令牌以解析用户信息,包括照片、名字、姓名和电子邮件地址

.then((response) => {
    if (response.ok) {                        
        return response.text();
    } else {
        reject(response.error);
    }
})
.then((responseJson) => {
    if (IsValidJSONString(responseJson)) {
        if (JSON.parse(responseJson).error)
            reject(JSON.parse(responseJson).error);
    } else if (responseJson) {
        accessToken = responseJson;
        console.log("Exchanged token: " + accessToken);
        getUserInfo(context.userPrincipalName);
        getPhotoAsync(accessToken);
    }
});

将 Teams 应用移到云端

您精彩的 Teams 应用已经启动并运行,但您仍然无法与组织中的其他人共享。请按照以下说明进行调整,并将您的应用移到云端。 

首先,在 VS Code 中,打开 requirements.txt 文件并包含 Flask 和 Requests 依赖项

azure-functions
flask==2.0.2
requests==2.25.0

然后,点击 **Azure** 选项卡,在 **Functions** 部分下找到 **Local Project** 文件夹。点击 **Deploy to Function App** 图标开始将您的 Function App 上传到 Azure

然后,转到您的应用注册刀片,打开您的应用,并将 **Redirect URI** 编辑为匹配您的 Function App URI

替换:

https://****-****-***-****-****-****-****-****-****.ngrok.io/api/az-function-auth-end

有了:

https://<<YOUR-FUNCTION-APP>>.azurewebsites.net/api/az-function-auth-end

接下来,打开 **Expose an API** 选项卡并将 **Application ID URI** 编辑如下

替换:

api://****-****-***-****-****-****-****-****-****.ngrok.io/<<YOUR-APP-CLIENT-ID>>

替换为:https://<<YOUR-FUNCTION-APP>>.azurewebsites.net/api/az-function-auth-end

现在,打开 manifest.json 文件。接下来,将版本从“1.0.0”递增到“2.0.0”(例如)。然后,将 staticTabs > contentUrl 值编辑如下

替换:

https://****-****-***-****-****-****-****-****-****.ngrok.io/api/az-function-index

有了:

https://<<YOUR-FUNCTION-APP>>.azurewebsites.net/api/az-function-index

接下来,将 webApplicationInfo > resource 值编辑如下

替换:

api://****-****-***-****-****-****-****-****-****.ngrok.io  

有了:

api://<<YOUR-FUNCTION-APP>>.azurewebsites.net

现在,将 appPackage 目录的内容压缩为 manifest.zip 文件。

在 **Teams** 中,点击应用卡片上的 **...** 按钮。点击 **Update** 以替换为新版本。

现在,选择您刚刚创建的 manifest.zip 文件。然后,打开您的 **Python Personal Tab SSO**。现在您的应用已托管在 Azure 云中,而不是本地项目,并且您可以与组织中的其他人共享。

后续步骤

我们刚刚体验了 Visual Studio Code、Azure Serverless Functions 和 Microsoft Teams 应用栈如何完美支持 Python 开发人员。

作为编程 IDE,Visual Studio Code 为 Python 编码提供了一系列便利设施。Python 扩展为该语言提供了丰富的支持,包括 IntelliSense、调试、代码导航和格式化以及重构。Azure Functions 扩展使您能够直接从 VS Code 快速创建、调试、管理和部署无服务器应用。这些工具与您的 Azure 账户无缝集成,可实现快速开发和直接部署。

在本系列接下来的两篇文章中,我们将探讨如何使用 Python 和 Azure Functions 构建 Channel 和 Group Tabs 以及带有 Adaptive Cards 的 Tabs。

注册免费试用 Azure,了解在 Azure 无服务器函数上创建和运行基于 Python 的 Teams 应用是多么容易,或者继续阅读第二部分

要了解有关如何为个人、您的团队、您的组织或所有 Microsoft Teams 用户构建应用的更多信息,请查看为 Microsoft Teams 开发应用

© . All rights reserved.