├── 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 | | openid |
34 | =$user['openid']?> |
35 |
36 |
37 | | unionid |
38 | =$user['unionid']?> |
39 |
40 |
41 | | 昵称 |
42 | =$user['nickname']?> |
43 |
44 |
45 | | 头像 |
46 |  |
47 |
48 |
49 | | 性别 |
50 | |
63 |
64 |
65 | | 省份 / 城市 |
66 | =$user['province'].' / '.$user['city']?> |
67 |
68 |
69 | | language |
70 | =$user['language']?> |
71 |
72 |
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 | * - 对要发送的消息进行AES-CBC加密
54 | * - 生成安全签名
55 | * - 将消息密文和安全签名打包成xml格式
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 | * - 利用收到的密文生成安全签名,进行签名验证
102 | * - 若验证通过,则提取xml中的加密消息
103 | * - 对消息进行解密
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 | }
--------------------------------------------------------------------------------