php 微信 sdk api

彭海阳
2023-12-01
<?php

class Weixin {

    private $receive;
    private $token;
    private $appid;
    private $appsecret;
    private $access_token;
    public $debug = false;
    public $error = false;
    public $errcode = array(
        '-1' => '系统繁忙',
        '0' => '请求成功',
        '40001' => '获取access_token时AppSecret错误,或者access_token无效',
        '40002' => '不合法的凭证类型',
        '40003' => '不合法的OpenID',
        '40004' => '不合法的媒体文件类型',
        '40005' => '不合法的文件类型',
        '40006' => '不合法的文件大小',
        '40007' => '不合法的媒体文件id',
        '40008' => '不合法的消息类型',
        '40009' => '不合法的图片文件大小',
        '40010' => '不合法的语音文件大小',
        '40011' => '不合法的视频文件大小',
        '40012' => '不合法的缩略图文件大小',
        '40013' => '不合法的APPID',
        '40014' => '不合法的access_token',
        '40015' => '不合法的菜单类型',
        '40016' => '不合法的按钮个数',
        '40017' => '不合法的按钮个数',
        '40018' => '不合法的按钮名字长度',
        '40019' => '不合法的按钮KEY长度',
        '40020' => '不合法的按钮URL长度',
        '40021' => '不合法的菜单版本号',
        '40022' => '不合法的子菜单级数',
        '40023' => '不合法的子菜单按钮个数',
        '40024' => '不合法的子菜单按钮类型',
        '40025' => '不合法的子菜单按钮名字长度',
        '40026' => '不合法的子菜单按钮KEY长度',
        '40027' => '不合法的子菜单按钮URL长度',
        '40028' => '不合法的自定义菜单使用用户',
        '40029' => '不合法的oauth_code',
        '40030' => '不合法的refresh_token',
        '40031' => '不合法的openid列表',
        '40032' => '不合法的openid列表长度',
        '40033' => '不合法的请求字符,不能包含\uxxxx格式的字符',
        '40035' => '不合法的参数',
        '40038' => '不合法的请求格式',
        '40039' => '不合法的URL长度',
        '40050' => '不合法的分组id',
        '40051' => '分组名字不合法',
        '41001' => '缺少access_token参数',
        '41002' => '缺少appid参数',
        '41003' => '缺少refresh_token参数',
        '41004' => '缺少secret参数',
        '41005' => '缺少多媒体文件数据',
        '41006' => '缺少media_id参数',
        '41007' => '缺少子菜单数据',
        '41008' => '缺少oauth code',
        '41009' => '缺少openid',
        '42001' => 'access_token超时',
        '42002' => 'refresh_token超时',
        '42003' => 'oauth_code超时',
        '43001' => '需要GET请求',
        '43002' => '需要POST请求',
        '43003' => '需要HTTPS请求',
        '43004' => '需要接收者关注',
        '43005' => '需要好友关系',
        '44001' => '多媒体文件为空',
        '44002' => 'POST的数据包为空',
        '44003' => '图文消息内容为空',
        '44004' => '文本消息内容为空',
        '45001' => '多媒体文件大小超过限制',
        '45002' => '消息内容超过限制',
        '45003' => '标题字段超过限制',
        '45004' => '描述字段超过限制',
        '45005' => '链接字段超过限制',
        '45006' => '图片链接字段超过限制',
        '45007' => '语音播放时间超过限制',
        '45008' => '图文消息超过限制',
        '45009' => '接口调用超过限制',
        '45010' => '创建菜单个数超过限制',
        '45015' => '回复时间超过限制',
        '45016' => '系统分组,不允许修改',
        '45017' => '分组名字过长',
        '45018' => '分组数量超过上限',
        '46001' => '不存在媒体数据',
        '46002' => '不存在的菜单版本',
        '46003' => '不存在的菜单数据',
        '46004' => '不存在的用户',
        '47001' => '解析JSON/XML内容错误',
        '48001' => 'api功能未授权',
        '50001' => '用户未授权该api'
    );

    public function __construct($options) {
        $this->token = isset($options['token']) ? $options['token'] : '';
        $this->receive = isset($options['receive']) ? $options['receive'] : '';
        $this->appid = isset($options['appid']) ? $options['appid'] : '';
        $this->appsecret = isset($options['appsecret']) ? $options['appsecret'] : '';
        $this->debug = isset($options['debug']) ? $options['debug'] : false;
    }

    public function get_error() {
        return $this->error;
    }

