最近刚刚完成一个微信群发图文消息的需求,同样也要先来一句吐槽:鹅厂的微信文档不能不看,又不能全信,挖坑不断,堪比剪不断,理还乱。
开发环境:框架使用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 5.0~5.6 各版本兼容性的 cURL 文件上传
微信上传图文消息素材报错:{ errcode: 40007, errmsg: 'invalid media_id hint: [klcWoA0078ure1]' }