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 .= "{$key}>";
- }
- 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 .= "{$key}>";
+ }
+ 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