    //==============================接收消息===================================
    /**
     * 获取微信服务器发来的信息
     */
    public function getRev() {
        $postStr = file_get_contents("php://input");
        $this->log($postStr);
        if (!empty($postStr)) {
            $this->receive = (array) simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
        }
        return $this;
    }

    /**
     * 获取微信服务器发来的信息
     */
    public function getRevData() {
        return $this->receive;
    }

    /**
     * 获取消息发送者
     */
    public function getRevFrom() {
        if ($this->receive) {
            return $this->receive['FromUserName'];
        }
        return false;
    }

    /**
     * 获取接收消息的类型
     */
    public function getRevType() {
        if (isset($this->receive['MsgType'])) {
            return $this->receive['MsgType'];
        }
        return false;
    }

    /**
     * 获取消息ID
     */
    public function getRevID() {
        if (isset($this->receive['MsgId'])) {
            return $this->receive['MsgId'];
        }
        return false;
    }

    /**
     * 获取消息发送时间
     */
    public function getRevCtime() {
        if (isset($this->receive['CreateTime'])) {
            return $this->receive['CreateTime'];
        }
        return false;
    }

    /**
     * 获取接收消息内容正文
     */
    public function getRevContent() {
        if (isset($this->receive['Content'])) {
            return $this->receive['Content'];
        } elseif (isset($this->receive['Recognition'])) {
            //获取语音识别文字内容,需申请开通
            return $this->receive['Recognition'];
        }
        return false;
    }

    /**
     * 获取接收消息图片
     */
    public function getRevPic() {
        if (isset($this->receive['PicUrl'])) {
            return $this->receive['PicUrl'];
        }
        return false;
    }

    /**
     * 获取接收消息链接
     */
    public function getRevLink() {
        if (isset($this->receive['Url'])) {
            return array(
                'url' => $this->receive['Url'],
                'title' => $this->receive['Title'],
                'description' => $this->receive['Description']
            );
        }
        return false;
    }

    /**
     * 获取接收地理位置
     */
    public function getRevGeo() {
        if (isset($this->receive['Location_X'])) {
            return array(
                'x' => $this->receive['Location_X'],
                'y' => $this->receive['Location_Y'],
                'scale' => $this->receive['Scale'],
                'label' => $this->receive['Label']
            );
        }
        return false;
    }

    /**
     * 获取接收事件推送
     */
    public function getRevEvent() {
        if (isset($this->receive['Event'])) {
            return array(
                'event' => $this->receive['Event'],
                'key' => $this->receive['EventKey'],
            );
        }
        return false;
    }

    /**
     * 获取接收语言推送
     */
    public function getRevVoice() {
        if (isset($this->receive['MediaId'])) {
            return array(
                'mediaid' => $this->receive['MediaId'],
                'format' => $this->receive['Format'],
            );
        }
        return false;
    }

    /**
     * 获取接收视频推送
     */
    public function getRevVideo() {
        if (isset($this->receive['MediaId'])) {
            return array(
                'mediaid' => $this->receive['MediaId'],
                'thumbmediaid' => $this->receive['ThumbMediaId']
            );
        }
        return false;
    }

    /**
     * 获取消息接受者
     */
    public function getRevTo() {
        if ($this->receive) {
            return $this->receive['ToUserName'];
        }
        return false;
    }

