4

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

 1 year ago
source link: https://www.xiabingbao.com/post/infrastructure/wxwork-notice-rpur2t.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

蚊子前端博客
发布于 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);
};

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK