企微消息推送功能对接
消息推送
官方参考文档:developer.work.weixin.qq.com/docum...
主动发送单聊消息
【POST】【qyapi.weixin.qq.com/cgi-bin/messag...】
企业微信发送单聊消息接口通常最为常用。接口支持推送文本、图片、视频、文件、图文等类型。
具体请求参数及其他详细信息获取,请移步官方文档
文本消息
特殊说明:其中text参数的content字段可以支持换行、以及A标签,即可打开自定义的网页(可参考以上示例代码)(注意:换行符请用转义过的\n)
文本卡片
特殊说明:卡片消息的展现形式非常灵活,支持使用br标签或者空格来进行换行处理,也支持使用div标签来使用不同的字体颜色,目前内置了3种文字颜色:灰色(gray)、高亮(highlight)、默认黑色(normal),将其作为div标签的class属性即可,具体用法请参考上面的示例。
图文news/图文mpnews
mpnews类型的图文消息,跟普通的图文消息一致,唯一的差异是图文内容存储在企业微信。
其他类型
【忽略】按需了解。如:图片、语音、视频、文件…
撤回消息
【post】qyapi.weixin.qq.com/cgi-bin/messag...
可以撤回24小时内通过发送应用消息接口推送的消息,仅可撤回企业微信端的数据,微信插件端的数据不支持撤回。
核心代码:消息类型枚举 + 消息数据结构 + 消息发送与撤销
消息类型枚举
<?php
namespace App\Service;
use App\Service\Traits\EnumTrait;
class OpenWeChatMsgTypeEnum
{
use EnumTrait;
/**
* 文本
*/
const TEXT = 'text';
/**
* 图片
*/
const IMAGE = 'image';
/**
* 语音
*/
const VOICE = 'voice';
/**
* 视频
*/
const VIDEO = 'video';
/**
* 文件
*/
const FILE = 'file';
/**
* 文本卡片
*/
const TEXTCARD = 'textcard';
/**
* 图文
*/
const NEWS = 'news';
/**
* 图文
*/
const MPNEWS = 'mpnews';
/**
* markdown
*/
const MARKDOWN = 'markdown';
/**
* 小程序通知
*/
const MINIPROGRAM_NOTICE = 'miniprogram_notice';
/**
* 模板卡片。包含:文本通知、图文展示、按钮交互、投票选择、多项选择
*/
const TEMPLATE_CARD = 'template_card';
public static function list(): array
{
return [
['id' => self::TEXT, 'name' => '文本'],
['id' => self::IMAGE, 'name' => '图片'],
['id' => self::VOICE, 'name' => '语音'],
['id' => self::VIDEO, 'name' => '视频'],
['id' => self::FILE, 'name' => '文件'],
['id' => self::TEXTCARD, 'name' => '文本卡片'],
['id' => self::NEWS, 'name' => '图文'],
['id' => self::MPNEWS, 'name' => '图文2'],
['id' => self::MARKDOWN, 'name' => 'markdown'],
['id' => self::MINIPROGRAM_NOTICE, 'name' => '小程序通知'],
['id' => self::TEMPLATE_CARD, 'name' => '模板卡片'],
];
}
}
消息数据结构
<?php
namespace App\Service;
/**
* 发送消息的数据类型和结构体
* 常用的文本、卡片、图文,其他类型请参考官方文档
*/
trait OpenWeChatMsgTrait
{
/**
* 消息类型
* @var string
*/
public string $msgType;
/**
* 不同消息类型的数据结构体
* @var array
*/
public array $msgBody;
/**
* 加密信息
* @var int
*/
public int $safe = 0;
/**
* 获取不同消息类型的数据结构body
* @return array
*/
public function getMsgBody()
{
return $this->msgBody;
}
/**
* 特殊说明:其中text参数的content字段可以支持换行、以及A标签,即可打开自定义的网页(可参考以上示例代码)(注意:换行符请用转义过的\n)如:
* 如:
* "text" : {
* "content" : "你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看<a href=\"http://work.weixin.qq.com\">邮件中心视频实况</a>,聪明避开排队。"
* },
* @param $msgType
* @param $content
* @return $this
*/
public function setTextBody($msgType, $content)
{
$this->msgType = $msgType;
$this->msgBody = [
'content' => $content,
];
return $this;
}
/**
* 特殊说明:卡片消息的展现形式非常灵活,支持使用br标签或者空格来进行换行处理,也支持使用div标签来使用不同的字体颜色,目前内置了3种文字颜色:灰色(gray)、高亮(highlight)、默认黑色(normal),将其作为div标签的class属性即可,具体用法请参考上面的示例。
* 如:
* "textcard" : {
* "title" : "领奖通知",
* "description" : "<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>",
* "url" : "URL",
* "btntxt":"更多"
* },
* @param $msgType
* @param $title
* @param $description
* @param $targetUrl
* @param string $btntxt
* @return $this
*/
public function setTextCardBody($msgType, $title, $description, $targetUrl, $btntxt = '更多')
{
$this->msgType = $msgType;
$this->msgBody = [
'title' => $title,
'description' => $description,
'url' => $targetUrl,
'btntxt' => $btntxt,
];
return $this;
}
/**
* mpnews类型的图文消息,跟普通的图文news类型的消息一致,唯一的差异是图文内容存储在企业微信。
* 如:
* "news" : {
* "articles" : [
* {
* "title" : "中秋节礼品领取",
* "description" : "今年中秋节公司有豪礼相送",
* "url" : "URL",
* "picurl" : "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png",
* "appid": "wx123123123123123",
* "pagepath": "pages/index?userid=zhangsan&orderid=123123123"
* }
* ]
* },
* articles : 支持1到8条图文 必填
* articles.*.title 标题 必填
* articles.*.description 描述 不必填
* articles.*.url 跳转 不必填
* articles.*.picurl 图片链接 不必填
* articles.*.appid + articles.*.pagepath 小程序id内部的页面url,填写后忽略 articles.*.url
* @param $msgType
* @param $articles | 要求 articles.* 的结构中必须填写:title, description, url, picurl
* @return $this
*/
public function setNewsBody($msgType, $articles)
{
$this->msgType = $msgType;
$this->msgBody = [
'articles' => $articles
];
return $this;
}
/**
* 同 setNewsBody
* @param $msgType
* @param $articles
* @return $this
*/
public function setMpNewsBody($msgType, $articles)
{
return $this->setNewsBody($msgType, $articles);
}
/**
* 直接设置不同消息类型的不同结构数据body
* 其他类型:图片、语音、视频、文件...
* 参考:https://developer.work.weixin.qq.com/document/path/90236
* @param $msgType
* @param $body
* @return $this
*/
public function setMsgBody($msgType, $body)
{
$this->msgType = $msgType;
$this->msgBody = $body;
return $this;
}
/**
* 设置消息是否加密
* @param $safe
* @return $this
*/
public function setSafe($safe)
{
$this->safe = $safe;
return $this;
}
}
消息发送与撤销
<?php
namespace App\Service;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class OpenWeChatMsgService extends OpenWeChatService
{
use OpenWeChatMsgTrait;
/**
* 发送企微消息
* @param array $toUser | 成员id列表,用“|”分割。特殊情况:"@all"时发送给全员
* @param array $toParty | 部门id列表,用“|”分割。
* @param array $toTag | 标签id列表,用“|”分割。
* @return mixed
* @throws \Exception
*/
public function sendMsg($toUser = [], $toParty = [], $toTag = [])
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' . $this->getAccessToken();
$data["msgtype"] = $this->msgType;
$data["agentid"] = $this->agentid;
$data[$this->msgType] = $this->getMsgBody();
//不咋用的参数
$data["safe"] = $this->safe;
$data["enable_id_trans"] = 0;
$data["enable_duplicate_check"] = 0;
$data["duplicate_check_interval"] = 1800;
$toUserArr = $this->getToUser($toUser, $toParty, $toTag);
$data = array_merge($toUserArr, $data);
try {
$response = Http::post($url, $data)->json();
Log::info(__METHOD__, ['url' => $url, 'data' => $data]);
} catch (\Exception $e) {
throw new \Exception('发送消息失败,url:' . $url . '|参数:' . json_encode($data, JSON_UNESCAPED_UNICODE));
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('发送消息失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
/**
* 获取发送人
* @param $toUser
* @param $toParty
* @param $toTag
* @return array
*/
public function getToUser($toUser, $toParty, $toTag): array
{
if (!app()->isProduction()) {
$toArr['touser'] = config('openwechat.toUser');
return $toArr;
}
$toArr = [];
if ($toUser === ["@all"]) {
$toArr['touser'] = "@all";
return $toArr;
}
if ($toUser) {
$toArr['touser'] = implode('|', $toUser);
}
if ($toParty) {
$toArr['toparty'] = implode('|', $toParty);
}
if ($toTag) {
$toArr['totag'] = implode('|', $toTag);
}
Log::info(__METHOD__ . ':' . $this->getToUserStr($toUser, $toParty, $toTag));
return $toArr;
}
/**
* getToUserStr
* @param $toUser
* @param $toParty
* @param $toTag
* @return string
*/
public function getToUserStr($toUser, $toParty, $toTag): string
{
$toUserStr = 'toUser:' . implode('|', $toUser) . '<==>' . 'toParty:' . implode('|', $toParty) . '<==>' . 'toTag:' . implode('|', $toTag);
return $toUserStr;
}
/**
* 撤回已发送的消息
* 撤回24小时内通过发送应用消息接口推送的消息,仅可撤回企业微信端的数据,微信插件端的数据不支持撤回。
* @param $msgId
* @return mixed
* @throws \Exception
*/
public function recallMsg($msgId)
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=' . $this->getAccessToken();
$data = ['msgid' => $msgId];
try {
$response = Http::post($url, $data)->json();
Log::info(__METHOD__, ['url' => $url, 'data' => $data]);
} catch (\Exception $e) {
throw new \Exception('撤回消息失败,url:' . $url . '|参数:' . json_encode($data, JSON_UNESCAPED_UNICODE));
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('撤回消息失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
}
接收单聊消息
【忽略】按需参考【developer.work.weixin.qq.com/docum...】 通过配置回调形式,接收企业微信的消息
互联企业消息推送
【忽略】按需了解
家校消息推送
【忽略】按需了解
发送消息到群
官方文档:developer.work.weixin.qq.com/docum...
通过接口创建群聊并发送消息到群。
【自建应用自行创建的群】应用消息仅限于发送到通过接口创建的内部群聊
不支持添加企业外部联系人进群。
此接口暂时仅支持企业内的自建应用接入使用,且要求自建应用的可见范围是根部门。
创建群聊会话
创建群聊会话:
【post】qyapi.weixin.qq.com/cgi-bin/appcha...
参数说明:
参数 | 必填 | 定义 | 说明 |
---|---|---|---|
name | 否 | 群聊名 | 最多50个utf8字符,超过将截断 |
owner | 否 | 指定群主的id | 如果不指定,系统会随机从userlist中选一人作为群主 |
userlist | 是 | 群成员id列表 | 至少2人,至多2000人 |
chatid | 否 | 群聊的唯一标志,不能与已有的群重复 | 字符串类型,最长32个字符。如果不填,系统会随机生成群id |
修改群聊会话
主要是新增或删除群成员。
【POST】qyapi.weixin.qq.com/cgi-bin/appcha...
获取群聊会话
获取群聊会话。
【GET】qyapi.weixin.qq.com/cgi-bin/appcha...
推送群消息
推送图文、广告等。分不同消息类型对应不同参数【可参考消息推送的类型,差不多】。
【POST】qyapi.weixin.qq.com/cgi-bin/appcha...
【消息类型】参数参考:developer.work.weixin.qq.com/docum...
群机器人
在群添加机器人,创建者可以在机器人详情页看到该机器人特有的webhookurl
向webhookurl发起【POST】请求,即可实现给该群组发送消息
消息类型及数据格式
官方文档参考:developer.work.weixin.qq.com/docum...
核心代码:群消息推送 + 机器人消息推送
<?php
namespace App\Service;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class OpenWeChatAppChatService extends OpenWeChatService
{
use OpenWeChatMsgTrait;
/**
* 机器人 webHookUrl 的 key
* @var string
*/
public string $robotKey;
/**
* 创建群聊
* 请求方式: POST(HTTPS)
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/appchat/create?access_token=ACCESS_TOKEN
* 入参:
* {
* "name" : "NAME",
* "owner" : "userid1",
* "userlist" : ["userid1", "userid2", "userid3"],
* "chatid" : "CHATID"
* }
* @param $name | 群名
* @param $owner | 群主
* @param $userList | 成员
* @param null $chatId | 群id, 不用传值,企微自行生成
* @return mixed => $response: {"errcode":0,"errmsg":"ok","chatid":"CHATID"}
* @throws \Exception
*/
public function create($name, $owner, $userList, $chatId = null)
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/appchat/create?access_token=' . $this->getAccessToken();
$data = [
'name' => $name,
'owner' => $owner,
'userlist' => $userList,
];
if ($chatId) {
$data['chatid'] = $chatId;
}
try {
$response = Http::post($url, $data)->json();
Log::info(__METHOD__, ['url' => $url, 'data' => $data]);
} catch (\Exception $e) {
throw new \Exception('创建群聊失败,url:' . $url . '|参数:' . json_encode($data, JSON_UNESCAPED_UNICODE));
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('创建群聊失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
/**
* 修改群聊会话
* 请求方式: POST(HTTPS)
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/appchat/update?access_token=ACCESS_TOKEN
* 入参:
* {
* "chatid" : "CHATID",
* "name" : "NAME",
* "owner" : "userid2",
* "add_user_list" : ["userid1", "userid2", "userid3"],
* "del_user_list" : ["userid3", "userid4"]
* }
* @param $chatId |群Id
* @param null $name | 群名
* @param null $owner | 群主
* @param array $addUserList | 新增群成员
* @param array $delUserList | 删除群成员
* @return mixed => $response: {"errcode":0,"errmsg":"ok"}
* @throws \Exception
*/
public function update($chatId, $name = null, $owner = null, $addUserList = [], $delUserList = [])
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/appchat/update?access_token=' . $this->getAccessToken();
$data['chatid'] = $chatId;
if ($name) {
$data['name'] = $name;
}
if ($owner) {
$data['owner'] = $owner;
}
if ($addUserList) {
$data['add_user_list'] = $addUserList;
}
if ($delUserList) {
$data['del_user_list'] = $delUserList;
}
try {
$response = Http::post($url, $data)->json();
Log::info(__METHOD__, ['url' => $url, 'data' => $data]);
} catch (\Exception $e) {
throw new \Exception('修改群聊失败,url:' . $url . '|参数:' . json_encode($data, JSON_UNESCAPED_UNICODE));
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('修改群聊失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
/**
* 获取群聊会话
* 请求方式: GET(HTTPS)
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/appchat/get?access_token=ACCESS_TOKEN&chatid=CHATID
* @param $chatId
* @return mixed => $response: {"errcode":0,"errmsg":"ok","chat_info":{"chatid":"CHATID","name":"NAME","owner":"userid2","userlist":["userid1","userid2","userid3"]}}
* @throws \Exception
*/
public function get($chatId)
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/appchat/get?access_token=' . $this->getAccessToken() . '&chatid=' . $chatId;
try {
$response = Http::get($url)->json();
} catch (\Exception $e) {
throw new \Exception('获取群聊【/appchat/get】详情失败,请求失败:' . $url);
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('获取群聊【/appchat/get】详情失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
/////////////////////////////////////////////////////////////--群消息推送--/////////////////////////////////////////////////////////////////////////////////
/**
* 应用推送消息
* 请求方式: POST(HTTPS)
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=ACCESS_TOKEN
* @param $chatId | 推送目标群Id
* @return mixed => $response: {"errcode":0,"errmsg":"ok"}
* @throws \Exception
*/
public function sendMsg($chatId)
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=' . $this->getAccessToken();
$data['chatid'] = $chatId;
$data['msgtype'] = $this->msgType;
$data[$this->msgType] = $this->getMsgBody();
$data['safe'] = $this->safe;
try {
$response = Http::post($url, $data)->json();
Log::info(__METHOD__, ['url' => $url, 'data' => $data]);
} catch (\Exception $e) {
throw new \Exception('推送群消息失败,url:' . $url . '|参数:' . json_encode($data, JSON_UNESCAPED_UNICODE));
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('推送群消息失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
/////////////////////////////////////////////////////////////--机器人发群消息--//////////////////////////////////////////////////////////////////////////////
/**
* 普通文本可做@操作:mentioned_list、mentioned_mobile_list
* 其他类型同群消息推送
* "text" : {
* "content": "广州今日天气:29度,大部分多云,降雨概率:60%",
* "mentioned_list":["wangqing","@all"], // userid的列表
* "mentioned_mobile_list":["13800001111","@all"] // 手机号列表
* },
* @param $msgType
* @param $content
* @param array $userIds | userid的列表, ["@all"]表示全部
* @param array $mobiles | 手机号列表, ["@all"]表示全部
* @return OpenWeChatAppChatService
*/
public function setRobotTextBody($msgType, $content, $userIds = [], $mobiles = [])
{
$this->msgType = $msgType;
$this->msgBody = [
'content' => $content,
'mentioned_list' => $userIds,
'mentioned_mobile_list' => $mobiles,
];
return $this;
}
/**
* 自定义机器人key
* @param $robotKey
* @return OpenWeChatAppChatService
*/
public function setRobotKey($robotKey)
{
$this->robotKey = $robotKey;
return $this;
}
/**
* 向机器人发送消息
* 创建者在机器人详情页获取机器人的 webhookurl
* @return mixed
* @throws \Exception
*/
public function sendRobotMsg()
{
$webHookUrl = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=' . $this->robotKey;
$data['msgtype'] = $this->msgType;
$data[$this->msgType] = $this->getMsgBody();
try {
$response = Http::post($webHookUrl, $data)->json();
Log::info(__METHOD__, ['url' => $webHookUrl, 'data' => $data]);
} catch (\Exception $e) {
throw new \Exception('机器人发送消息失败,url:' . $webHookUrl . '|参数:' . json_encode($data, JSON_UNESCAPED_UNICODE));
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('机器人发送消息失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
/**
* 文件上传接口,仅指定机器人可用(三天内有效)
* 请求方式:POST(HTTPS)
* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=KEY&type=TYPE
* 使用multipart/form-data POST上传文件或语音, 文件标识名为"media"
* @param $file : 资源 resource
* @param string $type : 语音(voice)和普通文件(file)
* @return mixed => $response: {"errcode":0,"errmsg":"ok","type":"file","media_id":"1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0","created_at":"1380000000"}
* @throws \Exception
*/
public function upload($file, $type = 'file'): array
{
$url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=' . $this->robotKey . '&type=' . $type;
try {
$response = Http::attach('attachment', $file, $file->filename)->post($url);
} catch (\Exception $e) {
throw new \Exception('机器人发送消息失败,url:' . $url);
}
if (isset($response['errcode']) && $response['errcode'] <> 0) {
throw new \Exception('机器人发送消息失败,响应:' . json_encode($response, JSON_UNESCAPED_UNICODE));
}
return $response;
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
这代码看着头皮发麻 :joy:
顶一下,已对接企业微信的好友价功能。企业微信的确有点复杂