微信群发,那些我踩过的坑

      最近刚刚完成一个微信群发图文消息的需求,同样也要先来一句吐槽:鹅厂的微信文档不能不看,又不能全信,挖坑不断,堪比剪不断,理还乱。

    开发环境:框架使用TP3.2.3,PHP版本>=5.6

      根据微信公众平台技术文档—消息管理—群发接口和原创校验可以看到,微信群发是比较繁琐的,但是复杂的事情都是简单问题的堆积,所以不要怕,一步步按照文档说明来开发。

1. 获取access_token

    做过微信开发的应该都知道access_token的重要性,文档介绍也已经说明了,不多说,不清楚的看接口文档微信公众平台技术文档—开始开发—获取access_token。

代码:

/**
     *  获取公众号的全局唯一接口调用凭据access_token
     *  建议这个函数写在公共方法或其他公共类中,方便调用
     */
    function getAccessToken(){
        $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET';
        $result = request_curl($url);
        if($result['errcode']){
            exit(json_encode($result));
        }
        S('access_token',$result['access_token'],$result['expires_in']);
        return $result['access_token'];
    }

/**
* 公共函数,curl访问远程连接
* @param string $url 需要访问的连接
* @param array|string  $data post请求方式时,需要提交的数据
*/ 
 function request_curl($url, $data = ''){
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    if (!empty($data)){
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $result = curl_exec($ch);
    curl_close($ch);
    $rs = json_decode($result, 1);
    return $rs;
}

/**
* 每次在用到的地方调取函数获取access_token的方法
*/
 $accessToken = S('access_token');
 if(empty($accessToken)){
     $accessToken = getAccessToken();
 }

2. 准备图片素材

    微信文档已经说过,微信内图片的浏览是不支持外部连接的,所以图文信息中的所有图片都需要预先上传到微信上。

(1)上传图文消息内的图片获取URL【订阅号与服务号认证后均可用】

    这个接口是用来上传微信图文消息中正文里的图片的,编辑正文时,插入图片的方式需要插入的是这个接口返回的图片url。其实这个接口就是微信公众平台技术文档—素材管理—新增永久素材—上传图文消息内的图片获取URL。

代码:(第一个坑:此处图片上传的curl传参方式,需要使用CURLFile,具体处理请看以下代码

/**
      *  微信——上传图文消息内的图片获取URL
      *  页面上传图片,得到图片的本地地址
      */
    public function uploadimg(){
        if (IS_POST){
            $postdata = I('post.pic');
            $news_url = trim($postdata['pic']); // 上传的图片相对地址,例如在TP3.2.3的框架中,我图片的存储地址是:./Public/Uploads/Wx/3.jpg
            // 图片的类型判断
            $ext = pathinfo($news_url, PATHINFO_EXTENSION);
            if($ext != 'jpg' && $ext != 'png'){
                deletePic($news_url); // 删除已上传的图片
                $this->error('图片仅支持jpg/png格式,请重传!', U('Wx/uploadimg'),3);
            }
            // 图片大小判断,单位字节
            $file_size = filesize($news_url);
            if(bccomp($file_size, 1024*1024) >= 0){
                deletePic($news_url);
                $this->error('图片大小必须在1MB以下,请重传!', U('Wx/uploadimg'),3);
            }
            // 图片上传微信
            //首先获取access_token
            $accessToken = S('access_token');
            if(empty($accessToken)){
                $accessToken = getAccessToken();
            }
            $wx_url = 'https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token='.$accessToken;
            $wx_data = array('media'=>new \CURLFile(realpath(C('UPLOAD_IMG_PATH').$news_url))); // PHP版本<=5.5的,可以使用array('media'=>'@'.$news_url)的方式上传图片,PHP>=5.6的,只能使用CURLFile上传图片,建议使用CURLFile上传
            $res = request_curl($wx_url ,$wx_data);
            if($res['url']){ // 上传成功
                // 请自行处理
                deletePic($news_url);
                $this->success("微信上传图文消息内的图片成功", U('Wx/uploadimg'),3);
            }else{
                // 上传失败,请自行处理
            }
         }
            
        
    }

(2)图文消息中需要的thumb_media_id的获取(第二个坑:这个media_id的获取,需要的是微信公众平台技术文档—素材管理—新增临时素材,而不是新增永久素材—新增其他类型永久素材

代码:

/**
      *  微信——新增临时素材
      *  因为上传图文消息的接口中提到thumb_media_id是缩略图media_id,所以这里处理图片按照“新增临时素材”接口中规定的缩略图规格判断上传的图片
      * @return json
      */
    public function mediaUpload(){
        if(IS_POST){
            $model=new WxNewsModel();
            $postdata = I('post.');
            $data['material_type'] = $postdata['material_type'];
            $news_url = trim($postdata['i_pic']); // 上传的图片的相对地址,例如我的是./Public/Uploads/1.jpg
           
        if(empty($news_url)){
            $this->error("请上传缩略图",U('Wx/mediaUpload'),3);
        }
        // 图片的类型判断
        $ext = pathinfo($news_url, PATHINFO_EXTENSION);
        if($ext != 'jpg'){
            deletePic($news_url); // 删除已上传本地图片的公共函数
            $this->error('缩略图仅支持jpg格式,请重传!', U('Wx/mediaUpload'),3);
        }
        // 图片大小判断,单位字节
        $file_size = filesize($news_url);
        if(bccomp($file_size, 64*1024) >= 0){
            deletePic($news_url);
            $this->error('缩略图大小必须在64KB以下,请重传!', U('Wx/mediaUpload'),3);
        }
        $material_type = 'thumb'; // 类型
        $material_type_key = 'thumb_media_id'; // 上传成功后返回的media_id的键名
 
            // 图片上传微信
            //首先获取access_token
            $accessToken = S('access_token');
            if(empty($accessToken)){
                $accessToken = $this->getAccessToken();
            }
            $wx_url = 'https://api.weixin.qq.com/cgi-bin/media/upload?access_token='.$accessToken.'&type='.$material_type;
            $wx_data = array('media'=>new \CURLFile(realpath($news_url)));
            $res = request_curl($wx_url ,$wx_data);
            if($res['type']){ // 上传成功
                deletePic($news_url); // 公共函数删除本地图片
                /*  此处可以做一些操作,例如储存返回的信息等 */
                $this->logs(0, "微信临时素材上传成功,本地添加失败", U('Wx/mediaUpload'));
            }else{ // 上传失败
                $this->deletePic($news_url);
                $this->logs(0, '失败:'.json_encode($res), U('Wx/mediaUpload'));
            }
        }
    }

3. 准备需要上传的图文消息(第三个坑:需要上传的信息,记得要做urlencode编码,尤其消息内容:含有双引号的建议替换成单引号,特殊字符转换成html实体再进行urlencode编码;发送post数据,发送json格式,记得再将经过编码处理的解码后再发送

    图片都准备好后,就应该上传想要群发的图文消息了,根据“上传图文信息的接口”说明可知,每次最少上传1条,最多可以上传8条消息,所以一定要注意。

代码:

/**
      *  上传图文信息到微信
      *  这里的前提条件:图文消息,都已经提前准备好
      * @return json
      */
    public function uploadnews(){
        $model=new WxMassMessageModel();
        $mass_ids=I('get.mass_ids/s'); // 所选中的要上传的图文消息id
        if(empty($mass_ids)){
            echo "请至少选择一条消息";
            exit;
        }
        $mass_ids = explode(',', $mass_ids);
        if(count($mass_ids) > 8){
            echo "一次最多支持8条消息的上传";
            exit;
//            $this->error("一次最多支持8条消息的上传",U('WxMassMessage/showlist'),3);
        }
        $where = array();
        $where['mass_id'] = array('IN', $mass_ids);
        $mass_res=$model->getWxByWhereSelect($where); // 根据接口参数要求,获取要上传的图文消息字段值
        foreach($mass_res as $mass_k => &$mass_v){ // 对需要上传的消息要做urlencode编码处理,此处使用引用,为了直接改变原数组的值
            foreach ($mass_v as $k => $v){
                if($k == 'content'){ // 图文消息的正文,先将含有双引号先替换成单引号,再将特殊字符转换为HTML实体,最后在进行urlencode编码。这样做主要是防止图片等的消息不能正确在微信浏览器中被访问到
                    $mass_v[$k] = urlencode(htmlspecialchars(str_replace("\"", "'", $v)));
                }else{
                    $mass_v[$k] = urlencode($v);
                }
            }
        }
        $uploadnews = array(
            'articles' => $mass_res,
        );

        $uploadnews = htmlspecialchars_decode(urldecode(json_encode($uploadnews))); // 需要上传的消息,使用json串格式,然后使用urldecode及htmlspecialchars_decode对上面的信息依次进行解码
        //首先获取access_token
        $accessToken = S('access_token');
        if(empty($accessToken)){
            $accessToken = $this->getAccessToken();
        }
        $url = 'https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token='.$accessToken;
        $uploadnews_res = request_curl($url, $uploadnews);
        if($uploadnews_res['errcode']){ // 上传失败,返回错误信息
            $this->savelogs('微信上传图文消息失败'.json_encode($uploadnews_res, JSON_UNESCAPED_UNICODE));
            echo json_encode($uploadnews_res);
            exit();
        }else{ // 上传成功
            $this->savelogs('微信上传图文消息成功media_id='.$uploadnews_res['media_id']); // 存储日志
//            file_put_contents('./Public/wx_uploadnews_logs.txt', date('Y-m-d H:i:s').'||'.json_encode($uploadnews_res, JSON_UNESCAPED_UNICODE).PHP_EOL, FILE_APPEND);
            // 根据需要向表中存储信息
            $mass_info_data = array();
            $mass_info_data['type'] = 'news'; // 消息类型
            $mass_info_data['media_id'] = $uploadnews_res['media_id'];
            $mass_info_data['created_at'] = $uploadnews_res['created_at'];
            $massInfoModel = new WxMassInfoModel();
            $mass_info_res = $massInfoModel->createAction($mass_info_data, $mass_ids);
            if($mass_info_res){
                echo '微信上传成功,本地添加成功';
                exit();
            }else{
                echo '微信上传成功,本地添加失败!';
                exit();
            }
        }
    }

4. 群发消息(根据标签进行群发)

    群发消息微信文档提供了“根据标签群发”和“根据openID列表群发”两种方式,我这里的需求是根据标签进行群发。标签已经提前在对应的微信公众号后台建好,并将标签列表存储到了本地库中,所以标签的创建我就不再提供代码,有需要的自行查看微信公众平台技术文档—用户管理—用户标签管理

代码:

/**
      *  根据标签群发
      * @return json
      */
    public function byTagMassSend(){
        if(IS_POST){
            $postdata = I('post.');
            $massSendModel = new WxMassSendModel();
            $where_data = array();
            $where_data['mass_info_id'] = $postdata['mass_info_id']; // 需要群发的 已上传信息返回信息存储在本地的id
            $where_data['tag_id'] = $postdata['tag_id']; // 标签的tag_id
            $mass_where_count = $massSendModel->getByWhereCount($where_data);
            unset($where_data);
            if($mass_where_count > 0){
                $this->error("同一消息不能向同一标签重复推送",U('WxMassInfo/showlist'),3);
            }
            $is_to_all = false;
            if(empty($postdata['tag_id'])){
                $is_to_all = true;
            }
            $mass_send_arr = array(
                'filter' => array(
                    'is_to_all' => $is_to_all,
                    'tag_id' => $postdata['tag_id'],
                ),
                'mpnews' => array(
                    'media_id' => $postdata['media_id']
                ),
                'msgtype' => 'mpnews',
                'send_ignore_reprint' => $postdata['send_ignore_reprint']
            );
            $mass_send_json = json_encode($mass_send_arr);
            unset($mass_send_arr);
            // 群发送
            //首先获取access_token
            $accessToken = S('access_token');
            if(empty($accessToken)){
                $accessToken = $this->getAccessToken();
            }
            $url = 'https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token='.$accessToken;
            $mass_send_res = request_curl($url, $mass_send_json);
            if($mass_send_res['errcode'] == 0){ // 消息发送成功
//                file_put_contents('./Public/wx_mass_sendall_logs.txt', date('Y-m-d H:i:s').'||'.json_encode($mass_send_res, JSON_UNESCAPED_UNICODE).PHP_EOL, FILE_APPEND);
                /* 以下是根据需要进行的一些信息存储 */
                $data = array();
                /*$material_type_key = C('WX_NEWS_MATERIAL_TYPE_KEY');
                $material_type = strtoupper($postdata['type']);*/
                $data['type'] = $postdata['type'];
                $data['msg_id'] = $mass_send_res['msg_id'];
                $data['msg_data_id'] = $mass_send_res['msg_data_id'];
                $data['created_at'] = time();
                $data['mass_info_id'] = $postdata['mass_info_id'];
                $data['tag_id'] = $postdata['tag_id'];
                $data['is_to_all'] = $is_to_all ? 1 : 0;
                $data['send_ignore_reprint'] = $postdata['send_ignore_reprint'];
                $mass_res = $massSendModel->createAction($data);
                if($mass_res){
                    $this->logs(1, "微信群推送成功,本地添加成功", U('WxMassInfo/showlist'));
                }else{
                    $this->logs(1, "微信群推送成功,本地添加失败", U('WxMassInfo/showlist'));
                }
            }else{ // 消息发送失败
                // 存储日志信息
                $this->logs(0, "微信群发失败:".json_encode($mass_send_res, JSON_UNESCAPED_UNICODE), U('WxMassInfo/byTagMassSend', array('id'=>$postdata['mass_info_id'])));
            }
        }
    }

5. 到此,消息群发成功,在对应标签下的用户,能够收到信息。

本次微信群发参考的文档有:

PHP微信公众号开发——群发消息

考虑 PHP 5.0~5.6 各版本兼容性的 cURL 文件上传

微信上传图文消息素材报错:{ errcode: 40007, errmsg: 'invalid media_id hint: [klcWoA0078ure1]' }

微信高级群发接口正文乱码解决方案

你可能感兴趣的:(微信群发,技术,微信开发,PHP)