基于 Azure Bot Service 的 Microsoft Teams Bot 开发上手指南

这篇文章本不必要,可惜无论是 Teams Developer platform 还是 Azure Bot Service 的官方文档都不够清晰易懂,容易走弯路。因此无奈在这里做简单小结,帮助读者更快上手。

Bot 的主要目的是接收并理解用户输入的文字信息或指令,并采取行动给予回应。在一些场景下,Bot 能够节省时间,提高效率,或提供更好的使用体验。在 Bot 不擅长的场景,开发并应用 Bot 则可能得不偿失。如非出于学习与研究目的,最好首先全面考察开发维护 Bot 的成本及相应的收益。

Azure Bot Service

Azure Bot Service 是 Microsoft Azure 提供的通用 Bot 服务。它不与特定消息平台频道(Channel)绑定,提供了包括语义理解、图像识别、知识库文档和通用搜索等功能。借助 Azure Bot Service 开发的 Bot 可以介入 Teams、Telegram、Line、WeChat、Facebook 等各种频道。

Microsoft Teams 是一款可扩展的通信平台,支持安装第三方应用。一款 Teams App 可以提供 tabs、bots、connector 和 messaging extension 这四大类功能。这里仅涉及 bot 部分。

两者的文档都提供了大量说明和范例,然而文章组织混乱,不少细节描述不够清晰,快速上手指南也在本地测试后戛然而止,而未作后续说明,对新手反而可能造成误导。这里简单整理。

用 Azure Bot Service 创建一个 Bot,往往意味着创建包含包括 App Service Plan、App Service、Bot Channels Registration/ App Bot 等在内的一系列资源。这些资源会被 Azure Bot Service 一同创建。如果选择 Bot 模板,则 App Service 创建之初就能提供模板描述的 Bot 功能,可以在 Bot Channels Registration/ App Bot 的 Test in Web Chat 菜单中实际体验。例如,选择 Echo Bot 模板,则数分钟后就能在 Test in Web Chat 中与该 Bot 对话并得到复述回答。

Azure Bot Framework ComposorAzure Bot Framework SDK 用于为 Azure Bot Service 开发复杂的 Bot 逻辑并发布。可惜的是 Azure Bot Framework Composor 无论是文档还是工具本身的易用性还都有待改善。创建完成的 Bot 将被发布至相应的 App Service 中。开发者也可以手动发布。如果是基于 NodeJs 的 Bot,直接通过 App Service 的 Editor 编辑 .js 文件,同样可以更改 Bot 的逻辑。基于 .NET 的 Bot 则需要在更改代码后进行编译。

Bot Channels Registration/ App Bot 之所以能够连接到 App Service 并与 Bot 交互,是因为它的配置菜单中提供了 App Service 的 messages API。要运行 Teams 连接至该 Bot,只要在其 Channels 菜单中添加 Teams 频道即可。

此外,除了上述由 Azure Bot Service 创建的 Bot Channels Registration/ App Bot,Azure Bot Framework 也提供了功能相似的替代品,能够创建 Bot 定义、连接频道,并访问由 messages API URL 指定的 Bot 服务。

以上是 Azure Bot Service 的部分。

Microsoft Teams Bot

另一方面,Teams App Bot 这边不提供实际的 Bot 逻辑。从 Teams 的角度来看,只有 App 这个概念,App 可以提供 Bot 功能。因此在 Teams 这边需要做的只有定义 App,更确切地说,准备一个包含了 manifest.json 、 color.png、outline.png 这三个文件的 Zip 文档。该文档可以被用于提交至 Teams 公开 App Store组织内部的 App Center,或者用户自己在自己的 Teams 客户端中上传安装(需要得到 IT 管理员许可才能使用该功能)。上述两个不同用途的图标文件按下不表,在 manifest 文档中会定义该 Teams App 的 Id、名称、描述、Bot 的 Id、范围和支持的命令等信息。

名为 App Studio 的 Teams App 和 Visual Studio Code 扩展 Microsoft Teams Toolkit能够帮助开发者快速创建和准备 Teams App 所需的信息(如 manifest 等)并打包或发布。

