├── README.md ├── composer.json ├── WeixinVideo ├── User.php ├── Oauth.php ├── Kernel │ ├── DataArray.php │ └── BaseApi.php └── Video.php ├── ApiExcepion.php └── Weixin.php /README.md: -------------------------------------------------------------------------------- 1 | # weixin-video-sdk 2 | 微信视频号SDK 3 | 4 | 代码维护:汪登科(9653992@qq.com) 5 | 本代码不能用于任何商业项目,只允许用于个人学习研究。 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phpwdk/weixin-video-sdk", 3 | "description": "微信视频号SDK", 4 | "authors": [ 5 | { 6 | "name": "汪登科", 7 | "email": "9653992@qq.com" 8 | } 9 | ], 10 | "require": { 11 | "php": ">=5.6" 12 | }, 13 | "autoload": { 14 | "classmap": [ 15 | "Weixin.php" 16 | ], 17 | "psr-4": { 18 | "WeixinVideo\\": "WeixinVideo/" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /WeixinVideo/User.php: -------------------------------------------------------------------------------- 1 | $finderUsername, 19 | 'rawKeyBuff' => null, 20 | '_log_finder_id' => null, 21 | 'timestamp' => (string)$this->getMillisecond(), 22 | ]; 23 | 24 | $header = [ 25 | 'Accept:application/json', 'Content-Type:application/json', 'Cookie:' . $cookie 26 | ]; 27 | $result = $this->https_request($api_url, json_encode($params), $header); 28 | return json_decode($result, true); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ApiExcepion.php: -------------------------------------------------------------------------------- 1 | responseHeaders = $responseHeaders; 16 | $this->responseBody = $responseBody; 17 | } 18 | 19 | public function getResponseHeaders() 20 | { 21 | return $this->responseHeaders; 22 | } 23 | 24 | public function getResponseBody() 25 | { 26 | return $this->responseBody; 27 | } 28 | 29 | public function setResponseObject($obj) 30 | { 31 | $this->responseObject = $obj; 32 | } 33 | 34 | public function getResponseObject() 35 | { 36 | return $this->responseObject; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /WeixinVideo/Oauth.php: -------------------------------------------------------------------------------- 1 | https_post($api_url); 13 | } 14 | 15 | public function login_status($token) 16 | { 17 | $api_url = 'https://channels.weixin.qq.com/cgi-bin/mmfinderassistant-bin/auth/auth_login_status'; 18 | $params = [ 19 | 'token' => $token, 20 | 'timestamp' => (string)$this->getMillisecond() 21 | ]; 22 | $post = [ 23 | 'rawKeyBuff' => null, 24 | 'token' => $token, 25 | '_log_finder_id' => null, 26 | 'timestamp' => (string)$this->getMillisecond(), 27 | ]; 28 | 29 | $api_url .= '?' . http_build_query($params); 30 | 31 | return $this->https_post($api_url, $post, false, true); 32 | } 33 | 34 | public function set_oauth($params) 35 | { 36 | if (empty($params['md5file']) && !$this->remote_file_exists($params['video'])) 37 | return ['code' => 0, 'info' => '视频文件不存在']; 38 | 39 | $api_url = self::BASE_API . '/weixin/oauth/set_oauth'; 40 | 41 | return $this->https_post($api_url, $params); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Weixin.php: -------------------------------------------------------------------------------- 1 | get(); 35 | } 36 | return []; 37 | } 38 | 39 | /** 40 | * 静态魔术加载方法 41 | * @param $name 42 | * @param $arguments 43 | * @return mixed 44 | */ 45 | public static function __callStatic($name , $arguments) 46 | { 47 | $name = ucfirst(strtolower($name)); 48 | $class = "\\WeixinVideo\\{$name}"; 49 | 50 | if (!empty($class) && class_exists($class)) { 51 | $option = array_shift($arguments); 52 | $config = is_array($option) ? $option : self::$config->get(); 53 | return new $class($config); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /WeixinVideo/Kernel/DataArray.php: -------------------------------------------------------------------------------- 1 | config = $options; 25 | } 26 | 27 | /** 28 | * 设置配置项值 29 | * @param string $offset 30 | * @param string|array|null|integer $value 31 | */ 32 | public function set($offset, $value) 33 | { 34 | $this->offsetSet($offset, $value); 35 | } 36 | 37 | /** 38 | * 获取配置项参数 39 | * @param string|null $offset 40 | * @return array|string|null 41 | */ 42 | public function get($offset = null) 43 | { 44 | return $this->offsetGet($offset); 45 | } 46 | 47 | /** 48 | * 合并数据到对象 49 | * @param array $data 需要合并的数据 50 | * @param bool $append 是否追加数据 51 | * @return array 52 | */ 53 | public function merge(array $data, $append = false) 54 | { 55 | if ($append) { 56 | return $this->config = array_merge($this->config, $data); 57 | } 58 | return array_merge($this->config, $data); 59 | } 60 | 61 | /** 62 | * 设置配置项值 63 | * @param string $offset 64 | * @param string|array|null|integer $value 65 | */ 66 | public function offsetSet($offset, $value) 67 | { 68 | if (is_null($offset)) { 69 | $this->config[] = $value; 70 | } else { 71 | $this->config[$offset] = $value; 72 | } 73 | } 74 | 75 | /** 76 | * 判断配置Key是否存在 77 | * @param string $offset 78 | * @return bool 79 | */ 80 | public function offsetExists($offset) 81 | { 82 | return isset($this->config[$offset]); 83 | } 84 | 85 | /** 86 | * 清理配置项 87 | * @param string|null $offset 88 | */ 89 | public function offsetUnset($offset = null) 90 | { 91 | if (is_null($offset)) { 92 | $this->config = []; 93 | } else { 94 | unset($this->config[$offset]); 95 | } 96 | } 97 | 98 | /** 99 | * 获取配置项参数 100 | * @param string|null $offset 101 | * @return array|string|null 102 | */ 103 | public function offsetGet($offset = null) 104 | { 105 | if (is_null($offset)) { 106 | return $this->config; 107 | } 108 | return isset($this->config[$offset]) ? $this->config[$offset] : null; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /WeixinVideo/Video.php: -------------------------------------------------------------------------------- 1 | https_post($api_url, $params); 18 | } 19 | 20 | /** 21 | * @title 上传 22 | */ 23 | public function sns_upload_big($params) 24 | { 25 | $api_url = self::BASE_API . '/weixin/upload/sns_upload_big'; 26 | 27 | return $this->https_post($api_url, $params); 28 | } 29 | 30 | /** 31 | * @title 发布 32 | */ 33 | public function post_create_local($params) 34 | { 35 | $api_url = self::BASE_API . '/weixin/upload/set_post_create_local'; 36 | 37 | return $this->https_post($api_url, $params); 38 | } 39 | 40 | /** 41 | * @title 凭证 42 | */ 43 | public function helper_upload($params) 44 | { 45 | $api_url = self::BASE_API . '/weixin/upload/helper_upload'; 46 | 47 | return $this->https_post($api_url, $params); 48 | } 49 | 50 | /** 51 | * @title 本地上传 52 | */ 53 | public function upload($url, $params) 54 | { 55 | // 准备分块上传 56 | if (!$this->remote_file_exists($url)) 57 | return ['code' => 0, 'info' => '文件不存在,请检查配置']; 58 | $stream = @file_get_contents($url); 59 | if (!$stream || !$file_size = strlen($stream)) 60 | return ['code' => 0, 'info' => '文件不能访问']; 61 | // 缓存临时文件 62 | $this->mkdirs($params['temp_path']); 63 | $params['fileuuid'] = $this->getUUID(); 64 | 65 | $upload_params = $this->helper_upload([ 66 | 'token' => $params['token'], 'raw_key_buff' => $params['raw_key_buff'], 'log_finder_id' => $params['log_finder_id'] 67 | ]); 68 | if ($upload_params['errCode'] != 0) 69 | return ['code' => 0, 'info' => '登陆超时']; 70 | $authkey = $upload_params['data']['authKey']; 71 | $weixinnum = $upload_params['data']['uin']; 72 | 73 | // 准备上传 74 | if ($file_size > $this->packet && $block_num = ceil($file_size / $this->packet)) { 75 | // 写入缓存 76 | $file_temp = $params['temp_path'] . $params['token']; 77 | @file_put_contents($file_temp, $stream); 78 | // 文件分块数据流 79 | $file_packet = []; 80 | $handle = fopen($file_temp, 'r'); 81 | while (false != ($content = fread($handle, $this->packet))) { 82 | $file_packet[] = $content; 83 | } 84 | fclose($handle); 85 | // 删除缓存 86 | unlink($file_temp); 87 | 88 | for ($i = 0; $i < $block_num; $i++) { 89 | $start = $i * $this->packet; 90 | $end = $start + $this->packet; 91 | $end = ($end > $file_size) ? $file_size : $end; 92 | $md5file_stream = md5($file_packet[$i]); 93 | $block_stream = [ 94 | 'start' => $start, 95 | 'end' => $end - 1, 96 | 'stream' => $file_packet[$i], 97 | 'md5' => $md5file_stream, 98 | 'fileuuid' => $params['fileuuid'], 99 | 'filesize' => $file_size, 100 | 'filetype' => $params['filetype'], 101 | 'weixinnum' => $weixinnum 102 | ]; 103 | $result = $this->send_file($authkey, $block_stream, $params['filename']); 104 | if ($result['code' === 0]) phpwdk_error($result['info']); 105 | $content = json_decode($result['content'], true); 106 | if ($content['code'] === 0) { 107 | $idx = 0; 108 | while (true) { 109 | if ($idx === 2) return ['code' => 0, 'info' => $i]; 110 | $result = $this->send_file($authkey, $block_stream, $params['filename']); 111 | $content = json_decode($result['content'], true); 112 | if ($content['retcode'] === 0) break; 113 | $idx++; 114 | } 115 | } 116 | usleep(800000); 117 | } 118 | } else { 119 | $block_stream = [ 120 | 'start' => 0, 121 | 'end' => $file_size - 1, 122 | 'stream' => $stream, 123 | 'md5' => $params['md5file'], 124 | 'fileuuid' => $params['fileuuid'], 125 | 'filesize' => $file_size, 126 | 'filetype' => $params['filetype'], 127 | 'weixinnum' => $weixinnum, 128 | ]; 129 | $result = $this->send_file($authkey, $block_stream, $params['filename']); 130 | $content = json_decode($result['content'], true); 131 | if ($content['code'] === 0) return $result; 132 | } 133 | if ($content['retcode'] === 0 && isset($content['fileurl'])) return ['code' => 1, 'fileurl' => $content['fileurl'], 'filesize' => $file_size]; 134 | else return ['code' => 0, 'info' => '上传文件失败']; 135 | } 136 | 137 | /** 138 | * @title 多线程本地上传 139 | */ 140 | public function upload_multi($url, $params) 141 | { 142 | $result = null; 143 | // 准备分块上传 144 | if (!$this->remote_file_exists($url)) 145 | return ['code' => 0, 'info' => '文件不存在,请检查配置']; 146 | $stream = @file_get_contents($url); 147 | if (!$stream || !$file_size = strlen($stream)) 148 | return ['code' => 0, 'info' => '文件不能访问']; 149 | // 缓存临时文件 150 | $this->mkdirs($params['temp_path']); 151 | $params['fileuuid'] = $this->getUUID(); 152 | 153 | $upload_params = $this->helper_upload([ 154 | 'token' => $params['token'], 'raw_key_buff' => $params['raw_key_buff'], 'log_finder_id' => $params['log_finder_id'] 155 | ]); 156 | if ($upload_params['code'] != 1) 157 | return ['code' => 0, 'info' => '登陆超时']; 158 | $authkey = $upload_params['data']['authKey']; 159 | $weixinnum = $upload_params['data']['uin']; 160 | 161 | if ($file_size > $this->packet && $block_num = intval(ceil($file_size / $this->packet))) { 162 | // 写入缓存 163 | $file_temp = $params['temp_path'] . $params['token']; 164 | @file_put_contents($file_temp, $stream); 165 | // 文件分块数据流 166 | $file_packet = []; 167 | $handle = fopen($file_temp, 'r'); 168 | while (false != ($content = fread($handle, $this->packet))) { 169 | $file_packet[] = $content; 170 | } 171 | fclose($handle); 172 | // 删除缓存 173 | unlink($file_temp); 174 | 175 | $mh = curl_multi_init(); 176 | $this->curl = null; 177 | $index = 0; 178 | $index_key = 0; 179 | $end_num = intval($block_num - 1); 180 | for ($i = 0; $i < $block_num; $i += $this->task_count) { 181 | $index_key = $i; 182 | for ($i2 = 0; $i2 < $this->task_count; $i2++) { 183 | $start = $index * $this->packet; 184 | $end = $start + $this->packet; 185 | $end = ($end > $file_size) ? $file_size : $end; 186 | $md5file_stream = md5($file_packet[$index]); 187 | $block_stream = [ 188 | 'start' => $start, 189 | 'end' => $end - 1, 190 | 'stream' => $file_packet[$index], 191 | 'md5' => $md5file_stream, 192 | 'fileuuid' => $params['fileuuid'], 193 | 'filesize' => $file_size, 194 | 'filetype' => $params['filetype'], 195 | 'weixinnum' => $weixinnum, 196 | ]; 197 | 198 | $this->send_file($authkey, $block_stream, $params['filename'], $index); 199 | curl_multi_add_handle($mh, $this->curl[$index]); 200 | $index++; 201 | if ($index === $block_num) break; 202 | } 203 | 204 | $key = 0; 205 | retry: 206 | if ($key === 3) break; 207 | $key++; 208 | sleep(3); 209 | $active = null; 210 | do { 211 | $mrc = curl_multi_exec($mh, $active); 212 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 213 | 214 | while ($active && $mrc == CURLM_OK) { 215 | if (curl_multi_select($mh) != -1) { 216 | do { 217 | $mrc = curl_multi_exec($mh, $active); 218 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 219 | } 220 | } 221 | 222 | $index = ($index >= $block_num) ? $block_num : $index; 223 | 224 | for ($index_key; $index_key < $index; $index_key++) { 225 | if (!isset($this->curl[$index_key])) continue; 226 | $output = curl_multi_getcontent($this->curl[$index_key]); 227 | var_dump([$output, $this->is_command, $index_key]); 228 | if (empty($output)) goto retry; 229 | true === $this->is_command || curl_close($this->curl[$index_key]); 230 | curl_multi_remove_handle($mh, $this->curl[$index_key]); 231 | if ($end_num === $index_key) $result = json_decode($output, true); 232 | } 233 | } 234 | 235 | if (true === $this->is_command) { 236 | for ($i = 0; $i < $index_key; $i++) { 237 | curl_close($this->curl[$i]); 238 | } 239 | } 240 | 241 | curl_multi_close($mh); 242 | } else { 243 | $block_stream = [ 244 | 'start' => 0, 245 | 'end' => $file_size - 1, 246 | 'stream' => $stream, 247 | 'md5' => $params['md5file'], 248 | 'fileuuid' => $params['fileuuid'], 249 | 'filesize' => $file_size, 250 | 'filetype' => $params['filetype'], 251 | 'weixinnum' => $weixinnum, 252 | ]; 253 | $result = $this->send_file($authkey, $block_stream, $params['filename']); 254 | if (isset($result['code']) && $result['code'] === 0) return $result; 255 | } 256 | 257 | if ($result['retcode'] === 0 && isset($result['fileurl'])) return ['code' => 1, 'fileurl' => $result['fileurl'], 'filesize' => $file_size]; 258 | else return ['code' => 0, 'info' => '上传文件失败']; 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /WeixinVideo/Kernel/BaseApi.php: -------------------------------------------------------------------------------- 1 | is_command = !empty($config['is_command']) ? !!$config['is_command'] : false; 26 | $this->client_authcode = $config['client_authcode']; 27 | } 28 | 29 | public function toArray() 30 | { 31 | return $this->response ? json_decode($this->response, true) : true; 32 | } 33 | 34 | public function https_get($url, $params = []) 35 | { 36 | $params['client_authcode'] = $this->client_authcode; 37 | if ($params) { 38 | $url = $url . '?' . http_build_query($params); 39 | } 40 | $this->response = $this->https_request($url); 41 | $result = json_decode($this->response, true); 42 | return $result['data']; 43 | } 44 | 45 | public function https_post($url, $data = [], $is_cloud = true, $is_header = false) 46 | { 47 | if ($is_cloud === true) { 48 | $data['client_authcode'] = $this->client_authcode; 49 | if (!isset($data['token'])) $data['token'] = !empty($_SESSION['video_token']) ? $_SESSION['video_token'] : ''; 50 | } 51 | $header = [ 52 | 'Accept:application/json', 'Content-Type:application/json' 53 | ]; 54 | $this->response = $this->https_request($url, json_encode($data), $header, $is_header); 55 | return false === $is_header ? json_decode($this->response, true) : $this->response; 56 | } 57 | 58 | public function https_request($url, $data = null, $headers = null, $is_header = false, $timeout = 60) 59 | { 60 | $curl = curl_init(); 61 | curl_setopt($curl, CURLOPT_URL, $url); 62 | if (!empty($data)) { 63 | curl_setopt($curl, CURLOPT_POST, 1); 64 | curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 65 | } 66 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); 67 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); 68 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 69 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20); 70 | curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); 71 | if ($is_header === true) curl_setopt($curl, CURLOPT_HEADER, 1); 72 | if (!empty($headers)) { 73 | curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 74 | } 75 | $output = curl_exec($curl); 76 | curl_close($curl); 77 | if ($is_header === true) $output = $this->ihttp_response_parse($output); 78 | 79 | return $output; 80 | } 81 | 82 | public function remote_file_exists($url_file) 83 | { 84 | $url_file = trim($url_file); 85 | if (empty($url_file)) return false; 86 | 87 | $url_arr = parse_url($url_file); 88 | if (!is_array($url_arr) || empty($url_arr)) return false; 89 | 90 | $file_headers = @get_headers($url_file); 91 | if ($file_headers[0] == 'HTTP/1.1 404 Not Found') return false; 92 | else return true; 93 | } 94 | 95 | public function send_file($authkey, $block_stream, $filename = '0.mp4', $index = null) 96 | { 97 | $params = [ 98 | 'seq' => $this->getMillisecond() . '.' . $this->random(4, true), 99 | 'weixinnum' => $block_stream['weixinnum'], 'apptype' => 251, 'filetype' => $block_stream['filetype'], 'authkey' => $authkey, 100 | 'filekey' => $filename, 'totalsize' => $block_stream['filesize'], 'fileuuid' => $block_stream['fileuuid'], 101 | 'rangestart' => $block_stream['start'], 'rangeend' => $block_stream['end'], 'blockmd5' => $block_stream['md5'], 102 | ]; 103 | 104 | $api_url = self::BASE_API_VIDEO . '/snsuploadbig'; 105 | return $this->https_byte($api_url, $params, $block_stream['stream'], $index); 106 | } 107 | 108 | public function https_byte($url, $options, $video_stream, $index = null) 109 | { 110 | $boundary = $this->random(16); 111 | $params = "------WebKitFormBoundary{$boundary}\r\n" 112 | . "Content-Disposition: form-data; name=\"ver\"\r\n" 113 | . "\r\n1\r\n" 114 | . "------WebKitFormBoundary{$boundary}\r\n" 115 | . "Content-Disposition: form-data; name=\"seq\"\r\n" 116 | . "\r\n" 117 | . $options['seq'] . "\r\n" 118 | . "------WebKitFormBoundary{$boundary}\r\n" 119 | . "Content-Disposition: form-data; name=\"weixinnum\"\r\n" 120 | . "\r\n" 121 | . $options['weixinnum'] . "\r\n" 122 | . "------WebKitFormBoundary{$boundary}\r\n" 123 | . "Content-Disposition: form-data; name=\"apptype\"\r\n" 124 | . "\r\n" 125 | . $options['apptype'] . "\r\n" 126 | . "------WebKitFormBoundary{$boundary}\r\n" 127 | . "Content-Disposition: form-data; name=\"filetype\"\r\n" 128 | . "\r\n" 129 | . $options['filetype'] . "\r\n" 130 | . "------WebKitFormBoundary{$boundary}\r\n" 131 | . "Content-Disposition: form-data; name=\"authkey\"\r\n" 132 | . "\r\n" 133 | . $options['authkey'] . "\r\n" 134 | . "------WebKitFormBoundary{$boundary}\r\n" 135 | . "Content-Disposition: form-data; name=\"hasthumb\"\r\n" 136 | . "\r\n0\r\n" 137 | . "------WebKitFormBoundary{$boundary}\r\n" 138 | . "Content-Disposition: form-data; name=\"filekey\"\r\n" 139 | . "\r\n" 140 | . $options['filekey'] . "\r\n" 141 | . "------WebKitFormBoundary{$boundary}\r\n" 142 | . "Content-Disposition: form-data; name=\"totalsize\"\r\n" 143 | . "\r\n" 144 | . $options['totalsize'] . "\r\n" 145 | . "------WebKitFormBoundary{$boundary}\r\n" 146 | . "Content-Disposition: form-data; name=\"fileuuid\"\r\n" 147 | . "\r\n" 148 | . $options['fileuuid'] . "\r\n" 149 | . "------WebKitFormBoundary{$boundary}\r\n" 150 | . "Content-Disposition: form-data; name=\"rangestart\"\r\n" 151 | . "\r\n" 152 | . $options['rangestart'] . "\r\n" 153 | . "------WebKitFormBoundary{$boundary}\r\n" 154 | . "Content-Disposition: form-data; name=\"rangeend\"\r\n" 155 | . "\r\n" 156 | . $options['rangeend'] . "\r\n" 157 | . "------WebKitFormBoundary{$boundary}\r\n" 158 | . "Content-Disposition: form-data; name=\"blockmd5\"\r\n" 159 | . "\r\n" 160 | . $options['blockmd5'] . "\r\n" 161 | . "------WebKitFormBoundary{$boundary}\r\n" 162 | . "Content-Disposition: form-data; name=\"forcetranscode\"\r\n" 163 | . "\r\n0\r\n" 164 | . "------WebKitFormBoundary{$boundary}\r\n" 165 | . "Content-Disposition: form-data; name=\"filedata\"; filename=\"blob\"\r\n" 166 | . "Content-Type: application/octet-stream\r\n" 167 | . "\r\n" 168 | . $video_stream . "\r\n" 169 | . "------WebKitFormBoundary{$boundary}--"; 170 | 171 | $first_newline = strpos($params, "\r\n"); 172 | $multipart_boundary = substr($params, 2, $first_newline - 2); 173 | $request_headers = array(); 174 | $request_headers[] = 'content-length: ' . strlen($params); 175 | $request_headers[] = 'content-type: multipart/form-data; boundary=' . $multipart_boundary; 176 | $request_headers[] = 'accept: application/json, text/plain, */*'; 177 | $request_headers[] = 'accept-encoding: gzip, deflate, br'; 178 | $request_headers[] = 'accept-language: zh-CN,zh;q=0.9'; 179 | $request_headers[] = 'origin: https://channels.weixin.qq.com'; 180 | $request_headers[] = 'referer: https://channels.weixin.qq.com/'; 181 | $request_headers[] = 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36'; 182 | 183 | if (function_exists('curl_init') && function_exists('curl_exec')) { 184 | if (!is_null($index)) { 185 | $this->curl[$index] = $this->doCurl($url, $params, $request_headers); 186 | return true; 187 | } else { 188 | $curl = $this->doCurl($url, $params, $request_headers); 189 | $output = curl_exec($curl); 190 | if (curl_errno($curl)) $error = curl_error($curl); 191 | true === $this->is_command || curl_close($curl); 192 | if (!empty($error)) return ['code' => 0, 'info' => $error]; 193 | 194 | return $output ? json_decode($output, true) : ['code' => 0, 'info' => '文件上传失败']; 195 | } 196 | } else return ['code' => 0, 'info' => '未安装CURL扩展']; 197 | } 198 | 199 | function ihttp_response_parse($data, $chunked = false) 200 | { 201 | $rlt = array(); 202 | 203 | $pos = strpos($data, "\r\n\r\n"); 204 | $split1[0] = substr($data, 0, $pos); 205 | $split1[1] = substr($data, $pos + 4, strlen($data)); 206 | 207 | $split2 = explode("\r\n", $split1[0], 2); 208 | preg_match('/^(\S+) (\S+) (.*)$/', $split2[0], $matches); 209 | $rlt['code'] = !empty($matches[2]) ? $matches[2] : 200; 210 | $rlt['status'] = !empty($matches[3]) ? $matches[3] : 'OK'; 211 | $rlt['responseline'] = !empty($split2[0]) ? $split2[0] : ''; 212 | $rlt['headers'] = []; 213 | $header = explode("\r\n", $split2[1]); 214 | $isgzip = false; 215 | $ischunk = false; 216 | foreach ($header as $v) { 217 | $pos = strpos($v, ':'); 218 | $key = substr($v, 0, $pos); 219 | $value = trim(substr($v, $pos + 1)); 220 | if (isset($rlt['headers'][$key]) && is_array($rlt['headers'][$key])) { 221 | $rlt['headers'][$key][] = $value; 222 | } elseif (!empty($rlt['headers'][$key])) { 223 | $temp = $rlt['headers'][$key]; 224 | unset($rlt['headers'][$key]); 225 | $rlt['headers'][$key][] = $temp; 226 | $rlt['headers'][$key][] = $value; 227 | } else { 228 | $rlt['headers'][$key] = $value; 229 | } 230 | if (!$isgzip && strtolower($key) == 'content-encoding' && strtolower($value) == 'gzip') { 231 | $isgzip = true; 232 | } 233 | if (!$ischunk && strtolower($key) == 'transfer-encoding' && strtolower($value) == 'chunked') { 234 | $ischunk = true; 235 | } 236 | } 237 | if ($chunked && $ischunk) { 238 | $rlt['content'] = $this->ihttp_response_parse_unchunk($split1[1]); 239 | } else { 240 | $rlt['content'] = $split1[1]; 241 | } 242 | if ($isgzip && function_exists('gzdecode')) { 243 | $rlt['content'] = gzdecode($rlt['content']); 244 | } 245 | 246 | $rlt['meta'] = $data; 247 | if ($rlt['code'] == '100') { 248 | return $this->ihttp_response_parse($rlt['content']); 249 | } 250 | return $rlt; 251 | } 252 | 253 | function ihttp_response_parse_unchunk($str = null) 254 | { 255 | if (!is_string($str) or strlen($str) < 1) { 256 | return false; 257 | } 258 | $eol = "\r\n"; 259 | $add = strlen($eol); 260 | $tmp = $str; 261 | $str = ''; 262 | do { 263 | $tmp = ltrim($tmp); 264 | $pos = strpos($tmp, $eol); 265 | if ($pos === false) { 266 | return false; 267 | } 268 | $len = hexdec(substr($tmp, 0, $pos)); 269 | if (!is_numeric($len) or $len < 0) { 270 | return false; 271 | } 272 | $str .= substr($tmp, ($pos + $add), $len); 273 | $tmp = substr($tmp, ($len + $pos + $add)); 274 | $check = trim($tmp); 275 | } while (!empty($check)); 276 | unset($tmp); 277 | return $str; 278 | } 279 | 280 | function ihttp_parse_url($url, $set_default_port = false) 281 | { 282 | if (empty($url)) return false; 283 | $urlset = parse_url($url); 284 | if (!empty($urlset['scheme']) && !in_array($urlset['scheme'], array('http', 'https'))) { 285 | return ['code' => 0, 'info' => '只能使用 http 及 https 协议']; 286 | } 287 | if (empty($urlset['path'])) { 288 | $urlset['path'] = '/'; 289 | } 290 | if (!empty($urlset['query'])) { 291 | $urlset['query'] = "?{$urlset['query']}"; 292 | } 293 | if (!strpos($url, 'https://') === false && !extension_loaded('openssl')) { 294 | if (!extension_loaded("openssl")) { 295 | return ['code' => 0, 'info' => '请开启您PHP环境的openssl']; 296 | } 297 | } 298 | 299 | if ($set_default_port && empty($urlset['port'])) { 300 | $urlset['port'] = $urlset['scheme'] == 'https' ? '443' : '80'; 301 | } 302 | return $urlset; 303 | } 304 | 305 | function ihttp_socketopen($hostname, $port = 80, &$errno, &$errstr, $timeout = 15) 306 | { 307 | $fp = ''; 308 | if (function_exists('fsockopen')) { 309 | $fp = @fsockopen($hostname, $port, $errno, $errstr, $timeout); 310 | } elseif (function_exists('pfsockopen')) { 311 | $fp = @pfsockopen($hostname, $port, $errno, $errstr, $timeout); 312 | } elseif (function_exists('stream_socket_client')) { 313 | $fp = @stream_socket_client($hostname . ':' . $port, $errno, $errstr, $timeout); 314 | } 315 | return $fp; 316 | } 317 | 318 | function ihttp_build_httpbody($url, $body, $request_headers) 319 | { 320 | $urlset = $this->ihttp_parse_url($url, true); 321 | if (!empty($urlset['ip'])) { 322 | $extra['ip'] = $urlset['ip']; 323 | } 324 | $fdata = "POST {$urlset['path']}{$urlset['query']} HTTP/1.1\r\n"; 325 | $fdata .= "Accept: application/json, text/plain, */*\r\n"; 326 | $fdata .= "Accept-Language: zh-cn\r\n"; 327 | $fdata .= "Host: {$urlset['host']}\r\n"; 328 | $fdata .= "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1\r\n"; 329 | if (function_exists('gzdecode')) { 330 | $fdata .= "Accept-Encoding: gzip, deflate\r\n"; 331 | } 332 | if (!empty($request_headers) && is_array($request_headers)) { 333 | foreach ($request_headers as $opt => $value) { 334 | $fdata .= $value . "\r\n"; 335 | } 336 | } 337 | $fdata .= "Connection: close\r\n\r\n"; 338 | // var_dump($fdata); 339 | $fdata .= $body . "\r\n"; 340 | return $fdata; 341 | } 342 | 343 | public function doCurl($url, $params, $request_headers, $timeout = 60) 344 | { 345 | $curl = curl_init(); 346 | curl_setopt($curl, CURLOPT_URL, $url); 347 | curl_setopt($curl, CURLOPT_POST, 1); 348 | curl_setopt($curl, CURLOPT_POSTFIELDS, $params); 349 | curl_setopt($curl, CURLOPT_HTTPHEADER, $request_headers); 350 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); 351 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); 352 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 353 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20); 354 | curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); 355 | 356 | return $curl; 357 | } 358 | 359 | public function getMillisecond() 360 | { 361 | list($t1, $t2) = explode(' ', microtime()); 362 | return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); 363 | } 364 | 365 | public function mkdirs($path) 366 | { 367 | if (!is_dir($path)) { 368 | $this->mkdirs(dirname($path)); 369 | mkdir($path); 370 | } 371 | 372 | return is_dir($path); 373 | } 374 | 375 | public function getUUID() 376 | { 377 | // Generate 128 bit random sequence 378 | $randmax_bits = strlen(base_convert(mt_getrandmax(), 10, 2)); // how many bits is mt_getrandmax() 379 | $x = ''; 380 | while (strlen($x) < 128) { 381 | $maxbits = (128 - strlen($x) < $randmax_bits) ? 128 - strlen($x) : $randmax_bits; 382 | $x .= str_pad(base_convert(mt_rand(0, pow(2, $maxbits)), 10, 2), $maxbits, "0", STR_PAD_LEFT); 383 | } 384 | 385 | // break into fields 386 | $a = array(); 387 | $a['time_low_part'] = substr($x, 0, 32); 388 | $a['time_mid'] = substr($x, 32, 16); 389 | $a['time_hi_and_version'] = substr($x, 48, 16); 390 | $a['clock_seq'] = substr($x, 64, 16); 391 | $a['node_part'] = substr($x, 80, 48); 392 | 393 | // Apply bit masks for "random or pseudo-random" version per RFC 394 | $a['time_hi_and_version'] = substr_replace($a['time_hi_and_version'], '0100', 0, 4); 395 | $a['clock_seq'] = substr_replace($a['clock_seq'], '10', 0, 2); 396 | 397 | // Format output 398 | return sprintf('%s-%s-%s-%s-%s', 399 | str_pad(base_convert($a['time_low_part'], 2, 16), 8, "0", STR_PAD_LEFT), 400 | str_pad(base_convert($a['time_mid'], 2, 16), 4, "0", STR_PAD_LEFT), 401 | str_pad(base_convert($a['time_hi_and_version'], 2, 16), 4, "0", STR_PAD_LEFT), 402 | str_pad(base_convert($a['clock_seq'], 2, 16), 4, "0", STR_PAD_LEFT), 403 | str_pad(base_convert($a['node_part'], 2, 16), 12, "0", STR_PAD_LEFT)); 404 | } 405 | 406 | public function random($length, $numeric = FALSE) 407 | { 408 | $seed = base_convert(md5(microtime()), 16, $numeric ? 10 : 35); 409 | $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed)); 410 | if ($numeric) { 411 | $hash = ''; 412 | } else { 413 | $hash = chr(rand(1, 26) + rand(0, 1) * 32 + 64); 414 | $length--; 415 | } 416 | $max = strlen($seed) - 1; 417 | for ($i = 0; $i < $length; $i++) { 418 | $hash .= $seed{mt_rand(0, $max)}; 419 | } 420 | return $hash; 421 | } 422 | } 423 | --------------------------------------------------------------------------------