├── README.md ├── menu.php ├── share.php ├── authorize.php ├── custom_msg.php └── message.php /README.md: -------------------------------------------------------------------------------- 1 | # weixinMp 2 | 一个PHP文件搞定微信公众号系列 3 | 4 | 网上的很多PHP微信公众号教程都颇为复杂,且需要配置和引入较多的文件,本人通过整理后给出一个单文件版的(代码只有200行左右),每个文件独立运行,不依赖和引入其他文件,希望可以给各位想接入微信公众号接口的带来些许帮助和借鉴意义。 5 | 6 | 一个PHP文件搞定微信支付系列请移步:https://github.com/dedemao/weixinPay 7 | 8 | 9 | # 环境依赖 10 | 11 | PHP5.3及其以上,且需要开启CURL服务、SSL服务。 12 | 13 | # 文件对应说明 14 | 15 | menu.php 自定义菜单创建 16 | 17 | message.php 消息管理(以关注、取消关注消息为例) 18 | 19 | 20 | # 注意事项 21 | 22 | 1.需要用到哪个接口,就只下载对应的单个文件即可。 23 | 24 | 2.文件开头的配置信息必须完善 25 | 26 | 27 | # 若对您有帮助,可以赞助并支持下作者哦,谢谢! 28 | 29 |

30 | 31 |

联系邮箱:884358@qq.com

32 |