Teams Toolkit 能快速创建 Bot 的定义并生成基本的 Bot 模板。被创建的 Bot 模板可以在之后发布到服务器作为 Bot 的实际逻辑。同时,Teams Toolkit 还能直接访问 Azure Bot Framework,读取已创建的 Bot 定义,并更新 messages API 的 URL。

即使没有 App Studio,根据文档手动创建 App 的 manifest 也并不麻烦。但 App Studio 提供了更方便的管理功能,推荐使用。

即使没有 Teams Toolkit,也可以直接访问 Azure Bot Framework 站点并登录,在网站上创建 Bot 定义,并更新各类信息,或添加频道支持。Toolkit 自动生成的 Bot 逻辑也可以通过 Azure Bot Service 自动创建。

小结

综上,为了让 Teams 能够安装一个提供了 Bot 功能的 App,需要完成以下工作:

  • 准备 manifest.json 、 color.png、outline.png 并打包成 Zip 文档(可借助 App Studio )
  • 创建 Bot 定义(经有 Azure Bot Service 或者 Azure Bot Framework),并将 Bot Id 填入 App manifest (可借助 Teams Toolkit)。Bot 定义中的 messaging api 需要指向一个实际可用的服务,如本文介绍的 Azure Bot Service 提供 App Service 的 messages API 接口

需要注意,Teams Bot 和 Azure Bot Service 的 Bot 功能有若干差异,在阅读文档时需要注意辨别。Bot Serivce 中的功能有时不一定能在 Teams 中正常工作。

以上便是基于 Azure Bot Service 的 Microsoft Teams Bot 开发上手指南。想必还没有实际开始,就能感受到这套框架的设计有多么地复杂与含糊。愿本文可以帮助对 Bot 开发感兴趣地读者节省一些时间,更愉快地开发出自己的第一个 Bot。

解决 HTML Canvas 元素在高像素密度/高分辨率屏幕上显示模糊的问题

最近在使用 HTML Canvas 元素时发现通过 fillText() 绘制的文字明显发虚,像是以低分辨率渲染后放大所致,再仔细一检查,发现事实上包括 fillRect() 在内的其他图案也都存在相同的问题。

初步推测和显示设备使用了较高的 DPI 有关,随进行了一番搜索,在 Stackoverflow 、Medium 等站点都有一些提问和回答。然而意外的是,自己能找到的一些解答,至少在自己这边并不能解决问题。

经过摸索,找到了能够解决自己问题的办法,分享于此,以便后人。

以下为 Angular component 代码片段,语言为 TypeScript。

@ViewChild('canvas', { static: true }) canvas: ElementRef; // Canvas 元素
@ViewChild('canvasDiv', { static: true }) canvasDiv: ElementRef; // Canvas 元素的父元素,一个 div
private ctx: CanvasRenderingContext2D; // Canvas context

private dpiRatio: number; // DPI 缩放比例
private canvasDivWidth: number;  // 父元素宽度

……

this.dpiRatio = window.devicePixelRatio;

this.canvasDivWidth = this.canvasDiv.nativeElement.offsetWidth;

// 分别设置 canvas 的宽度和样式宽度,canvas 的显示尺寸将会减半
this.ctx = this.canvas.nativeElement.getContext('2d');
this.ctx.canvas.width = this.canvasWidth * this.dpiRatio;
this.ctx.canvas.style.width = this.canvasWidth + 'px';

// 在绘制时考虑缩放因素
this.ctx.font = 12 * this.dpiRatio + 'px Roboto';
this.ctx.fillText('HIDPI文字', x  * this.dpiRatio, y * this.dpiRatio);

一个悬而未决的问题是,canvas context 本身的 transfer 函数似乎不能做到整体的放大,因此这里作为临时方案暂时在绘制每一个图形时都乘以了缩放倍率。这里应该有更好的办法,不过限于时间,暂且搁置。

Angular 9+ 升级小记 —— 应付 MSAL-Angular 与 ngx-restangular 的兼容性问题

