Wenzi

如何通过企业微信发送消息通知

蚊子前端博客
发布于 2023/02/10 14:52
企业微信为我们提供了多种消息通知的方式,每种方式都有哪些特点,怎么接入呢?

通过企业微信发送消息通知有两种方式:

  1. 群机器人;
  2. 企业内部自建应用;

两者的对比

接入难易程度 发送范围 是否可以接收回调
群机器人 简单,任何人都可以创建,并添加到群组中(默认任何人,但管理员可以开启白名单) 只能往群组中发送消息 不能,只能发送,不能接收
企业自建应用 需要管理员添加,并需要配置发送消息服务器的 IP 白名单 可以指定给某人或某几人或某部门发送消息(在管理员指定范围内) 可以接收消息,实现互动

适用范围

  • 群机器人:适合小范围推送,或者群内所有人或大部分人都关心的消息,否则容易对群内其他人造成困扰。如服务的监控消息、每周一次的科技信息等。
  • 企业自建应用:适合精准推送,推送一些偏私密性的消息,或者只需要让他自己知道就足够了。比如禅道的任务、bug 状态变动、内容增删等的通知;比如流水线的通知(谁触发谁接收通知)等;

群机器人 #

群机器人,顾名思义,是只能在群组中才能添加的机器人。二人对话的聊天框中是无法添加的,但我们可以通过一些技巧来实现。

如何创建小群测试 #

有时候我们想单独测试下我们的机器人,又不想打扰别人,可以用如下的方式操作。

先默默拉几个人建一个群,注意,不要发送消息、不要改群名。不要进行任何操作。然后再默默把其他人踢掉,就可以形成二人群或者一人群了。其他人是完全不感知的,他们是不知道自己被拉群了,然后又被踢掉了。

比如这个群,这群里只有我一个人:

群里只有一个人

添加群机器人 #

群里任何人都可以添加机器人。

添加成功后,就会有一个对应的 webhook 地址,其他应用使用这个地址,就可以通过群机器人发送消息了。 注意,不要泄露您的 webhook 地址,避免他人通过该地址发送垃圾消息。

如何发送消息 #

群机器人发送消息还是比较简单的,按照文档配置即可。开发者可以按以下说明向这个地址发起 HTTP POST 请求,即可实现给该群组发送消息。

官方发送不同格式消息的地址:https://developer.work.weixin.qq.com/document/path/91770

如在 shell 脚本中:

curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxx-yyyyyy-zzzzzz' \
 -H 'Content-Type: application/json' \
 -d '
  {
    "msgtype": "text",
    "text": {
      "content": "hello world"
    }
  }'

比如我在流水线是用的群机器人,来提示我流水线的启动和结束。gitlab 流水线可以执行 shell 脚本,我们直接编写 shell 脚本即可。

通过shell命令向机器人发通知

很奇怪,这段代码在博客里一直发布不成功,就改成截图了。

有的同学会通过后台服务发送一些信息,如在 nodejs 中:

const axios = require("axios");

const send = async () => {
  const result = await axios({
    url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxx-yyyyyy-zzzzzz",
    method: "post",
    data: {
      msgtype: "news",
      news: {
        articles: [
          {
            title: "中秋节礼品领取",
            description: "今年中秋节公司有豪礼相送",
            url: "www.qq.com",
            picurl:
              "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png",
          },
        ],
      },
    },
  });
  if (result.status === 200 && result.data?.errcode === 0) {
    console.log("success", result.data);
  } else {
    console.error("fail");
  }
};
send();

把机器人发布到公司 #

若您觉得您的群机器人功能很不错,后台服务也比较稳定,有的同学会搞一些笑话机器人、每周信息汇总机器人、每日一语机器人等等。就可以将其发布到公司范围内,其他人可以就把他添加到别的群组里。

将机器人发布到公司

添加机器人时,可以从这里选择已发布到公司的机器人。

发布到公司的机器人列表

对于没有发布到公司的群机器人,只能靠大家的口口相传了。

点评 #

企业微信机器人引入简单,使用方便。相应的,能力也有很大的局限性,比如无法鉴权,谁拿到这个地址,都能发送消息;同时,只有在有这个机器人的群里,才能收到消息,否则就无法感知。