33 | -------------------------------------------------------------------------------- /menu.php: -------------------------------------------------------------------------------- 1 | menuCreate($data); 21 | 22 | if($result['errcode']==0){ 23 | echo '

创建菜单成功!

'; 24 | }else{ 25 | echo '

创建菜单失败:'.$result['errmsg'].'

'; 26 | } 27 | 28 | class WxService 29 | { 30 | protected $appid; 31 | protected $appsecret; 32 | protected $templateId; 33 | protected $token = null; 34 | public $data = null; 35 | public function __construct($appid, $appsecret) 36 | { 37 | $this->appid = $appid; 38 | $this->appsecret = $appsecret; 39 | $this->token = $this->getToken(); 40 | } 41 | 42 | public function menuCreate($data) 43 | { 44 | $url = 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token='.$this->getToken(); 45 | $menu = array(); 46 | $i=0; 47 | foreach ($data as $item){ 48 | $menu['button'][$i]['name'] = $item['name'][0]; 49 | if($item['sub_button']){ 50 | $j=0; 51 | foreach ($item['sub_button'] as $sub){ 52 | $menu['button'][$i]['sub_button'][$j]['type'] = 'view'; 53 | $menu['button'][$i]['sub_button'][$j]['name'] = $sub[0]; 54 | $menu['button'][$i]['sub_button'][$j]['url'] = $sub[1]; 55 | $j++; 56 | } 57 | }else{ 58 | $menu['button'][$i]['type'] = 'view'; 59 | $menu['button'][$i]['url'] = $item['name'][1]; 60 | } 61 | $i++; 62 | } 63 | 64 | $data = self::xjson_encode($menu); 65 | $data = str_replace('\/','/',$data); 66 | $result = self::curlPost($url,$data); 67 | return json_decode($result,true); 68 | } 69 | 70 | 71 | function getToken() { 72 | if($this->token) return $this->token; 73 | $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$this->appid.'&secret='.$this->appsecret; 74 | $res = self::curlGet($url); 75 | $result = json_decode($res, true); 76 | if($result['errmsg']){ 77 | echo $res;exit(); 78 | } 79 | return $result['access_token']; 80 | } 81 | 82 | public static function curlGet($url = '', $options = array()) 83 | { 84 | $ch = curl_init($url); 85 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 86 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 87 | if (!empty($options)) { 88 | curl_setopt_array($ch, $options); 89 | } 90 | //https请求 不验证证书和host 91 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 92 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 93 | $data = curl_exec($ch); 94 | curl_close($ch); 95 | return $data; 96 | } 97 | public static function curlPost($url = '', $postData = '', $options = array()) 98 | { 99 | $ch = curl_init(); 100 | curl_setopt($ch, CURLOPT_URL, $url); 101 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 102 | curl_setopt($ch, CURLOPT_POST, 1); 103 | curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); 104 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数 105 | if (!empty($options)) { 106 | curl_setopt_array($ch, $options); 107 | } 108 | //https请求 不验证证书和host 109 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 110 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 111 | $data = curl_exec($ch); 112 | if($data === false) 113 | { 114 | echo 'Curl error: ' . curl_error($ch);exit(); 115 | } 116 | curl_close($ch); 117 | return $data; 118 | } 119 | 120 | public static function xjson_encode($data) 121 | { 122 | if(version_compare(PHP_VERSION,'5.4.0','<')){ 123 | $str = json_encode($data); 124 | $str = preg_replace_callback("#\\\u([0-9a-f]{4})#i",function($matchs){ 125 | return iconv('UCS-2BE', 'UTF-8', pack('H4', $matchs[1])); 126 | },$str); 127 | return $str; 128 | } 129 | return json_encode($data, JSON_UNESCAPED_UNICODE); 130 | } 131 | } 132 | ?> -------------------------------------------------------------------------------- /share.php: -------------------------------------------------------------------------------- 1 | 开发->基本配置->AppID 6 | $appKey = ''; //微信公众平台->开发->基本配置->AppSecret 7 | $title = '微信分享测试'; //微信分享显示的标题 8 | $desc = '微信分享测试,这里显示的是描述文字'; //微信分享显示的文字描述 9 | $img = 'https://www.dedemao.com/uploads/allimg/1709/03/2-1FZ3144P7-m.jpg'; //微信分享显示的缩略图 10 | /* 配置结束 */ 11 | $action = isset($_GET['action']) ? $_GET['action'] : ''; 12 | if($action == 'getConfig'){ 13 | $wxService = new WxService($appid,$appKey); 14 | $url = $_GET['url']; 15 | $config = $wxService->getShareConfig($url); 16 | echo json_encode($config);exit(); 17 | } 18 | ?> 19 | 20 | 21 | 22 | 23 | 微信分享DEMO 24 | 25 | 26 | 27 |

微信分享DEMO

28 | 29 | 30 | 95 | 96 | 97 | appid = $appid; 106 | $this->appKey = $appKey; 107 | $this->token = $this->wx_get_token(); 108 | } 109 | 110 | //获取微信公从号access_token 111 | public function wx_get_token() { 112 | $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$this->appid.'&secret='.$this->appKey; 113 | $res = self::curlGet($url); 114 | $res = json_decode($res, true); 115 | if($res['errmsg']){ 116 | echo json_encode($res);exit(); 117 | } 118 | //这里应该把access_token缓存起来,有效期是7200s 119 | return $res['access_token']; 120 | } 121 | //获取微信公从号ticket 122 | public function wx_get_jsapi_ticket() { 123 | $url = sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", $this->token); 124 | $res = self::curlGet($url); 125 | $res = json_decode($res, true); 126 | //这里应该把ticket缓存起来,有效期是7200s 127 | return $res['ticket']; 128 | } 129 | 130 | public function getShareConfig($url) { 131 | $wx = array(); 132 | //生成签名的时间戳 133 | $wx['timestamp'] = time(); 134 | //生成签名的随机串 135 | $wx['noncestr'] = uniqid(); 136 | //jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。 137 | $wx['jsapi_ticket'] = $this->wx_get_jsapi_ticket(); 138 | //分享的地址 139 | $wx['url'] = $url; 140 | $string = sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s", $wx['jsapi_ticket'], $wx['noncestr'], $wx['timestamp'], $wx['url']); 141 | //生成签名 142 | $wx['signature'] = sha1($string); 143 | return $wx; 144 | } 145 | 146 | public static function curlGet($url = '', $options = array()) 147 | { 148 | $ch = curl_init($url); 149 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 150 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 151 | if (!empty($options)) { 152 | curl_setopt_array($ch, $options); 153 | } 154 | //https请求 不验证证书和host 155 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 156 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 157 | $data = curl_exec($ch); 158 | curl_close($ch); 159 | return $data; 160 | } 161 | } 162 | ?> -------------------------------------------------------------------------------- /authorize.php: -------------------------------------------------------------------------------- 1 | 开发->基本配置->AppID 6 | $appKey = ''; //微信公众平台->开发->基本配置->AppSecret 7 | /* 配置结束 */ 8 | 9 | //①、获取用户openid 10 | $wxPay = new WxService($appid,$appKey); 11 | $data = $wxPay->GetOpenid(); //获取openid 12 | if(!$data['openid']) exit('获取openid失败'); 13 | //②、获取用户信息 14 | $user = $wxPay->getUserInfo($data['openid'],$data['access_token']); 15 | ?> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 微信获取用户信息demo 24 | 25 | 26 | 27 | 28 |
29 |
30 |