之前几次升级 Angular 版本的经历总体还算顺利,因此原本对 Angular 8 升级到 9 的过程也比较乐观。虽然 Angular 9 开始将默认启动新的 Angular Ivy,但考虑到已经正式发布数月,甚至 Android 10 都已问世,自己的项目又没有用特别冷门的依赖,依然没有担心会遇到问题。可惜墨菲定律无处不在,最后还是花了比想象中更多的时间完成升级。

为此,本文简单介绍自己在从 Angular 8 升级到 9 最终到 10 时遇到的问题及相应的解决方法,仅供参考。

首先,Angular 官方文档提供了详细的升级说明,建议在升级项目前首先通览相关文档,了解可能存在的问题。可以从下面的链接找到升级到 Angular 10 的最新信息。

https://angular.io/guide/updating-to-version-10

下面是自己在升级时实际操作的几个主要步骤:

  • 更新 Angular 8 相关依赖至最新版
    • ng update @angular/core@8 @angular/cli@8
    • ng build –prod –aot
    • 确认变更
  • 更新 @azure/msal-angular 至最新版 1.0.0
    • 安装 1.0.0 版并为新版的接口变化修改代码(1)
    • ng build –prod –aot
    • 确认变更
  • 升级至 Angular 9,检查并乎略一些依赖版本问题
    • ng update @angular/core@9 @angular/cli@9 –force(2)
    • ng build –prod –aot
    • 确认变更
    • 升级 Angular Material
      • ng update @angular/material@9
      • 检查 Angular Material 引用问题(3)
    • 增加 ngcc 作为 postinstall 脚本(4)
    • ng build –prod –aot
    • 确认变更
    • ng add @angular/localize (5)
    • ng serve –ssl 启动并检查应用基本功能
  • 升级至 Angular 10,检查并乎略一些依赖版本问题
    • ng update @angular/core @angular/cli –force
    • 临时手段解决 ngx-restangular 编译问题(6)
    • ng build –prod –aot
    • 确认变更
    • ng update @angular/material
    • ng build –prod –aot
    • 确认变更
    • ng serve –ssl 启动并检查应用基本功能
  • 大功告成

以下具体说明上述步骤中的一些关键操作。

(1)MSAL Angular 代码更改

在升级 Angular 9 之前,自己的项目使用 MSAL Angular 0.1.4 实现对 Microsoft 账户或 ADD 的验证。当时该库的文档和范例代码质量就差强人意,缺少说明和链接失效比比皆是。更重要的是,该库并不兼容 Angular Ivy,这也是没有更早升级 Angular 9 的原因之一。等着 MSAL Angular 花了几个月的时间终于正式发布 1.0.0 版本才开始升级,依然遇到几个问题:

  • 方法接口变化,getUser() 改为 getAccount(),displayableId 成员取消
  • 服务初始化方式变化,结构虽然较过去清晰,但文档和示例代码不足,甚至还有语法拼写错误
  • 请求验证成功后返回的 payload 格式发生变化,token 变量改为 rawIdToken
  • loginRedirect() 似乎不再正常工作,仍需进一步排查原因

无论如何,能够兼容 Angular 9 已经不易,总比 ngx-restangular 那样根本无法成功编译要好些。MSAL Angular 已经两个月没有新版本发布,不知今后的更新是否能解决一些问题。

(2)升级至 Angular 9

这步本身没有什么问题,不过需要检查一些报警的依赖版本冲突。自己的项目中主要是 tslib 已经到了 2.0 而 Angular 9 要求 1.10.0,以及 Angular http 停在了大版本7。确认没有问题后就以 –force 参数强制升级。

(3)检查 Angular Material 引用问题

新版本 Angular Material 要求每个引用都明确指定具体组件,如:

 import { MatSpinner } from '@angular/material/progress-spinner';

而不允许在一句引用中同时引入多个组件,如:

import { MatSpinner, MatSnackBar } from '@angular/material';

正常情况下 ng update 会自动更正代码,不过自己在操作时似乎遇到一些问题,没有顺利完成,只好收到批量做了一些处理。

(4)增加 ngcc 作为 postinstall 脚本

在 package.json 中增加 “postinstall”: “ngcc” 有助于避免可能的库兼容问题。参见 ngcc 的和 ModuleWithProviders 迁移的相关说明。

(5)增加 @angular/localize

