企微消息推送功能对接

消息推送

官方参考文档: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...

通过接口创建群聊并发送消息到群。

  1. 【自建应用自行创建的群】应用消息仅限于发送到通过接口创建的内部群聊

  2. 不支持添加企业外部联系人进群。

  3. 此接口暂时仅支持企业内的自建应用接入使用,且要求自建应用的可见范围是根部门。

创建群聊会话

创建群聊会话:
【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 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

这代码看着头皮发麻 :joy:

1个月前 评论

顶一下,已对接企业微信的好友价功能。企业微信的确有点复杂

1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!