Skip to the content.

Teams Bot App 代码解析

上一篇文章我们讲了如何使用 teams toolkit 来快速弄一个 teams bot,可以看到 toolkit 给我们提供了极大的方便性,让开发人员可以更好的把重心放在 coding 上,而不是各种配置上。

那我们这篇文章主要接着上篇,来解析一下 teams bot 的代码,让各种更深入的了解它是怎么运作起来的。

我们先来看一下teams bot 的代码目录下的文件:

TeamsTookkit

可以看到核心的代码文件也就两个 index.tsteamsBot.ts,外加两个 adaptive card的json文件。

可能眼尖的读者已经看到一个没有见过的文件 env.teamsfx.local,这个文件实际上是运行之后生成出来的,里面放的是一些环境变量。里面最关键的是 BOT_ID 和 BOT_PASSWORD,这也是 teams toolkit 给我们带来的方便性。一般情况下,我们需要自己先创建出一个 bot,得到 bot id 和 bot password,但现在 teams toolkit帮我们都自动完成了。

# Following variables are generated by TeamsFx
BOT_ID=2faecc33-2711-40f6-9681-1260b04ebeb4
BOT_PASSWORD=<<passord>>
M365_CLIENT_ID=
M365_CLIENT_SECRET=
M365_TENANT_ID=
M365_AUTHORITY_HOST=
INITIATE_LOGIN_ENDPOINT=
API_ENDPOINT=
M365_APPLICATION_ID_URI=

我们再来看一下整个程序的入口 index.ts 文件,由于整个文件有点过于繁杂,我简化了一下,帮我们更好的理解代码结构。

const adapter = new BotFrameworkAdapter({
  appId: process.env.BOT_ID,
  appPassword: process.env.BOT_PASSWORD,
});

...

const bot = new TeamsBot();

// Create HTTP server.
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, () => {
    ...
});

// Listen for incoming requests.
server.post("/api/messages", async (req, res) => {
  await adapter.processActivity(req, res, async (context) => {
    await bot.run(context);
  });
});

经过简化,我相信对于有 nodejs 开发经验的读者,代码的逻辑已经是一目了然,先创建了一个 bot adapter,这里就用到了刚才的 bot id 和 password。

然后创建了一个 bot,它是 TeamsBot 的一个实例,TeamsBot 是在另一个文件中定义的,我们等会儿讲到那个文件。

之后就创建了一个 http server,监听 3978 端口,这个端口实际上是 ngrok 重定向的端口,也就是说 teams 把请求发送给公网的ngrok网址,ngrok把请求转发给了我们本地的 3978 端口。

最后就是当 http request 是发送给 POST /api/messages 这个 endpoint 的话,就调用 adapter 的 processActivity() 来处理,adapter 的作用就是根据 activity 来产生一个 TurnContext 的对象 context,然后交由 bot 来处理 bot.run(context),bot 的逻辑在 teamsBot.ts 文件中。

那我们现在来看一下简化后的 teamsBot.ts

import rawWelcomeCard from "./adaptiveCards/welcome.json";
import rawLearnCard from "./adaptiveCards/learn.json";

export class TeamsBot extends TeamsActivityHandler {
  constructor() {
    super();

    this.onMessage(async (context, next) => {
      let txt = context.activity.text;
      switch (txt) {
        case "welcome": {
          const card = AdaptiveCards.declareWithoutData(rawWelcomeCard).render();
          await context.sendActivity({ attachments: [CardFactory.adaptiveCard(card)] });
          break;
        }
        case "learn": {
          this.likeCountObj.likeCount = 0;
          const card = AdaptiveCards.declare<DataInterface>(rawLearnCard).render(this.likeCountObj);
          await context.sendActivity({ attachments: [CardFactory.adaptiveCard(card)] });
          break;
        }
      }
      await next();
    });
  }

  ...

我们可以看到 TeamsBot 实现了 TeamsActivityHandler,在构造函数里,定义了当收到一个 message 之后的处理逻辑。

先从 message 里获取到用户发送的文字,当文字是 “welcome” 时,回复 rawWelcomeCard,这实际上是在 ./adaptiveCards/welcome.json 里定义的,当文字是 “learn” 时,回复 rawLearnCard,这实际上是在 ./adaptiveCards/learn.json 里定义的。

那让我们打开 welcome.json 文件看看,实际上 adaptive card 的 json 有时候不太容易知道这个card显示出来到底是怎么样的,好在 adaptive card/teams 团队为我们开发了一个 vscode 插件,如下图所示,先点击 “Preview and Debug Adaptive Cards”,然后点击 “Install” 来安装 “Adaptive Card Studio” 插件。

TeamsTookkit

稍等几秒钟,等插件安装完毕后,就可以预览到 welcome card 了

TeamsTookkit

我们分析了代码后,再来看看 bot 的运行结果,当我们发送 welcome 消息给 bot,bot就回复我们 welcome 的卡片了,和我们在 vscode 里预览看到的卡片一样。当我们发送 learn 消息时,我们就收到了 learn 的卡片。

TeamsTookkit

我们下篇文章会详细分析 learn 卡片,因为 learn 卡片里有一些用户交互,可以让我们更好的理解 teams bot app 的运行机制。

Written on September 25, 2022