如果项目中使用了 Angular 自带的 i18n 功能,可能需要执行 ng add @angular/localize 添加必要的依赖。不过这并非必要步骤,可以在遇到提示时才采取行动。

(6)解决 ngx-restangular 5.0.0 编译问题

很可惜,ngcc 不是银弹。至少,它无法解决 ngx-restangular 的兼容问题。

ngx-restangular 是自己项目中使用的主要第三方库之一,帮助使用 REST API 的使用。这个项目本身算不上太活跃,但基本功能还算充分。可惜的是,半年没有更新的它,果然无法在 Ivy 下正确编译,报错如下:

ERROR in node_modules/ngx-restangular/lib/ngx-restangular.module.d.ts:8:97 - error TS2314: Generic type 'ModuleWithProviders<T>' requires 1 type argument(s).

static forRoot(providers?: any[], configFunction?: (provider: any, ...arg: any[]) => void): ModuleWithProviders;

搜索尝试一些方法无果后,不得已手动修改库文件,增加类型 <RestangularModule> 在 node_modules/ngx-restangular/lib/ngx-restangular.module.d.ts 第八行末尾:

static forRoot(providers?: any[], configFunction?: (provider: any, ...arg: any[]) => void): ModuleWithProviders<RestangularModule>;

在 CI 管道中,则暂时每次都在 ng build 之前以 sed 命令完成代码更改:

sed -i 's/ModuleWithProviders;/ModuleWithProviders<RestangularModule>;/g' node_modules/ngx-restangular/lib/ngx-restangular.module.d.ts

于是,暂时绕过了编译错误。ngx-restangular 的功能本身倒似乎没有问题,可以正常工作。

经过功能确认和测试后,一个 Angular 8 应用终于顺利升级为了 Angular 10 应用。尽管并非一帆风顺,但 Ivy 确实明显提升了编译性能并减小了体积,总管没有白费功夫。如有需要,还可以参考 AngularGo GitHub 地址),了解升级过程中的配置与代码更改。

Angular on Azure Web App Service (IIS)

部署于 Azure App Service 的 Web App 事实上运行于 IIS (Internet Information Services),因此也可以通过 web.config 文件来配置它的行为。

由于 Angular 是一种 SPA Web 框架,需要对 IIS 做一些额外的 URL Rewrite 配置,才能在浏览器刷新页面时依然成功载入内容,参见以下代码。

规则 Index Rule 的作用是把所有匹配的 HTTP 请求全都重定向至 “/” 路径,之后 Angular 自己的路由(Routes)将会继续工作,完成内容的载入和跳转。可以参考此处了解如何实现 lazy loading 。

staticContent 节点则定义了允许的 mimeType,否则直接访问 contentType 为 application/json 等类型的请求将返回 404 Not Found。

AngularGo —— 一个开源 Angular SPA 模板

在接触 Angular 后,这些年工作业余也用 Angular 做了若干实际项目。不过,由于 Angular 在国内的流行度不高,各种原创内容和参考资料也相对较少。虽然网络上也能找到各种各样的技术文章,官方的文档也很全面,但总的来讲,信息还是有些分散。

很惭愧,几年里没有做什么特别有技术含量的工作。 只是提炼出一个很基础的模板,帮助开发者快速创建一个 SPA(Single Page Application)站点,或是供对 Android 感兴趣的读者了解 Android 的语法和基本的框架机制。该模板先分享于此 —— AngularGo on GitHub

该模板基于目前最新的 Angular 8,且会持续跟进更新。在 Angular CLI 自动创建的范例项目的基础上,目前版本的 AngularGo 还包含以下内容:

  • Angular 8 以及相关依赖的最新版本配置
  • 一种可能的源文件结构示例
  • 一种与 Cordova 共享代码库的可能方式
  • 基础 Angular 组件及依赖注入的使用范例
  • 基础 Angular Material 控件的使用范例
  • 支持桌面和移动设备的抽屉式侧滑菜单
  • 支持 lazy loading 的 app-routing 全局路由
  • 支持 authentication guard 的模块路由
  • 支持 bearer token 验证的 Restangular 初始化及 service 用例
  • 基于 HttpClient 的用户注册与登录 API 调用
  • 基于 scss 的 Angular Material 样式(尚未采用 BEM 命名规则)
  • Azure Application Insights 集成
  • 适用于 Windows server/ Azure App Service 的 web.config 配置

