From 7cc1736e903e5e96246536f7c6ccd4598b201a90 Mon Sep 17 00:00:00 2001 From: Anyon Date: Sat, 27 Jan 2018 16:37:42 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=9B=B4=E6=96=B0]=E5=AE=8C=E5=96=84=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Wechat/Contracts/Prpcrypt.php | 190 ++++++++ Wechat/Contracts/Request.php | 422 +++++++----------- Wechat/Contracts/Tools.php | 317 +++++++++++++ Wechat/Contracts/Wechat.php | 18 +- Wechat/Exceptions/InvalidDecryptException.php | 41 ++ .../Exceptions/InvalidResponseException.php | 4 +- Wechat/Exceptions/LocalCacheException.php | 4 +- Wechat/Media.php | 10 +- Wechat/Receive.php | 164 +++++++ 9 files changed, 896 insertions(+), 274 deletions(-) create mode 100644 Wechat/Contracts/Prpcrypt.php create mode 100644 Wechat/Contracts/Tools.php create mode 100644 Wechat/Exceptions/InvalidDecryptException.php create mode 100644 Wechat/Receive.php diff --git a/Wechat/Contracts/Prpcrypt.php b/Wechat/Contracts/Prpcrypt.php new file mode 100644 index 0000000..59c14c8 --- /dev/null +++ b/Wechat/Contracts/Prpcrypt.php @@ -0,0 +1,190 @@ + PKCS7Encoder::$block_size) { + $pad = 0; + } + return substr($text, 0, (strlen($text) - $pad)); + } + +} + +/** + * 公众号消息 - 加解密 + * Class Prpcrypt + */ +class Prpcrypt +{ + + public $key; + + /** + * Prpcrypt constructor. + * @param $key + */ + function __construct($key) + { + $this->key = base64_decode("{$key}="); + } + + /** + * 对明文进行加密 + * @param string $text 需要加密的明文 + * @param string $appid 公众号APPID + * @return array + */ + public function encrypt($text, $appid) + { + try { + $random = $this->getRandomStr(); + $iv = substr($this->key, 0, 16); + $pkcEncoder = new PKCS7Encoder(); + $text = $pkcEncoder->encode($random . pack("N", strlen($text)) . $text . $appid); + $encrypted = openssl_encrypt($text, 'AES-256-CBC', substr($this->key, 0, 32), OPENSSL_ZERO_PADDING, $iv); + return [ErrorCode::$OK, $encrypted]; + } catch (Exception $e) { + return [ErrorCode::$EncryptAESError, null]; + } + } + + /** + * 对密文进行解密 + * @param string $encrypted 需要解密的密文 + * @return array + */ + public function decrypt($encrypted) + { + try { + $iv = substr($this->key, 0, 16); + $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', substr($this->key, 0, 32), OPENSSL_ZERO_PADDING, $iv); + } catch (Exception $e) { + return [ErrorCode::$DecryptAESError, null]; + } + try { + $pkcEncoder = new PKCS7Encoder(); + $result = $pkcEncoder->decode($decrypted); + if (strlen($result) < 16) { + return [ErrorCode::$DecryptAESError, null]; + } + $content = substr($result, 16, strlen($result)); + $len_list = unpack("N", substr($content, 0, 4)); + $xml_len = $len_list[1]; + return [0, substr($content, 4, $xml_len), substr($content, $xml_len + 4)]; + } catch (Exception $e) { + return [ErrorCode::$IllegalBuffer, null]; + } + + } + + /** + * 随机生成16位字符串 + * @param string $str + * @return string 生成的字符串 + */ + function getRandomStr($str = "") + { + $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; + $max = strlen($str_pol) - 1; + for ($i = 0; $i < 16; $i++) { + $str .= $str_pol[mt_rand(0, $max)]; + } + return $str; + } + +} + +/** + * 仅用作类内部使用 + * 不用于官方API接口的errCode码 + * Class ErrorCode + */ +class ErrorCode +{ + + public static $OK = 0; + public static $ParseXmlError = 40002; + public static $IllegalAesKey = 40004; + public static $IllegalBuffer = 40008; + public static $EncryptAESError = 40006; + public static $DecryptAESError = 40007; + public static $EncodeBase64Error = 40009; + public static $DecodeBase64Error = 40010; + public static $GenReturnXmlError = 40011; + public static $ValidateAppidError = 40005; + public static $ComputeSignatureError = 40003; + public static $ValidateSignatureError = 40001; + public static $errCode = [ + '0' => '处理成功', + '40001' => '校验签名失败', + '40002' => '解析xml失败', + '40003' => '计算签名失败', + '40004' => '不合法的AESKey', + '40005' => '校验AppID失败', + '40006' => 'AES加密失败', + '40007' => 'AES解密失败', + '40008' => '公众平台发送的xml不合法', + '40009' => 'Base64编码失败', + '40010' => 'Base64解码失败', + '40011' => '公众帐号生成回包xml失败', + ]; + + /** + * 获取错误消息内容 + * @param string $err + * @return bool + */ + public static function getErrText($err) + { + if (isset(self::$errCode[$err])) { + return self::$errCode[$err]; + } + return false; + } + +} diff --git a/Wechat/Contracts/Request.php b/Wechat/Contracts/Request.php index 44e4f37..5a72271 100644 --- a/Wechat/Contracts/Request.php +++ b/Wechat/Contracts/Request.php @@ -15,303 +15,213 @@ namespace Wechat\Contracts; use Wechat\Exceptions\InvalidArgumentException; +use Wechat\Exceptions\InvalidDecryptException; use Wechat\Exceptions\InvalidResponseException; -use Wechat\Exceptions\LocalCacheException; /** - * 网络请求支持 + * 微信通知处理基本类 * Class Request - * @package Wechat + * @package Wechat\Contracts */ class Request { /** - * 缓存路径 - * @var null + * 公众号APPID + * @var string */ - public static $cache_path = null; + protected $appid; /** - * 根据文件后缀获取文件MINE - * @param array $ext 文件后缀 - * @param array $mine 文件后缀MINE信息 - * @return string - * @throws LocalCacheException + * 公众号推送XML内容 + * @var string */ - public static function getExtMine($ext, $mine = []) - { - $mines = self::getMines(); - foreach (is_string($ext) ? explode(',', $ext) : $ext as $e) { - $mine[] = isset($mines[strtolower($e)]) ? $mines[strtolower($e)] : 'application/octet-stream'; - } - return join(',', array_unique($mine)); - } + protected $postxml; /** - * 获取所有文件扩展的mine - * @return array - * @throws LocalCacheException + * 公众号推送加密类型 + * @var string */ - private static function getMines() - { - $mines = self::getCache('all_ext_mine'); - if (empty($mines)) { - $content = file_get_contents('http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types'); - preg_match_all('#^([^\s]{2,}?)\s+(.+?)$#ism', $content, $matches, PREG_SET_ORDER); - foreach ($matches as $match) { - foreach (explode(" ", $match[2]) as $ext) { - $mines[$ext] = $match[1]; - } - } - self::setCache('all_ext_mine', $mines); - } - return $mines; - } + protected $encryptType; /** - * 创建CURL文件对象 - * @param $filename - * @param string $mimetype - * @param string $postname - * @return \CURLFile|string - * @throws LocalCacheException + * 当前公众号配置对象 + * @var Config */ - public static function createCurlFile($filename, $mimetype = '', $postname = '') - { - $basename = $postname ?: basename($filename); - $basemine = $mimetype ?: self::getExtMine(pathinfo($filename, 4)); - if (function_exists('curl_file_create')) { - return curl_file_create($filename, $basemine, $basename); - } - return "@{$filename};filename={$basename};type={$basemine}"; - } + protected $config; /** - * 以get访问模拟访问 - * @param string $url 访问URL - * @param array $query GET数 + * 公众号的推送请求参数 + * @var Config + */ + protected $params; + + /** + * 公众号推送内容对象 + * @var Config + */ + protected $receive; + + /** + * 准备回复的消息内容 + * @var array + */ + protected $message; + + /** + * Request constructor. * @param array $options - * @return bool|string - */ - public static function get($url, $query = [], $options = []) - { - $options['query'] = $query; - return self::doRequest('get', $url, $options); - } - - /** - * 以post访问模拟访问 - * @param string $url 访问URL - * @param array $data POST数据 - * @param array $options - * @return bool|string - */ - public static function post($url, $data = [], $options = []) - { - $options['data'] = $data; - return self::doRequest('post', $url, $options); - } - - /** - * 数组转XML内容 - * @param array $data - * @return string - */ - public static function toXml($data) - { - return "" . self::_data_to_xml($data) . ""; - } - - /** - * XML内容生成 - * @param array $data 数据 - * @param string $content - * @return string - */ - private static function _data_to_xml($data, $content = '') - { - foreach ($data as $key => $val) { - $content .= "<{$key}>"; - if (is_array($val) || is_object($val)) { - $content .= self::_data_to_xml($val); - } elseif (is_string($val)) { - $content .= ''; - } else { - $content .= $val; - } - $content .= ""; - } - return $content; - } - - /** - * 解析XML内容到数组 - * @param string $xml - * @return array - */ - public static function fromXml($xml) - { - return json_decode(self::toJson(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); - } - - /** - * 数组转xml内容 - * @param array $data - * @return null|string|string - */ - public static function toJson($data) - { - return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) { - return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); - }, json_encode($data)); - } - - /** - * 解析JSON内容到数组 - * @param string $json - * @return array * @throws InvalidResponseException */ - public static function fromJson($json) + public function __construct(array $options) { - $result = json_decode($json, true); - if (empty($result)) { - throw new InvalidResponseException('invalid response.', '0'); + if (empty($options['appid'])) { + throw new InvalidArgumentException("Missing Config -- [appid]"); } - if (!empty($result['errcode'])) { - throw new InvalidResponseException($result['errmsg'], $result['errcode'], $result); + if (empty($options['appsecret'])) { + throw new InvalidArgumentException("Missing Config -- [appsecret]"); } - return $result; - } - - /** - * CURL模拟网络请求 - * @param string $method 请求方法 - * @param string $url 请求方法 - * @param array $options 请求参数[headers,data,ssl_cer,ssl_key] - * @return bool|string - */ - protected static function doRequest($method, $url, $options = []) - { - $curl = curl_init(); - // GET参数设置 - if (!empty($options['query'])) { - $url .= (stripos($url, '?') !== false ? '&' : '?') . http_build_query($options['query']); + if (empty($options['token'])) { + throw new InvalidArgumentException("Missing Config -- [token]"); } - // POST数据设置 - if (strtolower($method) === 'post') { - curl_setopt($curl, CURLOPT_POST, true); - curl_setopt($curl, CURLOPT_POSTFIELDS, self::build($options['data'])); - } - // CURL头信息设置 - if (!empty($options['headers'])) { - curl_setopt($curl, CURLOPT_HTTPHEADER, $options['headers']); - } - // 证书文件设置 - if (!empty($options['ssl_cer'])) { - if (file_exists($options['ssl_cer'])) { - curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM'); - curl_setopt($curl, CURLOPT_SSLCERT, $options['ssl_cer']); - } else { - throw new InvalidArgumentException("Certificate files that do not exist. --- [{$options['ssl_cer']}]"); - } - } - // 证书文件设置 - if (!empty($options['ssl_key'])) { - if (file_exists($options['ssl_key'])) { - curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM'); - curl_setopt($curl, CURLOPT_SSLKEY, $options['ssl_key']); - } else { - throw new InvalidArgumentException("Certificate files that do not exist. --- [{$options['ssl_key']}]"); - } - } - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_TIMEOUT, 60); - curl_setopt($curl, CURLOPT_HEADER, false); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - list($content, $status) = [curl_exec($curl), curl_getinfo($curl), curl_close($curl)]; - return (intval($status["http_code"]) === 200) ? $content : false; - } - - /** - * POST数据过滤处理 - * @param array $data - * @return array - */ - private static function build($data) - { - if (is_array($data)) { - foreach ($data as $key => $value) { - if (is_string($value) && class_exists('CURLFile', false) && stripos($value, '@') === 0) { - $filename = realpath(trim($value, '@')); - if ($filename && file_exists($filename)) { - $data[$key] = new \CURLFile($filename); - } + // 参数初始化 + $this->config = new Config($options); + $this->params = new Config($_REQUEST); + $this->appid = $this->config->get('appid'); + // 推送消息处理 + if ($_SERVER['REQUEST_METHOD'] == "POST") { + $this->postxml = file_get_contents("php://input"); + $this->encryptType = $this->params->get('encrypt_type'); + if ($this->encryptType == 'aes') { + if (empty($options['encodingaeskey'])) { + throw new InvalidArgumentException("Missing Config -- [encodingaeskey]"); } + if (!class_exists('Prpcrypt', false)) { + require __DIR__ . '/Prpcrypt.php'; + } + $prpcrypt = new \Prpcrypt($this->config->get('encodingaeskey')); + $result = Tools::fromXml($this->postxml); + $array = $prpcrypt->decrypt($result['Encrypt']); + if (intval($array[0]) > 0) { + throw new InvalidResponseException($array[1], $array[0]); + } + list($this->postxml, $this->appid) = [$array[1], $array[2]]; } - } - return $data; - } - - /** - * 缓存配置与存储 - * @param string $name 缓存名称 - * @param string $value 缓存内容 - * @param int $expired 缓存时间(0表示永久缓存) - * @throws LocalCacheException - */ - public static function setCache($name, $value = '', $expired = 3600) - { - $cache_file = self::getCacheName($name); - $content = serialize(['name' => $name, 'value' => $value, 'expired' => time() + intval($expired)]); - if (!file_put_contents($cache_file, $content)) { - throw new LocalCacheException('local cache error.', '0'); + $this->receive = new Config(Tools::fromXml($this->postxml)); + } elseif ($_SERVER['REQUEST_METHOD'] == "GET" && $this->checkSignature()) { + @ob_clean(); + exit($this->params->get('echostr')); + } else { + throw new InvalidResponseException('Invalid interface request.', '0'); } } /** - * 获取缓存内容 - * @param string $name 缓存名称 - * @return null|mixed - */ - public static function getCache($name) - { - $cache_file = self::getCacheName($name); - if (file_exists($cache_file) && ($content = file_get_contents($cache_file))) { - $data = unserialize($content); - if (isset($data['expired']) && (intval($data['expired']) === 0 || intval($data['expired']) >= time())) { - return $data['value']; - } - self::delCache($name); - } - return null; - } - - /** - * 移除缓存文件 - * @param string $name 缓存名称 + * 验证来自微信服务器 + * @param string $str * @return bool */ - public static function delCache($name) + private function checkSignature($str = '') { - $cache_file = self::getCacheName($name); - return file_exists($cache_file) ? unlink($cache_file) : true; + $nonce = $this->params->get('nonce'); + $timestamp = $this->params->get('timestamp'); + $msg_signature = $this->params->get('msg_signature'); + $signature = empty($msg_signature) ? $this->params->get('signature') : $msg_signature; + $tmpArr = [$this->config->get('token'), $timestamp, $nonce, $str]; + sort($tmpArr, SORT_STRING); + if (sha1(implode($tmpArr)) == $signature) { + return true; + } + return false; } /** - * 应用缓存目录 - * @param string $name + * 获取公众号推送对象 + * @return array + */ + public function getReceive() + { + return $this->receive->get(); + } + + /** + * 回复消息 + * @param array $data 消息内容 + * @param bool $return 是否返回XML内容 + * @return string + * @throws InvalidDecryptException + */ + public function reply(array $data = [], $return = false) + { + $xml = Tools::toXml(empty($data) ? $this->message : $data); + if ($this->encryptType == 'aes') { + if (!class_exists('Prpcrypt', false)) { + require __DIR__ . '/Prpcrypt.php'; + } + $prpcrypt = new \Prpcrypt($this->config->get('encodingaeskey')); + // 如果是第三方平台,加密得使用 component_appid + $component_appid = $this->config->get('component_appid'); + $appid = empty($component_appid) ? $this->appid : $component_appid; + $array = $prpcrypt->encrypt($xml, $appid); + if ($array[0] > 0) { + throw new InvalidDecryptException('Encrypt Error.', '0'); + } + list($timestamp, $encrypt) = [time(), $array[1]]; + $nonce = rand(77, 999) * rand(605, 888) * rand(11, 99); + $tmpArr = [$this->config->get('token'), $timestamp, $nonce, $encrypt]; + sort($tmpArr, SORT_STRING); + $signature = sha1(implode($tmpArr)); + $format = "%s"; + $xml = sprintf($format, $encrypt, $signature, $timestamp, $nonce); + } + if ($return) { + return $xml; + } + @ob_clean(); + echo $xml; + } + + /** + * 获取当前微信OPENID * @return string */ - private static function getCacheName($name) + public function getOpenid() { - if (empty(self::$cache_path)) { - self::$cache_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Cache' . DIRECTORY_SEPARATOR; - } - self::$cache_path = rtrim(self::$cache_path, '/\\') . DIRECTORY_SEPARATOR; - file_exists(self::$cache_path) || mkdir(self::$cache_path, 0755, true); - return self::$cache_path . md5($name); + return $this->receive->get('FromUserName'); + } + + /** + * 获取当前推送消息内容 + * @return string + */ + public function getMsgType() + { + return $this->receive->get('MsgType'); + } + + /** + * 获取当前推送消息ID + * @return string + */ + public function getMsgId() + { + return $this->receive->get('MsgId'); + } + + /** + * 获取当前推送时间 + * @return integer + */ + public function getMsgTime() + { + return $this->receive->get('CreateTime'); + } + + /** + * 获取当前推送公众号 + * @return string + */ + public function getToOpenid() + { + return $this->receive->get('ToUserName'); } } \ No newline at end of file diff --git a/Wechat/Contracts/Tools.php b/Wechat/Contracts/Tools.php new file mode 100644 index 0000000..814f3d4 --- /dev/null +++ b/Wechat/Contracts/Tools.php @@ -0,0 +1,317 @@ +" . self::_data_to_xml($data) . ""; + } + + /** + * XML内容生成 + * @param array $data 数据 + * @param string $content + * @return string + */ + private static function _data_to_xml($data, $content = '') + { + foreach ($data as $key => $val) { + $content .= "<{$key}>"; + if (is_array($val) || is_object($val)) { + $content .= self::_data_to_xml($val); + } elseif (is_string($val)) { + $content .= ''; + } else { + $content .= $val; + } + $content .= ""; + } + return $content; + } + + /** + * 解析XML内容到数组 + * @param string $xml + * @return array + */ + public static function fromXml($xml) + { + return json_decode(self::toJson(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); + } + + /** + * 数组转xml内容 + * @param array $data + * @return null|string|string + */ + public static function toJson($data) + { + return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) { + return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); + }, json_encode($data)); + } + + /** + * 解析JSON内容到数组 + * @param string $json + * @return array + * @throws InvalidResponseException + */ + public static function fromJson($json) + { + $result = json_decode($json, true); + if (empty($result)) { + throw new InvalidResponseException('invalid response.', '0'); + } + if (!empty($result['errcode'])) { + throw new InvalidResponseException($result['errmsg'], $result['errcode'], $result); + } + return $result; + } + + /** + * CURL模拟网络请求 + * @param string $method 请求方法 + * @param string $url 请求方法 + * @param array $options 请求参数[headers,data,ssl_cer,ssl_key] + * @return bool|string + */ + protected static function doRequest($method, $url, $options = []) + { + $curl = curl_init(); + // GET参数设置 + if (!empty($options['query'])) { + $url .= (stripos($url, '?') !== false ? '&' : '?') . http_build_query($options['query']); + } + // POST数据设置 + if (strtolower($method) === 'post') { + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, self::build($options['data'])); + } + // CURL头信息设置 + if (!empty($options['headers'])) { + curl_setopt($curl, CURLOPT_HTTPHEADER, $options['headers']); + } + // 证书文件设置 + if (!empty($options['ssl_cer'])) { + if (file_exists($options['ssl_cer'])) { + curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM'); + curl_setopt($curl, CURLOPT_SSLCERT, $options['ssl_cer']); + } else { + throw new InvalidArgumentException("Certificate files that do not exist. --- [{$options['ssl_cer']}]"); + } + } + // 证书文件设置 + if (!empty($options['ssl_key'])) { + if (file_exists($options['ssl_key'])) { + curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM'); + curl_setopt($curl, CURLOPT_SSLKEY, $options['ssl_key']); + } else { + throw new InvalidArgumentException("Certificate files that do not exist. --- [{$options['ssl_key']}]"); + } + } + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_TIMEOUT, 60); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + list($content, $status) = [curl_exec($curl), curl_getinfo($curl), curl_close($curl)]; + return (intval($status["http_code"]) === 200) ? $content : false; + } + + /** + * POST数据过滤处理 + * @param array $data + * @return array + */ + private static function build($data) + { + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_string($value) && class_exists('CURLFile', false) && stripos($value, '@') === 0) { + $filename = realpath(trim($value, '@')); + if ($filename && file_exists($filename)) { + $data[$key] = new \CURLFile($filename); + } + } + } + } + return $data; + } + + /** + * 缓存配置与存储 + * @param string $name 缓存名称 + * @param string $value 缓存内容 + * @param int $expired 缓存时间(0表示永久缓存) + * @throws LocalCacheException + */ + public static function setCache($name, $value = '', $expired = 3600) + { + $cache_file = self::getCacheName($name); + $content = serialize(['name' => $name, 'value' => $value, 'expired' => time() + intval($expired)]); + if (!file_put_contents($cache_file, $content)) { + throw new LocalCacheException('local cache error.', '0'); + } + } + + /** + * 获取缓存内容 + * @param string $name 缓存名称 + * @return null|mixed + */ + public static function getCache($name) + { + $cache_file = self::getCacheName($name); + if (file_exists($cache_file) && ($content = file_get_contents($cache_file))) { + $data = unserialize($content); + if (isset($data['expired']) && (intval($data['expired']) === 0 || intval($data['expired']) >= time())) { + return $data['value']; + } + self::delCache($name); + } + return null; + } + + /** + * 移除缓存文件 + * @param string $name 缓存名称 + * @return bool + */ + public static function delCache($name) + { + $cache_file = self::getCacheName($name); + return file_exists($cache_file) ? unlink($cache_file) : true; + } + + /** + * 应用缓存目录 + * @param string $name + * @return string + */ + private static function getCacheName($name) + { + if (empty(self::$cache_path)) { + self::$cache_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Cache' . DIRECTORY_SEPARATOR; + } + self::$cache_path = rtrim(self::$cache_path, '/\\') . DIRECTORY_SEPARATOR; + file_exists(self::$cache_path) || mkdir(self::$cache_path, 0755, true); + return self::$cache_path . md5($name); + } +} \ No newline at end of file diff --git a/Wechat/Contracts/Wechat.php b/Wechat/Contracts/Wechat.php index bfa9c31..cfc0c7b 100644 --- a/Wechat/Contracts/Wechat.php +++ b/Wechat/Contracts/Wechat.php @@ -57,8 +57,8 @@ class Wechat if (empty($options['appid'])) { throw new InvalidArgumentException("Missing Config -- [appid]"); } - if (empty($options['secret'])) { - throw new InvalidArgumentException("Missing Config -- [secret]"); + if (empty($options['appsecret'])) { + throw new InvalidArgumentException("Missing Config -- [appsecret]"); } $this->config = new Config($options); } @@ -75,15 +75,15 @@ class Wechat return $this->access_token; } $cacheKey = $this->config->get('appid') . '_accesstoken'; - $this->access_token = Request::getCache($cacheKey); + $this->access_token = Tools::getCache($cacheKey); if (!empty($this->access_token)) { return $this->access_token; } - list($appid, $secret) = [$this->config->get('appid'), $this->config->get('secret')]; + list($appid, $secret) = [$this->config->get('appid'), $this->config->get('appsecret')]; $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}"; - $result = Request::fromJson(Request::get($url)); + $result = Tools::fromJson(Tools::get($url)); if (!empty($result['access_token'])) { - Request::setCache($cacheKey, $result['access_token'], 6000); + Tools::setCache($cacheKey, $result['access_token'], 6000); } return $result['access_token']; } @@ -95,7 +95,7 @@ class Wechat public function delAccessToken() { $this->access_token = ''; - return Request::delCache($this->config->get('appid') . '_accesstoken'); + return Tools::delCache($this->config->get('appid') . '_accesstoken'); } @@ -107,7 +107,7 @@ class Wechat protected function httpGetForJson($url) { try { - return Request::fromJson(Request::get($url)); + return Tools::fromJson(Tools::get($url)); } catch (InvalidResponseException $e) { if (!$this->isTry && in_array($e->getCode(), ['40014', '40001', '41001', '42001'])) { $this->delAccessToken(); @@ -127,7 +127,7 @@ class Wechat protected function httpPostForJson($url, array $data, $buildToJson = true) { try { - return Request::fromJson(Request::post($url, $buildToJson ? Request::toJson($data) : $data)); + return Tools::fromJson(Tools::post($url, $buildToJson ? Tools::toJson($data) : $data)); } catch (InvalidResponseException $e) { if (!$this->isTry && in_array($e->getCode(), ['40014', '40001', '41001', '42001'])) { $this->delAccessToken(); diff --git a/Wechat/Exceptions/InvalidDecryptException.php b/Wechat/Exceptions/InvalidDecryptException.php new file mode 100644 index 0000000..6596a9e --- /dev/null +++ b/Wechat/Exceptions/InvalidDecryptException.php @@ -0,0 +1,41 @@ +raw = $raw; + } + +} \ No newline at end of file diff --git a/Wechat/Exceptions/InvalidResponseException.php b/Wechat/Exceptions/InvalidResponseException.php index 353f21b..017b21c 100644 --- a/Wechat/Exceptions/InvalidResponseException.php +++ b/Wechat/Exceptions/InvalidResponseException.php @@ -28,8 +28,8 @@ class InvalidResponseException extends \Exception /** * InvalidResponseException constructor. - * @param $message - * @param $code + * @param string $message + * @param integer $code * @param array $raw */ public function __construct($message, $code, $raw = []) diff --git a/Wechat/Exceptions/LocalCacheException.php b/Wechat/Exceptions/LocalCacheException.php index e654f41..4f112fa 100644 --- a/Wechat/Exceptions/LocalCacheException.php +++ b/Wechat/Exceptions/LocalCacheException.php @@ -29,8 +29,8 @@ class LocalCacheException extends \Exception /** * InvalidResponseException constructor. - * @param $message - * @param $code + * @param string $message + * @param integer $code * @param array $raw */ public function __construct($message, $code, $raw = []) diff --git a/Wechat/Media.php b/Wechat/Media.php index 0d4701b..023008b 100644 --- a/Wechat/Media.php +++ b/Wechat/Media.php @@ -14,7 +14,7 @@ namespace Wechat; -use Wechat\Contracts\Request; +use Wechat\Contracts\Tools; use Wechat\Contracts\Wechat; use Wechat\Exceptions\InvalidResponseException; @@ -40,7 +40,7 @@ class Media extends Wechat } $url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type={$type}"; $this->registerApi($url, __FUNCTION__, func_get_args()); - return $this->httpPostForJson($url, ['media' => Request::createCurlFile($filename)], false); + return $this->httpPostForJson($url, ['media' => Tools::createCurlFile($filename)], false); } /** @@ -54,7 +54,7 @@ class Media extends Wechat { $url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id={$media_id}"; $this->registerApi($url, __FUNCTION__, func_get_args()); - return Request::get($url); + return Tools::get($url); } /** @@ -99,7 +99,7 @@ class Media extends Wechat { $url = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN"; $this->registerApi($url, __FUNCTION__, func_get_args()); - return $this->httpPostForJson($url, ['media' => Request::createCurlFile($filename)], false); + return $this->httpPostForJson($url, ['media' => Tools::createCurlFile($filename)], false); } /** @@ -118,7 +118,7 @@ class Media extends Wechat } $url = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type={$type}"; $this->registerApi($url, __FUNCTION__, func_get_args()); - return $this->httpPostForJson($url, ['media' => Request::createCurlFile($filename), 'description' => Request::toJson($description)], false); + return $this->httpPostForJson($url, ['media' => Tools::createCurlFile($filename), 'description' => Tools::toJson($description)], false); } /** diff --git a/Wechat/Receive.php b/Wechat/Receive.php new file mode 100644 index 0000000..077dc63 --- /dev/null +++ b/Wechat/Receive.php @@ -0,0 +1,164 @@ +message = [ + 'CreateTime' => time(), + 'ToUserName' => $this->getOpenid(), + 'FromUserName' => $this->getToOpenid(), + 'MsgType' => 'transfer_customer_service', + ]; + empty($account) ?: $this->message['TransInfo'] = ['KfAccount' => $account]; + return $this; + } + + /** + * 设置文本消息 + * @param string $content 文本内容 + * @return $this + */ + public function text($content = '') + { + $this->message = [ + 'MsgType' => 'text', + 'CreateTime' => time(), + 'Content' => $content, + 'ToUserName' => $this->getOpenid(), + 'FromUserName' => $this->getToOpenid(), + ]; + return $this; + } + + /** + * 设置回复图文 + * @param array $newsData + * @return $this + */ + public function news($newsData = []) + { + $this->message = [ + 'CreateTime' => time(), + 'MsgType' => 'mpnews', + 'Articles' => $newsData, + 'ToUserName' => $this->getOpenid(), + 'FromUserName' => $this->getToOpenid(), + 'ArticleCount' => count($newsData), + ]; + return $this; + } + + /** + * 设置图片消息 + * @param string $mediaId 图片媒体ID + * @return $this + */ + public function image($mediaId = '') + { + $this->message = [ + 'MsgType' => 'image', + 'CreateTime' => time(), + 'ToUserName' => $this->getOpenid(), + 'FromUserName' => $this->getToOpenid(), + 'Image' => ['MediaId' => $mediaId], + ]; + return $this; + } + + /** + * 设置语音回复消息 + * @param string $mediaid 语音媒体ID + * @return $this + */ + public function voice($mediaid = '') + { + $this->message = [ + 'CreateTime' => time(), + 'MsgType' => 'voice', + 'ToUserName' => $this->getOpenid(), + 'FromUserName' => $this->getToOpenid(), + 'Voice' => ['MediaId' => $mediaid], + ]; + return $this; + } + + /** + * 设置视频回复消息 + * @param string $mediaid 视频媒体ID + * @param string $title 视频标题 + * @param string $description 视频描述 + * @return $this + */ + public function video($mediaid = '', $title = '', $description = '') + { + $this->message = [ + 'CreateTime' => time(), + 'MsgType' => 'video', + 'ToUserName' => $this->getOpenid(), + 'FromUserName' => $this->getToOpenid(), + 'Video' => [ + 'Title' => $title, + 'MediaId' => $mediaid, + 'Description' => $description, + ], + ]; + return $this; + } + + /** + * 设置音乐回复消息 + * @param string $title 音乐标题 + * @param string $desc 音乐描述 + * @param string $musicurl 音乐地址 + * @param string $hgmusicurl 高清音乐地址 + * @param string $thumbmediaid 音乐图片缩略图的媒体id(可选) + * @return $this + */ + public function music($title, $desc, $musicurl, $hgmusicurl = '', $thumbmediaid = '') + { + $this->message = [ + 'CreateTime' => time(), + 'MsgType' => 'music', + 'ToUserName' => $this->getOpenid(), + 'FromUserName' => $this->getToOpenid(), + 'Music' => [ + 'Title' => $title, + 'Description' => $desc, + 'MusicUrl' => $musicurl, + 'HQMusicUrl' => $hgmusicurl, + ], + ]; + if ($thumbmediaid) { + $this->message['Music']['ThumbMediaId'] = $thumbmediaid; + } + return $this; + } +} \ No newline at end of file