上一篇文章我们讲了如何使用 teams toolkit 来快速弄一个 teams bot,可以看到 toolkit 给我们提供了极大的方便性,让开发人员可以更好的把重心放在 coding 上,而不是各种配置上。
那我们这篇文章主要接着上篇,来解析一下 teams bot 的代码,让各种更深入的了解它是怎么运作起来的。
我们先来看一下teams bot 的代码目录下的文件:
可以看到核心的代码文件也就两个 index.ts
和 teamsBot.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=<>
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(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” 插件。
稍等几秒钟,等插件安装完毕后,就可以预览到 welcome card 了
我们分析了代码后,再来看看 bot 的运行结果,当我们发送 welcome 消息给 bot,bot就回复我们 welcome 的卡片了,和我们在 vscode 里预览看到的卡片一样。当我们发送 learn 消息时,我们就收到了 learn 的卡片。
我们下篇文章会详细分析 learn 卡片,因为 learn 卡片里有一些用户交互,可以让我们更好的理解 teams bot app 的运行机制。