比如上面的那个群里,群里只有我自己,我就可以接收到通知,但其他人,虽然他们触发流水产生的群机器人消息,但因为他们不在这个群里,就无法收到通知。

企业自建应用 #

如何创建企业自建应用 #

这需要拥有该企业微信的管理员权限,才能创建自建应用。

一. 进入管理后台,选择应用管理,下拉到底部,点击创建应用;

二. 填入提前准备好的 logo 图片(建议使用 750*750,1M 以内的 jpg、png 图片)、应用名称和应用介绍(选填),然后选择可见范围;

同时,还要准备好域名和几个 IP,这里需要从管理后台下载一个验证文件,放到该域名对应的服务器根目录中,域名验证通过后就可以添加 IP 白名单了。

  • 主动推送消息:可以在设置了本地机器 IP 白名单后,在本地机器就可以发起测试;
  • 接收并回复消息:只能在已验证的域名上操作;

三. 创建成功;得到如下的几条数据(获取方式:https://developer.work.weixin.qq.com/document/path/90665):

  • corpId: 即企业 ID,在“我的企业”tab 的最底部;
  • secret: 即该应用的密钥,点击进入刚才创建的应用内进行查看;
  • agentId: 该应用的 id(主动发送消息时用不到,但接收消息时需要);

若您不是管理员,还请将第 2 序列中准备好的数据给到公司的管理员,待管理员创建成功后,得到第 3 序列中需要的数据。

自建应用的特点:

  1. 只有能由有管理员权限的人员,进行创建;
  2. 可以定向给企业内的任何成员发送消息,对其他成员无干扰;
  3. 可以接收消息,并进行相应的回复(关键词的自动回复和接入程序的更复杂的回复等);
  4. 需要验证域名、IP 白名单,并且需要该企业对应的 corpId,才能正常使用;

获取 access_token #

企业自建应用的任何功能,都要首先获取到 access_token。

官网地址:https://developer.work.weixin.qq.com/document/path/91039

通过已获取到的 corpId 和 secret 就可以拿到 access_token 了。不过 token 接口有请求频率的限制,并且获取到的 token 有 7200s(2 小时)的有效期,程序需要缓存该 token。

const axios = require("axios");
const { corpId, secret } = require("./utils/secret");
const cache = require("./utils/cache");

const getAccessToken = async () => {
  const cacheToken = await cache.get("token");
  if (cacheToken) {
    return cacheToken;
  }
  const { status, data } = await axios({
    url: "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
    params: {
      corpid: corpId,
      corpsecret: secret,
    },
  });
  if (status === 200 && data.errcode === 0) {
    cache.set("token", data.access_token);
    return data.access_token;
  }
  return null;
};

module.exports = getAccessToken;

发送消息推送 #

企业自建应用,除消息推送外,还有很多其他的功能,如通讯录管理、身份验证、消息推送、创建群聊等,不过我们这里主讲消息推送这块。

跟群机器人类似,消息也有很多种类型,每种类型的消息所需要的字段也不一样,大家可自行查阅文档。这里仅举一个发送文本消息的例子:

const axios = require("axios");
const getAccessToken = require("./src/get-access-token");
const { agentId } = require("./src/utils/secret");

const sendTextMsg = async () => {
  const accessToken = await getAccessToken();

  const { status, data } = await axios({
    url: `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accessToken}`,
    method: "post",
    data: {
      touser: "xiaowenzi", // 多个用户,用 | 隔开
      // toparty: 'PartyID1|PartyID2', // 部门 id
      // totag: 'TagID1 | TagID2',
      msgtype: "text",
      agentid: agentId,
      text: {
        content:
          '你的快递已到,请携带工卡前往邮件中心领取。\n 出发前可查看<a href="https://www.xiabingbao.com">邮件中心视频实况</a>,聪明避开排队。',
      },
    },
  });
  console.log(status, data);
};

我们在创建好「消息提醒」的自建应用后,其他的内网服务(如禅道、知识库、流水线、错误率警告等服务),都可以通过该应用向用户或部门发送通知,方便周知各种内容的变更。

接收并回复消息 #

自建应用还有一个很重要的功能,就是可以接收每个用户发送过来的任何消息(包括底部菜单的点击),然后再针对该消息,进行相应的回复。

这里的配置相对来说比较麻烦一些:

  1. 所有的操作只能在管理后台配置的链接进行;
  2. 接收和要回复的消息均是 xml 格式或 xml string 格式的;

在接收消息之前,首先要在管理后台配置接收消息的线上地址,在保存地址时,就会校验这个地址的有效性,即企业微信会以 GET 请求的方式,携带一些参数,请求该 url,若能正常解密 url 参数中的内容并返回,即为配置成功。

加密和解密的算法与官方已有库 #

在接收并回复消息模块中,有三个过程:

  1. 验证 url:接收参数并解析出参数中的内容,然后返回;主要是在保存 url 地址时使用;
  2. 接收消息:企业微信会以 POST 方式并携带参数请求我们的保存的地址 ,根据参数和 body 中的数据,解析出真正的消息;
  3. 回复消息:把要回复的消息,连同时间戳、随机数等进行加密,然后返回给企业微信;

第 2、3 过程是连续的,若要回复消息,则直接返回即可;若不想回复消息或回复需要很长的时间(官方会等待 5 秒时间),可以直接在第 2 步中返回 200(即以空串为返回包),然后再通过上面的“发送消息推送”,主动给相关用户推送消息。

各种加密解密算法也挺绕的,官方也出了一些相关语言的包(库),可以直接拿来使用(https://developer.work.weixin.qq.com/document/path/90307)。我这里只有一个能运行 php 的主机,因此选择了 php 语言的库。各位可以根据自己的需要,选择相应的库即可。

我这里使用了 codeigniter 框架。

验证 url #

// 检测 url 的合法性
private function checkValidUrl()
{
  $this->load->library('WXBizMsgCrypt');

  $this->wxbizmsgcrypt->init($this->token, $this->encodingAesKey, $this->corpId);

  $sVerifyMsgSig = $this->input->get("msg_signature");
  $sVerifyTimeStamp = $this->input->get("timestamp");
  $sVerifyNonce = $this->input->get("nonce");
  $sVerifyEchoStr = $this->input->get("echostr");

  // 需要返回的明文
  $sEchoStr = "";
  $errCode = $this->wxbizmsgcrypt->VerifyURL($sVerifyMsgSig, $sVerifyTimeStamp, $sVerifyNonce, $sVerifyEchoStr, $sEchoStr);
  if ($errCode == 0) {
    echo $sEchoStr;
  } else {
    print("ERR: " . $errCode . "\n\n");
  }
}

接收消息 #

接收消息这里很特殊,企业微信发送过来的是一个 xml string 类型的。我也是好久没写过 php 了,不知道怎么接收这个数据,用 post 方式尝试了 N 多次,也没成功。后来才查到相关资料是用file_get_contents("php://input")的方式来接收。

接收到所有的数据,再通过官方提供的解密函数,解析出真实的 xml 信息。注意,这里并不是单纯的消息,还有各种如发送用户、发送的消息类型、发送的时间等信息。还得需要通过 xml 的进一步解析,才能解析出各个字段的值。

/**
 * 接收消息
 * 参数接收一些加密参数,具体消息是通过post的body传过来的,
 * 在php中,若body是一个纯字符串,需要用 file_get_contents('php://input') 的方式来接收
 *
 * 关于file_get_contents和post的区别:
 * @see https://www.cnblogs.com/phpper/p/9574419.html
 */
private function decodeMsg()
{
  $this->load->library('WXBizMsgCrypt');

  $this->wxbizmsgcrypt->init($this->token, $this->encodingAesKey, $this->corpId);

  $sReqMsgSig = $this->input->get("msg_signature");
  $sReqTimeStamp = $this->input->get("timestamp");
  $sReqNonce = $this->input->get("nonce");

  // post请求的密文数据
  $sReqData = file_get_contents('php://input');

  $sMsg = "";  // 解析之后的明文

  $errCode = $this->wxbizmsgcrypt->DecryptMsg($sReqMsgSig, $sReqTimeStamp, $sReqNonce, $sReqData, $sMsg);

  if ($errCode == 0) {
    // 解密成功,sMsg即为xml格式的明文
    echo ($sMsg);
    return $sMsg;
    // TODO: 对明文的处理
    /*
"<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName>
<FromUserName><![CDATA[mycreate]]></FromUserName>
<CreateTime>1409659813</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[hello]]></Content>
<MsgId>4561255354251345929</MsgId>
<AgentID>218</AgentID>
</xml>"
*/
  } else {
    print("ERR: " . $errCode . "\n\n");
  }
}

回复消息 #

回复消息与接收消息差不多,根据官方要求的字段格式,拼接 xml,然后再以 string 类型进行加密。

private function sendMsg()
{
  $this->load->library('WXBizMsgCrypt');

  $this->wxbizmsgcrypt->init($this->token, $this->encodingAesKey, $this->corpId);

  // 接收消息
  $getMsg = $this->decodeMsg();

  // 接收到消息后,经过处理,然后需要返回给用户消息了

  $now = time();

  // 需要发送的明文
  $sRespData = "<xml><ToUserName><![CDATA[xiaowenzi]]></ToUserName><FromUserName><![CDATA[{$this->corpId}]]></FromUserName><CreateTime>{$now}</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[{$getMsg}]]></Content><AgentID>{$this->agentId}</AgentID></xml>";
  $sReqTimeStamp = $now;
  $sReqNonce = rand();
  $sEncryptMsg = ""; //xml格式的密文
  $errCode = $this->wxbizmsgcrypt->EncryptMsg($sRespData, $sReqTimeStamp, $sReqNonce, $sEncryptMsg);
  if ($errCode == 0) {
    echo ($sEncryptMsg);
    // print("done \n");
    // TODO:
    // 加密成功,企业需要将加密之后的sEncryptMsg返回
    // HttpUtils.SetResponce($sEncryptMsg);  //回复加密之后的密文
  } else {
    print("ERR: " . $errCode . "\n\n");
    // exit(-1);
  }
}

在群聊会话中发通知 #

比如一些活动报名、或者中奖名单等,有一长串的用户名单,需要拉群知会一些消息。若要手动创建的话,那每次都得搜索拉入,需要很长时间。但若通过接口创建的话,几秒钟就可以。

创建群聊 #

创建群聊官方地址:https://developer.work.weixin.qq.com/document/path/90245

{
  "name": "NAME",
  "owner": "userid1",
  "userlist": ["userid1", "userid2", "userid3"],
  "chatid": "CHATID"
}

设置好群聊名称、群主、群成员、群 Id(可选),就可以创建了。创建成功后,会返回该群聊的 id:

// 创建群聊
const createGroupChat = async () => {
  const accessToken = await getAccessToken();
  const { status, data } = await axios({
    url: `https://qyapi.weixin.qq.com/cgi-bin/appchat/create?access_token=${accessToken}`,
    method: "post",
    data: {
      // chatid: Date.now(),
      name: `企业内建应用创建的群聊-${Date.now().toString(36)}`,
      owner: "xiaowenzi",
      userlist: ["xiaowenzi", "dawenzi"], // 成员名单至少需要2个人
    },
  });
  // 创建成功后,不会立即在聊天框中展示出来,需要通过发送消息,来激活该群聊
  console.log("createGroupChat", status, data); // chatid: wruVG5OwAAQF_MwwspiyXmD8T7NQt8yA
};

群聊创建成功后,会返回该群聊的 id,用于后续的比如修改群聊标题、群聊成员、发送群聊消息等操作。而且,群聊刚创建成功时,是不会立即在聊天框中展示出来的,需要通过发送消息,来激活该群聊。

向群聊中发送消息 #

这里也有一个发送消息的接口,但这里的接口跟上面的“发送消息推送”不是同一个接口。而且,这里的接口还需要指定群 id(即 chatid)才能发送消息。

const sendMsgToGroup = async () => {
  const accessToken = await getAccessToken();
  const { status, data } = await axios({
    url: `https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=${accessToken}`,
    method: "post",
    data: {
      chatid: "chatid", // 修改自己的群聊id
      msgtype: "markdown",
      markdown: {
        content:
          '# 你的快递已到\n请携带工卡前往邮件中心领取\n<a href="http://work.weixin.qq.com">邮件中心视频实况</a>,聪明避开排队',
      },
      safe: 0,
    },
  });
  console.log("sendMsgToGroup", status, data);
};

这里发送消息的格式,也是有多种格式。

总结 #

群机器人和企业自建应用有着不同的接入难度和接入场景,各位可以根据自己的需要,来选择适合自己的方式。

阅读(3603)
Simple Empty
No data