你的基本信息如下:

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
openid
unionid
昵称
头像
性别
省份 / 城市
language
73 |
74 |
75 | 76 | 77 | appid = $appid; //微信支付申请对应的公众号的APPID 88 | $this->appKey = $appKey; //微信支付申请对应的公众号的APP Key 89 | } 90 | 91 | /** 92 | * 通过跳转获取用户的openid,跳转流程如下: 93 | * 1、设置自己需要调回的url及其其他参数,跳转到微信服务器https://open.weixin.qq.com/connect/oauth2/authorize 94 | * 2、微信服务处理完成之后会跳转回用户redirect_uri地址,此时会带上一些参数,如:code 95 | * 96 | * @return 用户的openid 97 | */ 98 | public function GetOpenid() 99 | { 100 | //通过code获得openid 101 | if (!isset($_GET['code'])){ 102 | //触发微信返回code码 103 | $baseUrl = $this->getCurrentUrl(); 104 | $url = $this->__CreateOauthUrlForCode($baseUrl); 105 | Header("Location: $url"); 106 | exit(); 107 | } else { 108 | //获取code码,以获取openid 109 | $code = $_GET['code']; 110 | $openid = $this->getOpenidFromMp($code); 111 | return $openid; 112 | } 113 | } 114 | 115 | public function getCurrentUrl() 116 | { 117 | $scheme = $_SERVER['HTTPS']=='on' ? 'https://' : 'http://'; 118 | $uri = $_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']; 119 | if($_SERVER['REQUEST_URI']) $uri = $_SERVER['REQUEST_URI']; 120 | $baseUrl = urlencode($scheme.$_SERVER['HTTP_HOST'].$uri); 121 | return $baseUrl; 122 | } 123 | 124 | /** 125 | * 通过code从工作平台获取openid机器access_token 126 | * @param string $code 微信跳转回来带上的code 127 | * @return openid 128 | */ 129 | public function GetOpenidFromMp($code) 130 | { 131 | $url = $this->__CreateOauthUrlForOpenid($code); 132 | $res = self::curlGet($url); 133 | $data = json_decode($res,true); 134 | $this->data = $data; 135 | return $data; 136 | } 137 | 138 | /** 139 | * 构造获取open和access_toke的url地址 140 | * @param string $code,微信跳转带回的code 141 | * @return 请求的url 142 | */ 143 | private function __CreateOauthUrlForOpenid($code) 144 | { 145 | $urlObj["appid"] = $this->appid; 146 | $urlObj["secret"] = $this->appKey; 147 | $urlObj["code"] = $code; 148 | $urlObj["grant_type"] = "authorization_code"; 149 | $bizString = $this->ToUrlParams($urlObj); 150 | return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString; 151 | } 152 | 153 | /** 154 | * 构造获取code的url连接 155 | * @param string $redirectUrl 微信服务器回跳的url,需要url编码 156 | * @return 返回构造好的url 157 | */ 158 | private function __CreateOauthUrlForCode($redirectUrl) 159 | { 160 | $urlObj["appid"] = $this->appid; 161 | $urlObj["redirect_uri"] = "$redirectUrl"; 162 | $urlObj["response_type"] = "code"; 163 | $urlObj["scope"] = "snsapi_userinfo"; 164 | $urlObj["state"] = "STATE"; 165 | $bizString = $this->ToUrlParams($urlObj); 166 | return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString; 167 | } 168 | 169 | /** 170 | * 拼接签名字符串 171 | * @param array $urlObj 172 | * @return 返回已经拼接好的字符串 173 | */ 174 | private function ToUrlParams($urlObj) 175 | { 176 | $buff = ""; 177 | foreach ($urlObj as $k => $v) 178 | { 179 | if($k != "sign") $buff .= $k . "=" . $v . "&"; 180 | } 181 | $buff = trim($buff, "&"); 182 | return $buff; 183 | } 184 | 185 | /** 186 | * 获取用户信息 187 | * @param string $openid 调用【网页授权获取用户信息】接口获取到用户在该公众号下的Openid 188 | * @return string 189 | */ 190 | public function getUserInfo($openid,$access_token) 191 | { 192 | 193 | $response = self::curlGet('https://api.weixin.qq.com/sns/userinfo?access_token='.$access_token.'&openid='.$openid.'&lang=zh_CN'); 194 | return json_decode($response,true); 195 | 196 | } 197 | 198 | public static function curlGet($url = '', $options = array()) 199 | { 200 | $ch = curl_init($url); 201 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 202 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 203 | if (!empty($options)) { 204 | curl_setopt_array($ch, $options); 205 | } 206 | //https请求 不验证证书和host 207 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 208 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 209 | $data = curl_exec($ch); 210 | curl_close($ch); 211 | return $data; 212 | } 213 | 214 | public static function curlPost($url = '', $postData = '', $options = array()) 215 | { 216 | if (is_array($postData)) { 217 | $postData = http_build_query($postData); 218 | } 219 | $ch = curl_init(); 220 | curl_setopt($ch, CURLOPT_URL, $url); 221 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 222 | curl_setopt($ch, CURLOPT_POST, 1); 223 | curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); 224 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数 225 | if (!empty($options)) { 226 | curl_setopt_array($ch, $options); 227 | } 228 | //https请求 不验证证书和host 229 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 230 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 231 | $data = curl_exec($ch); 232 | curl_close($ch); 233 | return $data; 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /custom_msg.php: -------------------------------------------------------------------------------- 1 | 开发->基本配置->AppID 6 | $appsecret = ''; //微信公众平台->开发->基本配置->AppSecret 7 | $openid=''; //接收消息的用户的openid,如果留空,则默认自动获取当前用户的openid 8 | $msgType = 'text'; //发送消息的类型 文本:text ,图片:image 9 | $msg = '你好'; //如果类型是text,这里填写文本消息内容。如果是image,这里填写素材id(media_id) 10 | $imgPath = ''; //如果类型是image且素材id为空,则填写图片的绝对路径,例如:D:/www/1.jpg 11 | /* 配置结束 */ 12 | $wx = new WxService($appid,$appsecret); 13 | if(!$openid){ 14 | $openid = $wx->GetOpenid(); //获取openid 15 | if(!$openid) exit('获取openid失败'); 16 | } 17 | if($msgType=='text' && !$msg){ 18 | exit('发送消息内容不能为空!'); 19 | } 20 | if($msgType=='image' && !$msg){ 21 | //发送图片 22 | if(!$msg){ 23 | if(!$imgPath) exit('图片路径不能为空!'); 24 | $msg = $wx->uploadMedia($imgPath); 25 | } 26 | } 27 | $result = $wx->postMsg($openid,$msg,$msgType); 28 | if($result['errcode']==0){ 29 | echo '