AngularGo 还很初步,很多细节因水平有限和时间限制写得也比较粗糙,想必会有其他优秀的开源模板提供了更好的实现。从某种意义上来讲,该模板一方面是对自己经验得一个整理,同时期望能起到一个抛砖引玉的作用。希望对读者有帮助,也欢迎批评指教。

两分钟了解 Node.js

本文粗略介绍 Node.js 特点与用途,以及意外地很多 Windows 用户不那么容易找到的使用 npm 的方法。

  • Node.js 是高速的服务器端单线程事件驱动异步非阻塞式 JavaScript 平台
  • 占用内存少,可无缝处理客户端与服务器端数据,适用于 SPA 或 WebSocket 应用
  • 异步处理使得连接大量客户端时性能依然较好,无需等待磁盘I/O等低速操作
  • 通过npm(node package manager)管理模块依赖

访问官方站点获取详细信息 https://nodejs.org/ 。

如无特殊需求,通过 .msi 安装包安装 Node.js 后在开始菜单启动 Node.js command prompt 即可执行命令,如,输入 npm install g @angular/cli 便能安装 Angular CLI。

 

Angular 快速入门

之前做过一些 AngularJS 的小型项目,用的工具和流程却大多是过时的一套,大半精力也仍花费在后台业务逻辑。时至今日虽然晚了一些,打算跟上时代,尝试一下 Angular 的开发。

有几个关注点:

  • 以 Angular 4 入手,系统学习 Angular 框架的概念与机制
  • 完全的前后端分离
  • 使用 Angular CLI 创建模板
  • 借助 VSTS 管理项目代码与发布
  • Angular 部分完全使用 VS Code 开发

Angular 4 并不是最新版本,大版本升级的周期是每6个月,但从 Angular 2 起每个大版本都能基本兼容,Angular 4 的文档也相对丰富,故选择从这里开始。完全前后端分离可以更高效分工,带来一系列好处,如提高团队合作效率。而 Angular CLI 几乎已经成为默认的选项,这次也打算应用。至于 VSTS,最近的项目经历感觉没有最大程度发挥其价值,所以希望在也与项目中进一步探索其可能性。VS Code 虽已成为自己的常用工具,但能否完全担当起开发之职,也会是这次的一个课题。

起动步骤极为简单。

在安装 Node.js 后,通过命令行安装 Angular CLI:

CD 定位至希望的文件夹后,新建 Angular 项目:

运行新建的项目,并以 open 选项打开浏览器页面,默认端口为4200:

此时,通过 VS Code 对代码做任何修改并保存后都会立即体现在程序上。

至此,尝试 Angular 的第一步已经踏出。今后将不定期更新心得感想。

JavaScript 避免 Ajax Get 方法因缓存而返回错误数据结果的问题

代码与范例:

方法4 – 页面 meta tag

说明:Ajax 调用存在缓存,再次以相同参数调用时可能会直接调取先前结果,造成结果错误。可以用以上四种方式回避该问题。

在 Windows 下安装与配置 Karma

1. 安装 Karma 与相关插件

karma 与 karma-chrome-launcher 将被安装至当前工作目录下的 node_modules 并以 devDependencies 形式保存于 package.json。这样一来,其他开发者只需通过 npm install 来获取这些已经安装的依赖。之后,通过 -g 指定在全局安装 CLI 以实现命令行操作。

2. 创建配置文件
在项目的测试用例文件夹下执行以下命令

3. 启动 Karma

或在配置文件路径下执行

4. 终止 Karma

AngularJS 中为 input 添加字母大小写不敏感正则表达式匹配验证

代码与范例:

说明:虽然包括 JavaScript 在内的正则表达式实现支持大小写不敏感匹配(i),但 HTML5 中,input 元素的 pattern 属性并不支持该标记位。为此,要实现该功能,必须借助额外的逻辑。如需显示错误信息,可为错误信息添加 ng-show=‘!isEmailValid’ 属性。