FinChat服务端私有云部署



  • FinChat 私有云部署介绍

    FinChat私有云部署是一种为方便企业客户单独使用FinChat通信协作平台而构建的一种云服务模式,利用正确的管理程序、软硬件以及合适的广域网和宽带技术实现私有云,因而提供对数据、安全性和服务质量的最有效控制。FinChat私有云部署基于FinChat通信协作平台构建出一套独立的服务器集群,所有的硬件资源都是专属独有,帮助客户满足对数据、安全性和服务质量等的更高需求

    FinChat私有云部署优势:

    • 独立集群资源--服务器集群资源专享独有,每个客户都有一个独立的客服集群。
    • 独立部署升级--根据业务定制升级,经过商用验证,更成熟版本。
    • 开发平台--支持系统二次开发,并有专业工程师指导完成。
    • 弹性扩容--水平架构,弹性扩容,服务配置可随数据变化快速变更。
    • 更高SLA保障--专属运维保障,SLA最低保证99.9%。

    FinChat私有云部署申请流程

    企业用户可通过提交相关资料,便可申请私有云部署体验。申请链接如下:

    https://www.finogeeks.com/#/doc/private-cloud

    前提条件

    在部署服务之前,需要每台机器都安装Docker以及Docker Compose。

    推荐服务器最低配置如下:

    项目 参数
    服务器参数 CPU:4核、内存:8G、硬盘:500G
    服务器数量 3台
    操作系统 Ubuntu-16.04 LTS ,防火墙需关掉
    容器环境 Docker v1.12.3、docker-compose v1.9.0、Rancher v1.6.5

    需要开通以下链接的访问权限:

    网络权限 说明
    https://git.finogeeks.club 代码托管
    https://docker.finogeeks.club 镜像服务
    https://hub.docker.com 镜像服务
    https://index.docker.io 镜像服务
    https://registry-1.docker.io 镜像服务
    https://github.com 代码托管
    https://gateway.sandbox.push.apple.com 苹果推送

    需要申请的域名:

    序号 域名说明
    1 用于IM服务的域名
    2 用于容器集群管理平台的域名

    安装Docker

    安装docker1.12的脚本

    sudo curl https://releases.rancher.com/install-docker/1.12.sh | sh
    

    将用户名添加到docker组里,your-username需替换成实际的用户名:

    sudo usermod -aG docker your-username
    

    然后推出重新登陆服务器生效,使用以下命令验证docker版本是否正确:

    docker version
    

    安装Docker Compose

    下载docker-compose版本到本地并安装

    sudo curl -LJO https://github.com/docker/compose/releases/download/1.9.0/docker-compose-Linux-x86_64
    

    修改权限

    sudo mv docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose
    

    安装完成后在命令行中输入docker-compose version 查看当前安装的版本是否正确:

    docker-compose version
    

    安装Rancher

    #指定 v1.6.5 的 Rancher 版本,8090端口可修改为其他未被占用的端口
    sudo docker run -d --restart=unless-stopped -p 8090:8080 rancher/server:v1.6.5
    

    UI 以及 API 会使用 8090 端口对外提供服务。下载 Docker 镜像完成后,需要 1 到 2 分钟时间 Rancher 才能完全启动并提供供服务。Rancher server 服务安装成功,在浏览器中访问 http://host_ip:8090 , 即可看到 Rancher 的 web 管理界面。
    当 UI 已经启动并运行,你可以在应用商店选择一个容器编排引擎。默认情况下,如果没有选择容器编排引擎,当前环境会使用 Cattle 引擎。

    设置访问控制

    访问控制是用来控制哪些用户可以访问你的 Rancher 服务。在默认情况下,Rancher 没有启用访问控制。这意味着知道你的 Rancher 服务 IP 的人都可以访问你的 Rancher 服 务和 API。你的 Rancher 服务是对外开放的!我们强烈建议你在启动 Rancher 后立即配 置访问控制,这样你可以按照需要分享你的 Rancher 服务。用户在访问你的 Rancher 服 务之前,需要进行身份认证。同时,只有拥有合法的 API 密钥才能使用 Rancher API。
    系统管理 选项卡中, 点击访问控制。在页面中会要求你设置管理员用户,填入账户密码信息后点击启用本地验证即可,在你配置了 Rancher 的访问控制后,访问控制将被启用。访问控制使你能够管理不同的环境并把它们分享给不同的个人或团队。

    设置本地身份认证

    启用本地身份认证后,管理员可以通过访问系统管理> 账号设置选项卡来创建其他 管理员/用户。 点击添加账号并填写你要添加的帐户的详细信息。 你可以选择其帐户 类型为管理员或用户。 管理员可以查看系统管理页面,普通用户无法看到该页面。一旦帐户被创建后,该账户可以被添加到任何环境中。如果 Rancher 启用访问控制功能,则只要管理员才能更改主机注册地址。默认情况 下,第一个管理员是配置启用访问控制的用户。如果仍未配置访问控制,则任何用户都可 以更新主机注册地址。

    添加主机到 Rancher

    打开浏览器,输入 http://{ranche-server 的 ip 地址}:8090/ 进入控制页面。在 基础架构 选项卡中, 点击主机,如下图所示:

    在第一次添加主机时,Rancher Server 会要求配置主机注册地址。这个地址可以是域名或者 IP 地址(如果 80 端口不可访问,还需要加上可访问的端口号,默认是启动Rancher的docker镜像时暴露的端口号),能够访问 Rancher 接口即可。任何时候可以改变主机注册地址,相关操作可以 查看系统管理下的系统设置。如果不需要更改,直接点击保存即可跳到添加主机页面。

    按照提示,一步一步进行操作。一般情况下,前面的 3 步操作可以不用做任何操作,只要确保机器上安装了 docker, docker-compose,以及端口的开放问题。为了防止 docker
    网桥的 IP 被误认为是主机的 IP(错误的 IP 通常是 172.17.42.1 或以 172.17.x.x 开
    头的 IP),在第 4 步中填写主机的内网 IP,接着再在需要添加的主机终端命令行中执行第 5 步
    的命令。如果命令成功执行,则可以在 Hosts 页面上看到刚刚添加的主机,具体效果如下

    通过类似的步骤添加多台主机到 Rancher 中。主机添加完成后,请确保 Rancher 的基础设施服务都是正常的,如果有某个服务出现异常或者状态为 Initializing,则需要排查一下问题,确保每个服务都处于 Active 状态。


    由于docker服务在Rancher上根据主机标签进行调度相关服务,所以可以预先在主机上添加所有标签,也可以部署相应服务的时候再添加相应的标签

    序号 参数 说明
    1 postgres.master=staff 添加标签到staff端主机
    2 postgres.slave=staff 添加标签到retail端主机
    3 postgres.master=retail 添加标签到retail端主机
    4 postgres.slave=retail 添加标签到staff端主机
    5 mongo.cluster=finochat 添加标签到所有mongo主机,用于调度MongoDB服务
    6 hs.staff.env=finochat 添加标签到staff主机
    7 hs.retail.env=finochat 添加标签到retail主机
    8 host.wan=true 添加标签到任意主机
    9 kong.env=finochat 添加标签到任意主机
    10 finochat.minio=pcloud 添加标签到任意两台主机,用于调度minio server 服务
    11 netdisk=finochat 添加标签到任意两台主机,用于调度netdisk服务
    12 knowledge.ref-server.env=finochat 添加标签到任意主机
    13 knowledge.search-server.env=finochat 添加标签到任意主机
    14 knowledge.mongodb.env=finochat 添加标签到任意主机
    15 knowledge.es.env=finochat 添加标签到任意主机

    配置镜像库

    在 Rancher 的管理页面中添加完主机后,需要配置一下镜像库的仓库地址,拉取凡泰极客的私有镜像仓库。点击系统管理菜单中的系统设置菜单按钮,进入设置页面,在 应用商店选项中,配置凡泰极客的镜像库地址:

    如果需要配置多个,则继续点击 添加应用商店 进行添加即可。名字可以按需求自定义,我们这里会给每一个机构分配一个专属 的 url,url 中涉及的账号可以联系相关接口人员,在本例中需要添加两个应用商店,分别是 FinChat和Infrastructure,具体信息如下:

    序号 链接 说明
    1 https://username:password@git.finogeeks.club/catalog/finochat Finchat服务应用商店
    2 https://username:password@git.finogeeks.club/catalog/infrastructure Infrastructure服务应用商店

    注:凡泰极客会给每一个机构分配一个专属的账号及密码,请根据实际替换地址中的username和passwd。

    添加完应用商店后,在 应用商店菜单中刷新查看刚刚添加的应用商店 是否成功,如果没有看到 应用商店信息,则手动刷新下管理左侧旁的刷新按钮进行刷新。

    配置私有镜像仓库

    基础架构菜单中的镜像库 的页面里选择CUSTOM,然后添加凡泰极客的镜像库地址: docker.finogeeks.club ,并填入凡泰极客提供的用户名和密码后,点击保存

    数据库服务

    FinChat整套后台服务需要用到的数据库服务包括Postgres和MongoDB两个数据库服务。

    1.Postgres数据库服务部署

    Postgres提供主从复制,在Rancher的管理界面中点击名称为Infrastructure应用商店在Rancher上部署Postgres集群。以在Rancher上部署Postgres集群为例,说明如何在Rancher上通过我们提供的应用商店部署一套Postgres集群,凡泰提供的Postgres的主从之间的复制采用的是异步复制的方式。
    在部署Postgres集群时,需要先准备2台机器,2台的硬盘要尽可能的大些(以存储365天文本数据为例,需要配备硬盘容量>=500G)。
    基础架构菜单中的主机 的页面里,在主机上展开菜单,然后点击编辑,如下图所示:

    编辑页面中点击添加标签,如下图所示:

    然后分别在2台主机中设置标签

    序号 参数 说明
    1 postgres.master=staff 添加标签到staff端主机。
    2 postgres.slave=staff 添加标签到retail端主机。
    3 postgres.master=retail 添加标签到retail端主机。
    4 postgres.slave=retail 添加标签到staff端主机。

    如果用户同时需要staff端和retail端,则需要添加以上4个标签2台主机。

    在Rancher的管理界面中点击名称为Infrastructure应用商店中选择postgres-cluster这个Stack, 点击View Details进入到这个Stack的详细信息页面,在Template Version选择9.6版本,选择后会弹出postgres cluster服务的具体配置信息如下:

    需要修改的参数如下:

    序号 参数
    1 名称 pg-staff
    2 application staff

    其他保持默认即可,点击启动进行部署。
    此外,还需要再拉取一套retail端的postgres cluster服务,

    需要修改的参数如下:

    序号 参数
    1 名称 pg-retail
    2 application retail

    其他的变量信息保持默认值即可,点击启动进行部署。

    2.MongoDB数据库服务部署

    MongoDB的部署采用Replica Sets模式(副本集),在Rancher部署MongoDB集群时,需要先准备三台主机(备注:推荐三台主机只部署MongoDB服务以提高性能),在每台机器上设置同一个标签:

    序号 参数 说明
    1 mongo.cluster=finochat 添加标签到mongo主机,用于调度MongoDB服务。

    在Rancher的管理界面中点击名称为Infrastructure应用商店中选择MongoDB Cluster这个Stack, 点击View Details进入到这个Stack的详细信息页面,在Template Version选择3.6版本,选择后会弹出mongo cluster服务的具体配置信息:

    上述参数中,mongo scale为要部署的mongo 实例个数,这里以部署三台机器为例,mongo scale值为3。其他的变量信息保持默认值即可,点击启动进行部署。

    Finochat服务

    Finochat服务主要是提供聊天服务,涉及到员工端和客户端两套homeserver,两个server上的用户通过federation进行数据的交互。在部署finochat之前,需要先准备好两个server的域名,以及苹果推送服务的APNs证书文件。(推荐准备5台服务器,两台用来单独部署两套homeserver。另外三台用来部署gateway的高可用模式和finochat其他的服务,这三台机器中,需要有一台能访问外网的权限)。
    在部署finochat整套服务前,需要先设置以下的标签信息:

    序号 参数 说明
    1 hs.staff.env=finochat 添加标签到staff主机
    2 hs.retail.env=finochat 添加标签到retail主机
    3 host.wan=true 添加标签到任意主机
    4 kong.env=finochat 添加标签到任意主机

    在Rancher的管理界面中点击名称为FinChat应用商店中选择finochat这个Stack, 点击View Details进入到这个Stack的详细信息页面,在Template Version选择2.6.2版本,如果当前模板版本有更新的版本,则选择最新的版本,选择后会弹出finochat服务的具体配置信息:

    Finochat服务涉及的配置参数比较多,体验部署只需修改以下参数,其他保持默认即可,

    序号 参数 说明
    1 api gateway env 用于初始化gateway的模板,默认值机构名称,体验部署可使用"public.cloud"
    2 staff domain 员工端homeserver的域名,需要机构自己申请,体验部署可以随便填入符合域名格式的伪域名,建议使用类似emp.pcloud-test.com,区分emp和cust端
    3 retail domain 客户端homeserver的域名,需要机构自己申请,体验部署可以随便填入符合域名格式的伪域名,建议使用类似cust.pcloud-test.com,区分emp和cust端
    4 staff ip 设置标签为hs.staff.env=finochat机器的主机IP
    5 retail ip 设置标签为hs.retail.env=finochat机器的主机IP
    6 staff_outer_url 员工端homeserver的对外访问地址,实际生产环境中配置staff homeserver的外部域名地址,体验部署配置为getway的入口地址,格式为http://xxx.xxx.xxx.xxx:8000/emp(xxx.xxx.xxx.xxx需替换为kong的宿主机地址,调度标签为kong.env=finochat)
    7 retail_outer_url 客户端homeserver的对外访问地址,实际生产环境中配置retail homeserver的外部域名地址,体验部署配置为getway的入口地址,格式为http://xxx.xxx.xxx.xxx:8000/cust(xxx.xxx.xxx.xxx需替换为kong的宿主机地址,调度标签为kong.env=finochat)
    8 retail ios appid: 员工端 ios appid,体验部署可以使用staff ios appid
    9 retail apns pem url 客户端ios推送证书地址,体验部署可以使用staff apns pem url

    其他具体各个参数的解释如下:

    序号 参数 说明
    1 enable retail homeserver 是否启用retail homeserver
    2 allow guest access 是否允许匿名访问homeserver
    3 add host 是否在容器启动的时候将IP地址和域名写入/etc/hosts文件
    4 application app类型,默认值finochat,用于设置机器的label
    5 api gateway env 用于初始化gateway的模板,默认值机构名称
    6 staff domain 员工端homeserver的域名,需要机构自己申请
    7 retail domain 客户端homeserver的域名,需要机构自己申请
    8 staff app type 员工端类型,使用STAFF来表示员工端
    9 retail app type 客户端类型,使用RETAIL来表示客户端
    10 staff client staff homeserver提供的客户端类型
    11 retail client retail homeserver提供的客户端类型
    12 staff ip 设置标签为hs.staff.env=finochat机器的主机IP
    13 retail ip 设置标签为hs.retail.env=finochat机器的主机IP
    14 staff db host 员工端的数据库地址,这里使用Rancher的service.stackname的方式访问,使用默认值即可,如果是物理机部署数据库,使用实际的postgres访问地址
    15 retail db host 客户端的数据库地址,这里使用Rancher的service.stackname的方式访问,使用默认值即可,如果是物理机部署数据库,使用实际的postgres访问地址
    16 postgres user postgres数据库的用户名
    17 postgres password postgres数据库的密码
    18 postgres db name postgres数据库名称
    19 mongo cluster domain mongo集群的访问路径,这里使用rancher的servicename.stackname方式访问,如果是物理机部署Mongo集群,则使用实际地址
    20 kong_admin_url 配置gateway的dashboard连接gateway的地址
    21 kong admin passwd gateway dashboard 管理员密码
    22 login_provider_url 账号系统认证服务地址,对接机构的账号系统
    23 staff_outer_url 员工端homeserver的对外访问地址,实际生产环境中配置staff homeserver的外部域名地址
    24 retail_outer_url 客户端homeserver的对外访问地址,实际生产环境中配置retail homeserver的外部域名地址
    25 login_provider_url 账号系统认证服务地址,对接机构的账号系统
    26 staff ios appid 员工端ios appid
    27 staff apns pem url 员工端ios推送证书的地址
    28 retail ios appid 员工端 ios appid
    29 retail apns pem url 客户端ios推送证书地址
    30 autojoin_whitelist 允许用户自动加入白名单
    31 fionchat_license finochat提供的license文件
    32 secret_key homeserver的认证密码
    33 mongo auth mongo数据库认证密码
    34 init plugins 初始化的服务插件
    35 init bots 初始化机器人信息
    36 contact server 通讯录接口服务地址,客户自己开发的通讯录接口应用地址

    确认上述的配置信息准确无误后,点击启动进行部署。

    网盘服务

    网盘服务主要提供图片文件,音视频文件等存储服务,以及Finochat中群文件保密性和转发相关权限的控制。网盘后台服务需要用到两个存储,一个是MongoDB的用户数据存储,一个是minio的文件块存储。minio和MongoDB都是以集群的方式运行,后期可根据业务量进行扩容。netdisk的服务为无状态服务,可根据客户的实际访问量进行扩容。

    以部署两个minio server为例,首先需要在两台机器上添加标签:

    序号 参数 说明
    1 finochat.minio=pcloud 添加标签到任意两台主机,用于调度minio server 服务

    另外,两个节点下的磁盘要挂载数据盘,挂载1T的磁盘或者更大的磁盘。
    在Rancher的管理界面中点击应用商店中选择Minio Cloud Storage这个Stack, 点击View Details进入到这个Stack的详细信息页面,在Template Version选择2.0.0版本,选择后会弹出minio服务的具体配置信息:

    只需修改Host Label to minio Tags调度标签为:“finochat.minio=pcloud”,其他保持默认值即可。部署的节点数可根据实际情况进行变更,如果机器数量足够的话,可以配置为4或者更多,只需要将调度标签添加到相应主机。确认上述的配置信息准确无误后,点击启动进行部署。

    minio服务部署正常启动后,接着需要部署网盘的netdisk后台服务。
    首先需要在两台机器上添加标签:

    序号 参数 说明
    1 netdisk=finochat 添加标签到任意两台主机,用于调度netdisk服务

    然后在Rancher的管理页面中点击应用商店中选择NetDisk这个Stack,点击View Detail进入到这个Stack的详细信息页面,在Template Version选择2.0.0版本,选择后会弹出netdisk服务的具体配置信息:

    参数保持默认值即可,配置确认后,点击启动进行部署。

    知识库

    知识库的服务依赖于MongoDB和Elasticsearch,在安装部署知识库时,需要注意的是,在需要部署Elasticsearch服务的宿主机上需要设置一下vm.max_map_count,具体的操作命令(需要在宿主机的命令终端中执行): “sudo sysctl -w vm.max_map_count=262144”,否则,Elasticsearch启动时会报错,导致服务启动失败。在部署知识库服务时,需要给部署该服务的主机添加4个标签:

    序号 参数 说明
    1 knowledge.ref-server.env=finochat 添加标签到任意主机
    2 knowledge.search-server.env=finochat 添加标签到任意主机
    3 knowledge.mongodb.env=finochat 添加标签到任意主机
    4 knowledge.es.env=finochat 添加标签到任意主机

    添加这几个标签的目的是用来调度知识库的服务到指定的机器上运行。注意:上述几个标签对应的value为finochat是根据应用商店中配置的“finochat environment”的默认值finochat。如果在实际部署中更改了默认值,则对应的标签的value也要进行相应的变更。
    在Rancher的管理界面中点击应用商店中选择knowledge这个Stack,点击View Details进入到这个Stack的详细信息页面,在Template Version选择 2.6.0版本,选择后会弹出知识库服务的具体配置信息:

    上述的变量配置中,需要修改以下配置:

    序号 参数 说明
    1 knowledge_base_enviroment 格式为http://xxx.xxx.xxx.xxx:8000/emp(xxx.xxx.xxx.xxx需替换为kong的宿主机地址,调度标签为kong.env=finochat)

    其他值保持默认值即可,该值表示服务启动后,init服务会等待60s后往知识库中导入初始数据。

    知识库服务对外提供服务的API集中在ref-server和search-server两个服务中,如果后期的用户访问量增加,可以部署多个实例。直接在Rancher中对该服务进行扩容,具体操作如下:

    机器人框架

    机器人框架主要基于Matrix聊天协议接入FinoChat应用并通过FSM(有限状态机)管理会话状态,以及FinoChat ConvoUI协议扩展实现的聊天机器人。

    1 安装

    机器人开发使用Node.js开发,推荐使用v9.4.0版本。Node.js环境搭建这里不做描述,请自行翻阅相关资料搭建。

    安装机器人框架

    npm install finochat-botkit
    

    2 注册机器人

    可通过调用内部接口注册机器人账号,详细信息请联系相关接口人。

    3 config.js配置文件

    module.exports = {
        // 以下配置接口和机器人账号仅供演示测试用
        homeserver: process.env.HOMESERVER || "https://api.finolabs.club",
        loginUrl: process.env.LOGIN_URL || "https://api.finolabs.club/api/v1/registry/botlogin",
        fcid: process.env.FCID || "@test-bot:finolabs.club",
        password: process.env.PASSWORD || "123456",
        logLevel: 'debug',
        timeout: 30000,
        routelist: [],
        whitelist: [],
        blacklist: [],
    };
    
    

    参数说明:
    • homeserver:FinoChat聊天服务器接口,体验部署时https://api.finolabs.club修改为http://<kong的ip>:8000/emp
    • loginUrl:机器人中心登录接口,体验部署时https://api.finolabs.club/api/v1/registry/botlogin修改为http://<kong的ip>:8000/emp/api/v1/registry/botlogin
    • fcid:机器人登录ID
    • password:机器人登录密码

    4 demo工程

    // 引入机器人框架依赖
    const botkit = require('finochat-botkit');
    // 定义状态机的各种状态
    const States = {
        INIT: 'INIT',
        STEP1: 'STEP1',
        STEP2: 'STEP2'
    };
    // 创建自己的机器人
    class demoBot extends botkit.Bot {	
    	// 机器人被邀请加入房间
        onJoinRoom(bot, roomId, userId, displayName) {
            return `亲,我是DemoBot【roomId: ${roomId}, userId: ${userId}, displayName: ${displayName}】`;
        }
        // 用户被邀请加入房间
        onUserJoinRoom(bot, roomId, userId, displayName) {
            return this.onJoinRoom(bot, roomId, userId, displayName);
        }
        // 用户当前视图切换入房间
        onUserEnterRoom(bot, roomId, userId, displayName) {
            return this.onJoinRoom(bot, roomId, userId, displayName);
        }
        // 会话超时结束
        onTimeout(bot, roomId, userId, displayName) {
            return `会话结束【roomId: ${roomId}, userId: ${userId}, displayName: ${displayName}】`;
        }  
        // 状态机定义函数, 主要的逻辑写在这里
        describe(fsm, bot) {
     		/**
             * 状态机 DSL
             * startWith 描述状态机的初始状态和初始 data,只需调用一次
             * when 描述某个状态下,发生Event时,Bot业务执行与状态迁移的细节
             * goto 用于生成when()函数的返回值,返回 nextState
             * stay goto(CurrentState)的另一种形式,停留在本状态
             * stop goto(Done)的另一种形式,结束会话
             */
            const { startWith, when, goto, stay, stop } = botkit.DSL(fsm);
    		/**
             * matcher API
             * BodyCase 匹配普通聊天中的string, 支持变长 pattern(String or RegExp type)
             * ActionCase 匹配convoUI消息的action,支持变长 pattern(String or RegExp type)
             * CommandCase 匹配convoUI消息的command类型,支持变长pattern(String or RegExp type)
             * DefaultCase 模式匹配的Default分支
             */
            const { match, BodyCase, ActionCase, CommandCase, DefaultCase } = botkit.Matcher;	
    		// 初始化状态
            startWith(States.INIT);		
    		// INIT状态描述
            when(States.INIT)(async (sender, content) => {       
            	// 匹配函数,接收第一个参数(content消息体)和其余参数(消息匹配方法)
                return match(content,           
    				// 匹配消息成功后回调,返回客户端消息(withConvoMsg方法)并转移到STEP1状态
                    BodyCase('Hey bot!')(() => {
                        return goto(States.STEP1).withConvoMsg('Hi there!');
                    })
                );
            });
            when(States.STEP1)(async (sender, content) => {
                return match(content,
                    ActionCase('action1')(() => {
                        return goto(States.STEP2).withConvoMsg('U r in Step2 now');
                    }),
                    DefaultCase(() => {               
                    	// ConvoUI工厂方法创建Assist消息
                        const ui = botkit.ConvoFactory.ui()                   	
                        	// body文本在ConvoUI无法渲染时显示,类似HTML中img标签的alt提示属性
                            .setBody('assist demo')                      
                            // Layout工厂方法创建带两个按钮的Assist消息
                            .setPayload(
                                botkit.LayoutFactory.assist().setTitle('assist').addItems(
                                    botkit.ActionFactory.button('button1', 'action1')
                                )
                            );
                        return stay().withConvoMsg(ui);
                    })
                );
            });
            when(States.STEP2)(async (sender, content) => {
                return match(content,
                    BodyCase('apple')(() => {
                        return stop().withConvoMsg('You can buy an Apple product in https://www.apple.com/');
                    })
                );
            });
        }
    }
    // 机器人运行
    new demoBot(require('config')).run();