消息发送成功!

'; 30 | }else{ 31 | echo '

消息发送失败:'.$result['errmsg'].'

'; 32 | } 33 | class WxService 34 | { 35 | protected $appid; 36 | protected $appsecret; 37 | protected $token = null; 38 | public function __construct($appid, $appsecret) 39 | { 40 | $this->appid = $appid; 41 | $this->appsecret = $appsecret; 42 | $this->token = $this->getToken(); 43 | } 44 | 45 | function getToken($force=false) { 46 | if($force===false && $this->token) return $this->token; 47 | $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$this->appid.'&secret='.$this->appsecret; 48 | $res = self::curlGet($url); 49 | $result = json_decode($res, true); 50 | if($result['errmsg']){ 51 | exit($res); 52 | } 53 | //access_token有效期是7200s 54 | return $result['access_token']; 55 | } 56 | 57 | /** 58 | * 通过跳转获取用户的openid,跳转流程如下: 59 | * 1、设置自己需要调回的url及其其他参数,跳转到微信服务器https://open.weixin.qq.com/connect/oauth2/authorize 60 | * 2、微信服务处理完成之后会跳转回用户redirect_uri地址,此时会带上一些参数,如:code 61 | * @return 用户的openid 62 | */ 63 | public function GetOpenid() 64 | { 65 | //通过code获得openid 66 | if (!isset($_GET['code'])){ 67 | //触发微信返回code码 68 | $baseUrl = $this->getCurrentUrl(); 69 | $url = $this->__CreateOauthUrlForCode($baseUrl); 70 | Header("Location: $url"); 71 | exit(); 72 | } else { 73 | //获取code码,以获取openid 74 | $code = $_GET['code']; 75 | $openid = $this->getOpenidFromMp($code); 76 | return $openid; 77 | } 78 | } 79 | 80 | public function getCurrentUrl() 81 | { 82 | $scheme = $_SERVER['HTTPS']=='on' ? 'https://' : 'http://'; 83 | $uri = $_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']; 84 | if($_SERVER['REQUEST_URI']) $uri = $_SERVER['REQUEST_URI']; 85 | $baseUrl = urlencode($scheme.$_SERVER['HTTP_HOST'].$uri); 86 | return $baseUrl; 87 | } 88 | /** 89 | * 通过code从工作平台获取openid机器access_token 90 | * @param string $code 微信跳转回来带上的code 91 | * @return openid 92 | */ 93 | public function GetOpenidFromMp($code) 94 | { 95 | $url = $this->__CreateOauthUrlForOpenid($code); 96 | $res = self::curlGet($url); 97 | //取出openid 98 | $data = json_decode($res,true); 99 | $this->data = $data; 100 | $openid = $data['openid']; 101 | return $openid; 102 | } 103 | /** 104 | * 构造获取open和access_toke的url地址 105 | * @param string $code,微信跳转带回的code 106 | * @return 请求的url 107 | */ 108 | private function __CreateOauthUrlForOpenid($code) 109 | { 110 | $urlObj["appid"] = $this->appid; 111 | $urlObj["secret"] = $this->appsecret; 112 | $urlObj["code"] = $code; 113 | $urlObj["grant_type"] = "authorization_code"; 114 | $bizString = $this->ToUrlParams($urlObj); 115 | return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString; 116 | } 117 | /** 118 | * 构造获取code的url连接 119 | * @param string $redirectUrl 微信服务器回跳的url,需要url编码 120 | * @return 返回构造好的url 121 | */ 122 | private function __CreateOauthUrlForCode($redirectUrl) 123 | { 124 | $urlObj["appid"] = $this->appid; 125 | $urlObj["redirect_uri"] = "$redirectUrl"; 126 | $urlObj["response_type"] = "code"; 127 | $urlObj["scope"] = "snsapi_base"; 128 | $urlObj["state"] = "STATE"."#wechat_redirect"; 129 | $bizString = $this->ToUrlParams($urlObj); 130 | return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString; 131 | } 132 | /** 133 | * 拼接签名字符串 134 | * @param array $urlObj 135 | * @return 返回已经拼接好的字符串 136 | */ 137 | private function ToUrlParams($urlObj) 138 | { 139 | $buff = ""; 140 | foreach ($urlObj as $k => $v) 141 | { 142 | if($k != "sign") $buff .= $k . "=" . $v . "&"; 143 | } 144 | $buff = trim($buff, "&"); 145 | return $buff; 146 | } 147 | 148 | function postMsg($openid,$msg,$type='text') 149 | { 150 | $url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token='.$this->token; 151 | $data['touser'] = $openid; 152 | $data['msgtype'] = $type; 153 | if($type=='text') $data['text']['content'] = $msg; 154 | else $data['image']['media_id'] = $msg; 155 | $result = self::curlPost($url,self::xjson_encode($data)); 156 | $msg = json_decode($result,true); 157 | return $msg; 158 | } 159 | 160 | /** 161 | * 上传临时素材 162 | * @param $filepath 图片路径 163 | * @param $type 图片(image)、语音(voice)、视频(video)和缩略图(thumb) 164 | * @return 素材id 165 | */ 166 | function uploadMedia($filepath,$type='image') 167 | { 168 | $url = 'https://api.weixin.qq.com/cgi-bin/media/upload?access_token='.$this->getToken().'&type='.$type; 169 | if(version_compare(phpversion(),'5.5.0') >= 0 && class_exists('\CURLFile')){ 170 | $data = array('media' => new \CURLFile($filepath)); 171 | } else { 172 | $data = array('media'=>'@'.$filepath); 173 | } 174 | $result = self::curlPost($url,$data); 175 | $resultArr = json_decode($result,true); 176 | if($resultArr['errcode']){ 177 | exit($result); 178 | } 179 | return $resultArr['media_id']; 180 | } 181 | 182 | public static function curlGet($url = '', $options = array()) 183 | { 184 | $ch = curl_init($url); 185 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 186 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 187 | if (!empty($options)) { 188 | curl_setopt_array($ch, $options); 189 | } 190 | //https请求 不验证证书和host 191 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 192 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 193 | $data = curl_exec($ch); 194 | curl_close($ch); 195 | return $data; 196 | } 197 | public static function curlPost($url = '', $postData = '', $options = array()) 198 | { 199 | $ch = curl_init(); 200 | curl_setopt($ch, CURLOPT_URL, $url); 201 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 202 | curl_setopt($ch, CURLOPT_POST, 1); 203 | curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); 204 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数 205 | if (!empty($options)) { 206 | curl_setopt_array($ch, $options); 207 | } 208 | //https请求 不验证证书和host 209 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 210 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 211 | $data = curl_exec($ch); 212 | if($data === false) 213 | { 214 | echo 'Curl error: ' . curl_error($ch);exit(); 215 | } 216 | curl_close($ch); 217 | return $data; 218 | } 219 | 220 | private function replace($matchs){ 221 | return iconv('UCS-2BE', 'UTF-8', pack('H4', $matchs[1])); 222 | } 223 | 224 | public static function xjson_encode($data) 225 | { 226 | if(version_compare(PHP_VERSION,'5.4.0','<')){ 227 | $str = json_encode($data); 228 | $str = preg_replace_callback("#\\\u([0-9a-f]{4})#i", 'self::replace',$str); 229 | return $str; 230 | } 231 | return json_encode($data, JSON_UNESCAPED_UNICODE); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /message.php: -------------------------------------------------------------------------------- 1 | decryptMsg($msgSignature, $timestamp, $nonce, $xml, $msg); 15 | if ($errCode == 0) { 16 | if($msg->Event=='subscribe'){ 17 | //关注 18 | $toUser = $msg->FromUserName; 19 | $fromUser = $msg->ToUserName; 20 | $respondMsg = ''.time().''; 21 | echo $respondMsg; 22 | exit(); 23 | } 24 | if($msg->Event=='unsubscribe'){ 25 | //取消关注 26 | $toUser = $msg->FromUserName; //取消关注者的openid 27 | } 28 | } else { 29 | error_log("解密失败:".$errCode, 3, "log.txt"); 30 | } 31 | class WXBizMsgCrypt 32 | { 33 | private $token; 34 | private $encodingAesKey; 35 | private $appId; 36 | 37 | /** 38 | * 构造函数 39 | * @param $token string 公众平台上,开发者设置的token 40 | * @param $encodingAesKey string 公众平台上,开发者设置的EncodingAESKey 41 | * @param $appId string 公众平台的appId 42 | */ 43 | public function WXBizMsgCrypt($token, $encodingAesKey, $appId) 44 | { 45 | $this->token = $token; 46 | $this->encodingAesKey = $encodingAesKey; 47 | $this->appId = $appId; 48 | } 49 | 50 | /** 51 | * 将公众平台回复用户的消息加密打包. 52 | *
    53 | *
  1. 对要发送的消息进行AES-CBC加密
  2. 54 | *
  3. 生成安全签名
  4. 55 | *
  5. 将消息密文和安全签名打包成xml格式
  6. 56 | *
57 | * 58 | * @param $replyMsg string 公众平台待回复用户的消息,xml格式的字符串 59 | * @param $timeStamp string 时间戳,可以自己生成,也可以用URL参数的timestamp 60 | * @param $nonce string 随机串,可以自己生成,也可以用URL参数的nonce 61 | * @param &$encryptMsg string 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串, 62 | * 当return返回0时有效 63 | * 64 | * @return int 成功0,失败返回对应的错误码 65 | */ 66 | public function encryptMsg($replyMsg, $timeStamp, $nonce, &$encryptMsg) 67 | { 68 | $pc = new Prpcrypt($this->encodingAesKey); 69 | 70 | //加密 71 | $array = $pc->encrypt($replyMsg, $this->appId); 72 | $ret = $array[0]; 73 | if ($ret != 0) { 74 | return $ret; 75 | } 76 | 77 | if ($timeStamp == null) { 78 | $timeStamp = time(); 79 | } 80 | $encrypt = $array[1]; 81 | 82 | //生成安全签名 83 | $sha1 = new SHA1; 84 | $array = $sha1->getSHA1($this->token, $timeStamp, $nonce, $encrypt); 85 | $ret = $array[0]; 86 | if ($ret != 0) { 87 | return $ret; 88 | } 89 | $signature = $array[1]; 90 | 91 | //生成发送的xml 92 | $xmlparse = new XMLParse; 93 | $encryptMsg = $xmlparse->generate($encrypt, $signature, $timeStamp, $nonce); 94 | return ErrorCode::$OK; 95 | } 96 | 97 | 98 | /** 99 | * 检验消息的真实性,并且获取解密后的明文. 100 | *
    101 | *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. 102 | *
  3. 若验证通过,则提取xml中的加密消息
  4. 103 | *
  5. 对消息进行解密
  6. 104 | *
105 | * 106 | * @param $msgSignature string 签名串,对应URL参数的msg_signature 107 | * @param $timestamp string 时间戳 对应URL参数的timestamp 108 | * @param $nonce string 随机串,对应URL参数的nonce 109 | * @param $postData string 密文,对应POST请求的数据 110 | * @param &$msg string 解密后的原文,当return返回0时有效 111 | * 112 | * @return int 成功0,失败返回对应的错误码 113 | */ 114 | public function decryptMsg($msgSignature, $timestamp = null, $nonce, $postData, &$msg) 115 | { 116 | if (strlen($this->encodingAesKey) != 43) { 117 | return ErrorCode::$IllegalAesKey; 118 | } 119 | 120 | $pc = new Prpcrypt($this->encodingAesKey); 121 | 122 | //提取密文 123 | $xmlparse = new XMLParse; 124 | $array = $xmlparse->extract($postData); 125 | 126 | $ret = $array[0]; 127 | 128 | if ($ret != 0) { 129 | return $ret; 130 | } 131 | 132 | if ($timestamp == null) { 133 | $timestamp = time(); 134 | } 135 | 136 | $encrypt = $array[1]; 137 | $touser_name = $array[2]; 138 | 139 | //验证安全签名 140 | $sha1 = new SHA1; 141 | $array = $sha1->getSHA1($this->token, $timestamp, $nonce, $encrypt); 142 | 143 | $ret = $array[0]; 144 | 145 | if ($ret != 0) { 146 | return $ret; 147 | } 148 | 149 | $signature = $array[1]; 150 | if ($signature != $msgSignature) { 151 | return ErrorCode::$ValidateSignatureError; 152 | } 153 | 154 | $result = $pc->decrypt($encrypt, $this->appId); 155 | if ($result[0] != 0) { 156 | return $result[0]; 157 | } 158 | $msg = simplexml_load_string($result[1], 'SimpleXMLElement', LIBXML_NOCDATA); 159 | 160 | return ErrorCode::$OK; 161 | } 162 | 163 | } 164 | 165 | class ErrorCode 166 | { 167 | public static $OK = 0; 168 | public static $ValidateSignatureError = -40001; 169 | public static $ParseXmlError = -40002; 170 | public static $ComputeSignatureError = -40003; 171 | public static $IllegalAesKey = -40004; 172 | public static $ValidateAppidError = -40005; 173 | public static $EncryptAESError = -40006; 174 | public static $DecryptAESError = -40007; 175 | public static $IllegalBuffer = -40008; 176 | public static $EncodeBase64Error = -40009; 177 | public static $DecodeBase64Error = -40010; 178 | public static $GenReturnXmlError = -40011; 179 | } 180 | 181 | /** 182 | * PKCS7Encoder class 183 | * 184 | * 提供基于PKCS7算法的加解密接口. 185 | */ 186 | class PKCS7Encoder 187 | { 188 | public static $block_size = 32; 189 | 190 | /** 191 | * 对需要加密的明文进行填充补位 192 | * @param $text 需要进行填充补位操作的明文 193 | * @return 补齐明文字符串 194 | */ 195 | function encode($text) 196 | { 197 | $block_size = PKCS7Encoder::$block_size; 198 | $text_length = strlen($text); 199 | //计算需要填充的位数 200 | $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size); 201 | if ($amount_to_pad == 0) { 202 | $amount_to_pad = PKCS7Encoder::block_size; 203 | } 204 | //获得补位所用的字符 205 | $pad_chr = chr($amount_to_pad); 206 | $tmp = ""; 207 | for ($index = 0; $index < $amount_to_pad; $index++) { 208 | $tmp .= $pad_chr; 209 | } 210 | return $text . $tmp; 211 | } 212 | 213 | /** 214 | * 对解密后的明文进行补位删除 215 | * @param decrypted 解密后的明文 216 | * @return 删除填充补位后的明文 217 | */ 218 | function decode($text) 219 | { 220 | 221 | $pad = ord(substr($text, -1)); 222 | if ($pad < 1 || $pad > 32) { 223 | $pad = 0; 224 | } 225 | return substr($text, 0, (strlen($text) - $pad)); 226 | } 227 | 228 | } 229 | 230 | /** 231 | * Prpcrypt class 232 | * 233 | * 提供接收和推送给公众平台消息的加解密接口. 234 | */ 235 | class Prpcrypt 236 | { 237 | public $key; 238 | 239 | function Prpcrypt($k) 240 | { 241 | $this->key = base64_decode($k . "="); 242 | } 243 | 244 | /** 245 | * 对明文进行加密 246 | * @param string $text 需要加密的明文 247 | * @return string 加密后的密文 248 | */ 249 | public function encrypt($text, $appid) 250 | { 251 | 252 | try { 253 | //获得16位随机字符串,填充到明文之前 254 | $random = $this->getRandomStr(); 255 | $text = $random . pack("N", strlen($text)) . $text . $appid; 256 | // 网络字节序 257 | $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); 258 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 259 | $iv = substr($this->key, 0, 16); 260 | //使用自定义的填充方式对明文进行补位填充 261 | $pkc_encoder = new PKCS7Encoder; 262 | $text = $pkc_encoder->encode($text); 263 | mcrypt_generic_init($module, $this->key, $iv); 264 | //加密 265 | $encrypted = mcrypt_generic($module, $text); 266 | mcrypt_generic_deinit($module); 267 | mcrypt_module_close($module); 268 | 269 | //print(base64_encode($encrypted)); 270 | //使用BASE64对加密后的字符串进行编码 271 | return array(ErrorCode::$OK, base64_encode($encrypted)); 272 | } catch (Exception $e) { 273 | //print $e; 274 | return array(ErrorCode::$EncryptAESError, null); 275 | } 276 | } 277 | 278 | /** 279 | * 对密文进行解密 280 | * @param string $encrypted 需要解密的密文 281 | * @return string 解密得到的明文 282 | */ 283 | public function decrypt($encrypted, $appid) 284 | { 285 | 286 | try { 287 | //使用BASE64对需要解密的字符串进行解码 288 | $ciphertext_dec = base64_decode($encrypted); 289 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 290 | $iv = substr($this->key, 0, 16); 291 | mcrypt_generic_init($module, $this->key, $iv); 292 | 293 | //解密 294 | $decrypted = mdecrypt_generic($module, $ciphertext_dec); 295 | mcrypt_generic_deinit($module); 296 | mcrypt_module_close($module); 297 | } catch (Exception $e) { 298 | return array(ErrorCode::$DecryptAESError, null); 299 | } 300 | 301 | 302 | try { 303 | //去除补位字符 304 | $pkc_encoder = new PKCS7Encoder; 305 | $result = $pkc_encoder->decode($decrypted); 306 | //去除16位随机字符串,网络字节序和AppId 307 | if (strlen($result) < 16) 308 | return ""; 309 | $content = substr($result, 16, strlen($result)); 310 | $len_list = unpack("N", substr($content, 0, 4)); 311 | $xml_len = $len_list[1]; 312 | $xml_content = substr($content, 4, $xml_len); 313 | $from_appid = substr($content, $xml_len + 4); 314 | } catch (Exception $e) { 315 | //print $e; 316 | return array(ErrorCode::$IllegalBuffer, null); 317 | } 318 | if ($from_appid != $appid) 319 | return array(ErrorCode::$ValidateAppidError, null); 320 | return array(0, $xml_content); 321 | 322 | } 323 | 324 | 325 | /** 326 | * 随机生成16位字符串 327 | * @return string 生成的字符串 328 | */ 329 | function getRandomStr() 330 | { 331 | 332 | $str = ""; 333 | $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; 334 | $max = strlen($str_pol) - 1; 335 | for ($i = 0; $i < 16; $i++) { 336 | $str .= $str_pol[mt_rand(0, $max)]; 337 | } 338 | return $str; 339 | } 340 | 341 | } 342 | 343 | /** 344 | * SHA1 class 345 | * 346 | * 计算公众平台的消息签名接口. 347 | */ 348 | class SHA1 349 | { 350 | /** 351 | * 用SHA1算法生成安全签名 352 | * @param string $token 票据 353 | * @param string $timestamp 时间戳 354 | * @param string $nonce 随机字符串 355 | * @param string $encrypt 密文消息 356 | */ 357 | public function getSHA1($token, $timestamp, $nonce, $encrypt_msg) 358 | { 359 | //排序 360 | try { 361 | $array = array($encrypt_msg, $token, $timestamp, $nonce); 362 | sort($array, SORT_STRING); 363 | $str = implode($array); 364 | return array(ErrorCode::$OK, sha1($str)); 365 | } catch (Exception $e) { 366 | //print $e . "\n"; 367 | return array(ErrorCode::$ComputeSignatureError, null); 368 | } 369 | } 370 | 371 | } 372 | 373 | /** 374 | * XMLParse class 375 | * 376 | * 提供提取消息格式中的密文及生成回复消息格式的接口. 377 | */ 378 | class XMLParse 379 | { 380 | 381 | /** 382 | * 提取出xml数据包中的加密消息 383 | * @param string $xmltext 待提取的xml字符串 384 | * @return string 提取出的加密消息字符串 385 | */ 386 | public function extract($xmltext) 387 | { 388 | libxml_disable_entity_loader(true); 389 | try { 390 | $xml = new DOMDocument(); 391 | $xml->loadXML($xmltext); 392 | $array_e = $xml->getElementsByTagName('Encrypt'); 393 | $array_a = $xml->getElementsByTagName('ToUserName'); 394 | $encrypt = $array_e->item(0)->nodeValue; 395 | $tousername = $array_a->item(0)->nodeValue; 396 | return array(0, $encrypt, $tousername); 397 | } catch (Exception $e) { 398 | //print $e . "\n"; 399 | return array(ErrorCode::$ParseXmlError, null, null); 400 | } 401 | } 402 | 403 | /** 404 | * 生成xml消息 405 | * @param string $encrypt 加密后的消息密文 406 | * @param string $signature 安全签名 407 | * @param string $timestamp 时间戳 408 | * @param string $nonce 随机字符串 409 | */ 410 | public function generate($encrypt, $signature, $timestamp, $nonce) 411 | { 412 | $format = " 413 | 414 | 415 | %s 416 | 417 | "; 418 | return sprintf($format, $encrypt, $signature, $timestamp, $nonce); 419 | } 420 | 421 | } --------------------------------------------------------------------------------