    /**
     * 获取access token     2000次/天
     * @param type $appid 第三方用户唯一凭证 
     * @param type $secret 第三方用户唯一凭证密钥,既appsecret 
     */
    private function get_access_token() {
        if (isset($_COOKIE['access_token'])) {
            $this->access_token = $_COOKIE['access_token'];
        }

        if (empty($this->access_token)) {
            $data = $this->http_get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $this->appid . '&secret=' . $this->appsecret);
            $this->access_token = $data['access_token'];
            setcookie("access_token", $data['access_token'], time() + 7200);
        }
        return $this->access_token;
    }

    /**
     * 回复文本消息
     * @param string $content
     * @return \Core_Helper_Weixin
     */
    public function send_text($content) {
        $msg = array(
            'ToUserName' => $this->getRevFrom(),
            'FromUserName' => $this->getRevTo(),
            'CreateTime' => time(),
            'MsgType' => 'text',
            'Content' => $content
        );
        return $this->send($msg);
    }

    /**
     * 回复图片消息
     * @param int $id
     * @return \Core_Helper_Weixin
     */
    public function send_image($id) {
        $msg = array(
            'ToUserName' => $this->getRevFrom(),
            'FromUserName' => $this->getRevTo(),
            'CreateTime' => time(),
            'MsgType' => 'image',
            'Image' => array('MediaId' => $id)
        );
        return $this->send($msg);
    }

    /**
     * 回复语音消息
     * @param int $id
     * @return \Core_Helper_Weixin
     */
    public function send_voice($id) {
        $msg = array(
            'ToUserName' => $this->getRevFrom(),
            'FromUserName' => $this->getRevTo(),
            'CreateTime' => time(),
            'MsgType' => 'voice',
            'Voice' => array('MediaId' => $id)
        );
        return $this->send($msg);
    }

    /**
     * 回复视频消息
     * @param int $id
     * @param string $title
     * @param string $desc
     * @return \Core_Helper_Weixin
     */
    public function send_video($id, $title, $desc) {
        $msg = array(
            'ToUserName' => $this->getRevFrom(),
            'FromUserName' => $this->getRevTo(),
            'CreateTime' => time(),
            'MsgType' => 'video',
            'Video' => array(
                'MediaId' => $id,
                'Title' => $title,
                'Description' => $desc
            )
        );
        return $this->send($msg);
    }

    /**
     * 回复音乐消息
     * @param string $title  音乐标题 
     * @param string $url    音乐链接 
     * @param string $desc   音乐描述 
     * @param string $hq_url 高质量音乐链接,WIFI环境优先使用该链接播放音乐 
     * @return \Core_Helper_Weixin
     */
    public function send_music($title, $url = '', $desc = '', $hq_url = '', $id = '') {
        $msg = array(
            'ToUserName' => $this->getRevFrom(),
            'FromUserName' => $this->getRevTo(),
            'CreateTime' => time(),
            'MsgType' => 'music',
            'Music' => array(
                'Title' => $title,
                'Description' => $desc,
                'MusicUrl' => $url,
                'HQMusicUrl' => empty($hq_url) ? $url : $hq_url,
                "ThumbMediaId" => $id
            )
        );
        return $this->send($msg);
    }

    /**
     * 回复图文消息
     * @param array $news
     * 数组结构:
     * array(
     *         [0]=>array(
     *                 'Title'=>'msg title',
     *                 'Description'=>'summary text',
     *                 'PicUrl'=>'http://www.domain.com/1.jpg',
     *                 'Url'=>'http://www.domain.com/1.html'
     *         ),
     *         [1]=>....
     * )
     * 
     */
    public function send_news(array $news = array()) {
        $count = count($news);
        $msg = array(
            'ToUserName' => $this->getRevFrom(),
            'FromUserName' => $this->getRevTo(),
            'CreateTime' => time(),
            'MsgType' => 'news',
            'ArticleCount' => $count,
            'Articles' => $news
        );
        return $this->send($msg);
    }

    private function send($msg, $type = 'XML') {
        if ($type == 'XML') {
            echo self::xml_encode($msg);
        } else {
            echo self::json_encode($msg);
        }
    }

    //==============================发送消息===================================

    public function custom_send_text($fromuser, $content) {
        $msg = array(
            'touser' => $fromuser,
            'msgtype' => 'text',
            'text' => array(
                'content' => $content
            )
        );
        return $this->custom_send($msg);
    }

    public function custom_send_image($fromuser, $id) {
        $msg = array(
            'touser' => $fromuser,
            'msgtype' => 'image',
            'image' => array(
                "media_id" => $id
            )
        );
        return $this->custom_send($msg);
    }

    public function custom_send_voice($fromuser, $id) {
        $msg = array(
            'touser' => $fromuser,
            'msgtype' => 'voice',
            'voice' => array(
                "media_id" => $id
            )
        );
        return $this->custom_send($msg);
    }

    public function custom_send_video($fromuser, $id) {
        $msg = array(
            'touser' => $fromuser,
            'msgtype' => 'video',
            'video' => array(
                "media_id" => $id
            )
        );
        return $this->custom_send($msg);
    }

    public function custom_send_music($fromuser, $title, $url = '', $desc = '', $hq_url = '', $id = '') {
        $msg = array(
            'touser' => $fromuser,
            'msgtype' => 'music',
            'music' => array(
                "title" => $title,
                "description" => $desc,
                "musicurl" => $url,
                "hqmusicurl" => empty($hq_url) ? $url : $hq_url,
                "thumb_media_id" => $id
            )
        );
        return $this->custom_send($msg);
    }

    private function custom_send($msg) {
        return $this->http_post('https://api.weixin.qq.com/cgi-bin/message/custom?access_token=' . $this->get_access_token(), self::json_encode($msg));
    }

    /**
     * 查询分组
     */
    public function groups_get() {
        return $this->http_get('https://api.weixin.qq.com/cgi-bin/groups/get?access_token=' . $this->get_access_token());
    }

    /**
     * 创建分组
     * 最多支持创建500个分组
     * {"group":{"name":"test"}}
     */
    public function groups_create($name) {
        $data = array('group' => array('name' => $name));
        return $this->http_post('https://api.weixin.qq.com/cgi-bin/groups/create?access_token=' . $this->get_access_token(), self::json_encode($data));
    }

    /**
     * 修改分组名
     * {"group":{"id":108,"name":"test2_modify2"}}
     */
    public function groups_update($id, $name) {
        $data = array('group' => array("id" => $id, 'name' => $name));
        return $this->http_post('https://api.weixin.qq.com/cgi-bin/groups/update?access_token=' . $this->get_access_token(), self::json_encode($data));
    }

    /**
     * 移动用户分组
     * {"openid":"oDF3iYx0ro3_7jD4HFRDfrjdCM58","to_groupid":108}
     */
    public function groups_members_update($openid, $to_groupid) {
        $data = array('group' => array("openid" => $openid, 'to_groupid' => $to_groupid));
        return $this->http_post('https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=' . $this->get_access_token(), self::json_encode($data));
    }

    /**
     * 获取用户基本信息
     * @param string $openid 普通用户的标识,对当前公众号唯一
     * @return array   "subscribe"         用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。 
     *                  "openid"            用户的标识,对当前公众号唯一 
     *                  "nickname"          用户的昵称 
     *                  "sex": 1            用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 
     *                  "language"          用户的语言,简体中文为zh_CN 
     *                  "city"              用户所在城市 
     *                  "province"          用户所在省份 
     *                  "country"           用户所在国家 
     *                  "headimgurl"        用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 
     *                  "subscribe_time"    用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间 
     */
    public function user_info($openid) {
        return $this->http_get('https://api.weixin.qq.com/cgi-bin/user/info?access_token=' . $this->get_access_token() . '&openid=' . $openid);
    }

    /**
     * 获取关注者列表
     * @param type $next_openid $next_openid 第一个拉取的OPENID,不填默认从头开始拉取 
     * @return {
     *      "total":23000, //关注该公众账号的总用户数 
     *      "count":10000, //拉取的OPENID个数,最大值为10000 
     *      "data":{"      //列表数据,OPENID的列表 
     *        openid":[
     *            "OPENID1",
     *            "OPENID2",
     *            ...,
     *            "OPENID10000"
     *         ]
     *        },
     *      "next_openid":"NEXT_OPENID1" //拉取列表的后一个用户的OPENID
     *     }
     */
    public function user_get($next_openid = '') {
        return $this->http_get('http://api.weixin.qq.com/cgi-bin/user/get?access_token=' . $this->get_access_token() . '&next_openid=' . $next_openid);
    }

    /**
     * 创建菜单
     * @param array $data 菜单数组数据
     * example:
     * array(
     *       "button"=>
     *               array(
     *                    array('type'=>'click','name'=>'最新消息','key'=>'MENU_KEY_NEWS'),
     *                    array('type'=>'view','name'=>'我要搜索','url'=>'http://www.baidu.com'),
     *               )
     * );
     */
    public function create_menu($data) {
        $this->http_post('https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=' . $this->get_access_token(), self::json_encode($data));
        return TRUE;
    }

    /**
     * 自定义菜单查询接口
     * @return array
     */
    public function menu_get() {
        return $this->http_get('https://api.weixin.qq.com/cgi-bin/menu/get?access_token=' . $this->get_access_token());
    }

    /**
     * 自定义菜单删除接口
     * @return boolean
     */
    public function menu_delete() {
        $this->http_get('https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=' . $this->get_access_token());
        return true;
    }

    /**
     * 创建二维码ticket
     * @param int  $scene_id       自定义追踪id,临时二维码时为32位整型,永久二维码时最大值为1000
     * @param type $action_name    二维码类型,
     *                              QR_SCENE为临时 https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
     *                              QR_LIMIT_SCENE为永久 https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
     * @param type $expire_seconds 该二维码有效时间,以秒为单位。 最大不超过1800。
     * @return string $ticket
     */
    public function qrcode_create($id, $type = TRUE, $expire = 1800) {
        $param = array(
            'action_name' => $type ? "QR_LIMIT_SCENE" : "QR_SCENE",
            'expire_seconds' => $expire,
            'action_info' => array('scene' => array('scene_id' => $id))
        );
        $data = $this->http_post('https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $this->get_access_token(), $param);
        return $data['ticket'];
    }

    /**
     * 通过ticket换取二维码
     * @return string $image 图片
     */
    public function qrcode_show() {
        return $this->http_get('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' . $this->token);
    }

    private function http_get($url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //设置是否返回信息
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $result = curl_exec($ch);
        if (curl_errno($ch)) {
            $this->error = curl_error($ch);
            return FALSE;
        }
        $data = json_decode($result, TRUE);
        if (isset($data['errcode']) && $data['errcode'] !== 0) {
            $this->error = $this->errcode[$data['errcode']];
            return FALSE;
        }
        curl_close($ch);
        return $data;
    }

    private function http_post($url, $param) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //设置是否返回信息
        curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, self::json_encode($param)); //POST数据
        $result = curl_exec($ch);
        if (curl_errno($ch)) {
            $this->error = curl_error($ch);
            return FALSE;
        }
        $data = json_decode($result, TRUE);
        if (isset($data['errcode']) && $data['errcode'] !== 0) {
            $this->error = $this->errcode[$data['errcode']];
            return FALSE;
        }
        curl_close($ch);
        return $data;
    }

    /**
     * 微信api不支持中文转义的json结构
     * @param array $arr
     */
    private static function json_encode($arr) {
        $parts = array();
        $is_list = false;
        $keys = array_keys($arr);
        $max_length = count($arr) - 1;
        if (($keys [0] === 0) && ($keys [$max_length] === $max_length )) {
            $is_list = true;
            for ($i = 0; $i < count($keys); $i ++) {
                if ($i != $keys [$i]) {
                    $is_list = false;
                    break;
                }
            }
        }
        foreach ($arr as $key => $value) {
            if (is_array($value)) {
                if ($is_list)
                    $parts [] = self::json_encode($value);
                else
                    $parts [] = '"' . $key . '":' . self::json_encode($value);
            } else {
                $str = '';
                if (!$is_list)
                    $str = '"' . $key . '":';
                if (is_numeric($value) && $value < 2000000000)
                    $str .= $value;
                elseif ($value === false)
                    $str .= 'false';
                elseif ($value === true)
                    $str .= 'true';
                else
                    $str .= '"' . addslashes($value) . '"';
                $parts [] = $str;
            }
        }
        $json = implode(',', $parts);
        if ($is_list)
            return '[' . $json . ']';
        return '{' . $json . '}';
    }

    /**
     * XML编码
     * @param mixed $data 数据
     * @param string $root 根节点名
     * @param string $item 数字索引的子节点名
     * @param string $attr 根节点属性
     * @param string $id 数字索引子节点key转换的属性名
     * @param string $encoding 数据编码
     * @return string
     */
    private function xml_encode($data, $root = 'xml', $item = 'item', $attr = '', $id = 'id', $encoding = 'utf-8') {
        if (is_array($attr)) {
            $_attr = array();
            foreach ($attr as $key => $value) {
                $_attr[] = "{$key}=\"{$value}\"";
            }
            $attr = implode(' ', $_attr);
        }
        $attr = trim($attr);
        $attr = empty($attr) ? '' : " {$attr}";
        $xml = "<{$root}{$attr}>";
        $xml .= self::data_to_xml($data, $item, $id);
        $xml .= "</{$root}>";
        return $xml;
    }

    /**
     * 数据XML编码
     * @param mixed $data 数据
     * @return string
     */
    private static function data_to_xml($data) {
        $xml = '';
        foreach ($data as $key => $val) {
            is_numeric($key) && $key = "item id=\"$key\"";
            $xml .= "<$key>";
            $xml .= ( is_array($val) || is_object($val)) ? self::data_to_xml($val) : self::xmlSafeStr($val);
            list($key, ) = explode(' ', $key);
            $xml .= "</$key>";
        }
        return $xml;
    }

    private static function xmlSafeStr($str) {
        return '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $str) . ']]>';
    }

    private function log($log) {
        if ($this->debug) {
            
        }
    }

    /**
     * 验证消息真实性
     * @return boolean
     */
    public function checkSignature() {
        $signature = isset($_GET["signature"]) ? $_GET["signature"] : '';
        $timestamp = isset($_GET["timestamp"]) ? $_GET["timestamp"] : '';
        $nonce = isset($_GET["nonce"]) ? $_GET["nonce"] : '';
        $tmpArr = array($this->token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = sha1(implode($tmpArr));
        if ($tmpStr == $signature) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 公众平台校验
     * @return string
     */
    public function valid() {
        $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"] : '';
        if ($this->checkSignature()) {

            return $echoStr;
        }
    }

}


 类似资料: