diff --git a/crmeb/crmeb/services/upload/BaseClient.php b/crmeb/crmeb/services/upload/BaseClient.php new file mode 100644 index 000000000..f7cdb2590 --- /dev/null +++ b/crmeb/crmeb/services/upload/BaseClient.php @@ -0,0 +1,111 @@ + + * +---------------------------------------------------------------------- + */ + +namespace crmeb\services\upload; + +/** + * 基础请求 + * Class BaseClient + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + * @package crmeb\services\upload + */ +abstract class BaseClient +{ + + /** + * 是否解析为xml + * @var bool + */ + protected $isXml = true; + + /** + * + * @var []callable + */ + protected $curlFn = []; + + /** + * @param callable $curlFn + * @return $this + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function middleware(callable $curlFn) + { + $this->curlFn[] = $curlFn; + return $this; + } + + /** + * 发起请求 + * @param string $url + * @param string $method + * @param array $data + * @param array $clientHeader + * @param int $timeout + * @return array|extend\cos\SimpleXMLElement + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + protected function requestClient(string $url, string $method, array $data = [], array $clientHeader = [], int $timeout = 10) + { + $headers = []; + foreach ($clientHeader as $key => $item) { + $headers[] = $key . ':' . $item; + } + $curl = curl_init($url); + //请求方式 + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); + //post请求 + if (!empty($data['body'])) { + curl_setopt($curl, CURLOPT_POSTFIELDS, $data['body']); + } else if (!empty($data['json'])) { + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data['json'])); + } else { + $curlFn = $this->curlFn; + foreach ($curlFn as $item) { + if ($item instanceof \Closure) { + $curlFn($curl); + } + } + } + //超时时间 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + //设置header头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + + curl_setopt($curl, CURLOPT_FAILONERROR, false); + //返回抓取数据 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + //输出header头信息 + curl_setopt($curl, CURLOPT_HEADER, true); + //TRUE 时追踪句柄的请求字符串,从 PHP 5.1.3 开始可用。这个很关键,就是允许你查看请求header + curl_setopt($curl, CURLINFO_HEADER_OUT, true); + //https请求 + if (1 == strpos("$" . $url, "https://")) { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + } + [$content, $status] = [curl_exec($curl), curl_getinfo($curl)]; + $content = trim(substr($content, $status['header_size'])); + if ($this->isXml) { + return XML::parse($content); + } else { + return json_decode($content, true); + } + } +} diff --git a/crmeb/crmeb/services/upload/extend/cos/XML.php b/crmeb/crmeb/services/upload/XML.php similarity index 98% rename from crmeb/crmeb/services/upload/extend/cos/XML.php rename to crmeb/crmeb/services/upload/XML.php index dd2e14a61..157902291 100644 --- a/crmeb/crmeb/services/upload/extend/cos/XML.php +++ b/crmeb/crmeb/services/upload/XML.php @@ -1,6 +1,6 @@ + * +---------------------------------------------------------------------- + */ + +namespace crmeb\services\upload\extend\jdoss; + + +use crmeb\services\upload\BaseClient; + +/** + * 京东云上传 + * Class Client + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + * @package crmeb\services\upload\extend\jdoss + */ +class Client extends BaseClient +{ + /** + * AK + * @var + */ + protected $accessKeyId = 'FHZVOVXFEMUGWOVARS8H'; + + /** + * SK + * @var + */ + protected $secretKey; + + /** + * 桶名 + * @var string + */ + protected $bucketName; + + /** + * 地区 + * @var string + */ + protected $region; + + /** + * @var mixed|string + */ + protected $uploadUrl; + + /** + * @var string + */ + protected $baseUrl = 's3..jdcloud-oss.com'; + + //默认地域 + const DEFAULT_REGION = 'cn-north-1'; + + /** + * Client constructor. + * @param array $config + */ + public function __construct(array $config = []) + { + $this->accessKeyId = $config['accessKey'] ?? ''; + $this->secretKey = $config['secretKey'] ?? ''; + $this->bucketName = $config['bucket'] ?? ''; + $this->region = $config['region'] ?? self::DEFAULT_REGION; + $this->uploadUrl = $config['uploadUrl'] ?? ''; + } + + /** + * 获取桶列表 + * @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function listBuckets() + { + $url = $this->getRequestUrl(); + $header = [ + 'Host' => $url, + 'Date' => gmdate('D, d M Y H:i:s \G\M\T') + ]; + + $res = $this->request('https://' . $url . '/', 'GET', [], $header); + + return $res; + } + + /** + * 获取请求域名 + * @param string $bucket + * @param string $region + * @return string + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + protected function getRequestUrl(string $bucket = '', string $region = self::DEFAULT_REGION) + { + return ($bucket ? $bucket . '.' : '') . 's3.' . $region . '.jdcloud-oss.com'; + } + + /** + * 发起请求 + * @param string $url + * @param string $method + * @param array $data + * @param array $clientHeader + * @param int $timeout + * @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + protected function request(string $url, string $method, array $data = [], array $clientHeader = [], int $timeout = 10) + { + $canonicalUri = '/'; + $canonicalQueryString = ''; + $canonicalHeaders = ''; + $signedHeaders = ''; + $payload = ''; + + $urlAttr = pathinfo($url); + if ($urlAttr['dirname'] !== 'https:') { + $urlParse = parse_url($urlAttr['dirname'] ?? ''); + if (isset($urlParse['path'])) { + $canonicalUri .= substr($urlParse['path'], 1) . '/'; + } + if (isset($urlAttr['basename'])) { + $canonicalUri .= $urlAttr['basename']; + } + if (!($pos = strripos($canonicalUri, '/')) || strlen($canonicalUri) - 1 !== $pos) { + $canonicalUri .= '/'; + } + } + + if (!empty($data['query'])) { + $query = $payload = $data['query']; + ksort($query); + $queryAttr = []; + foreach ($query as $key => $item) { + $queryAttr[urlencode($key)] = urlencode($item); + } + if ($queryAttr) { + $canonicalQueryString = implode('&', $queryAttr); + } + } elseif (!empty($data['body'])) { + $payload = $data['body']; + } elseif (!empty($data['json'])) { + $payload = json_encode($data['json']); + } + + if ($clientHeader) { + $canonicalHeadersAtrr = $signedHeadersAttr = []; + ksort($clientHeader); + foreach ($clientHeader as $key => $item) { + $canonicalHeadersAtrr[] = strtolower($key) . ':' . trim($item); + $signedHeadersAttr[] = strtolower($key); + } + if ($canonicalHeadersAtrr) { + $canonicalHeaders = implode("\n", $canonicalHeadersAtrr); + $signedHeaders = implode(';', $signedHeadersAttr); + } + } + + $clientHeader['Authorization'] = $this->generateAwsSignatureV4( + $data['region'] ?? self::DEFAULT_REGION, + $method, + $canonicalUri, + $canonicalQueryString, + $canonicalHeaders, + $signedHeaders, + $payload); + + return $this->requestClient($url, $method, $data, $clientHeader, $timeout); + } + + /** + * 生成签名 + * @param string $region + * @param string $httpMethod + * @param string $canonicalUri + * @param string $canonicalQueryString + * @param string $canonicalHeaders + * @param string $signedHeaders + * @param $payload + * @param string $service + * @return string + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + protected function generateAwsSignatureV4(string $region, string $httpMethod, string $canonicalUri, string $canonicalQueryString, string $canonicalHeaders, string $signedHeaders, $payload, string $service = 's3') + { + $algorithm = 'AWS4-HMAC-SHA256'; + $t = new \DateTime('UTC'); + $amzDate = $t->format('Ymd\THis\Z'); + $dateStamp = $t->format('Ymd'); + + $canonicalRequest = $httpMethod . "\n" . $canonicalUri . "\n" . $canonicalQueryString . "\n" . $canonicalHeaders . "\n" . $signedHeaders . "\n" . hash('sha256', $payload); + $credentialScope = $dateStamp . '/' . $region . '/' . $service . '/aws4_request'; + dump(compact('region', 'httpMethod', 'canonicalUri', 'canonicalQueryString', 'canonicalHeaders', 'signedHeaders', 'payload', 'service')); + $stringToSign = $algorithm . "\n" . $amzDate . "\n" . $credentialScope . "\n" . hash('sha256', $canonicalRequest); + $signingKey = hash_hmac('sha256', 'aws4_request', + hash_hmac('sha256', $service, + hash_hmac('sha256', $region, + hash_hmac('sha256', $dateStamp, 'AWS4' . $this->secretKey, true), + true), + true), + true); + $signature = hash_hmac('sha256', $stringToSign, $signingKey); + + return $algorithm . ' Credential=' . $this->accessKeyId . '/' . $credentialScope . ', SignedHeaders=' . $signedHeaders . ', Signature=' . $signature; + } + + public function getRegion() + { + return [ + [ + 'value' => 'cn-north-1', + 'label' => '华北-北京', + ], + [ + 'value' => 'cn-south-1', + 'label' => '华南-广州', + ], + [ + 'value' => 'cn-east-2', + 'label' => '华东-上海', + ], + [ + 'value' => 'cn-east-1', + 'label' => '华东-宿迁', + ] + ]; + } +} diff --git a/crmeb/crmeb/services/upload/extend/obs/Client.php b/crmeb/crmeb/services/upload/extend/obs/Client.php index ba67aead0..613dbabdf 100644 --- a/crmeb/crmeb/services/upload/extend/obs/Client.php +++ b/crmeb/crmeb/services/upload/extend/obs/Client.php @@ -14,9 +14,19 @@ namespace crmeb\services\upload\extend\obs; +use crmeb\exceptions\UploadException; +use crmeb\services\upload\BaseClient; use crmeb\services\upload\extend\cos\XML; -class Client +/** + * 华为云上传 + * Class Client + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + * @package crmeb\services\upload\extend\obs + */ +class Client extends BaseClient { const HEADER_PREFIX = 'x-obs-'; @@ -64,6 +74,7 @@ class Client 'x-oss-process' ]; + //桶acl const OBS_ACL = [ [ 'value' => 'public-read', @@ -74,7 +85,7 @@ class Client 'label' => '公共读写', ], ]; - + //默认acl const DEFAULT_OBS_ACL = 'public-read'; protected $isCname = false; @@ -84,32 +95,118 @@ class Client /** * @var */ - protected $accessKeyId = 'AUL8K0BMYLSZTJDT9FCM'; + protected $accessKeyId; /** * @var */ - protected $secretKey = 'SwjS5huuunY6Bzjrhr7RGvOIA3kHkfNZuzIp8t2z'; + protected $secretKey; /** * 桶名 * @var string */ - protected $bucketName = ''; + protected $bucketName; + + /** + * 地区 + * @var string + */ + protected $region; + + /** + * @var mixed|string + */ + protected $uploadUrl; /** * @var string */ protected $baseUrl = 'obs.cn-north-1.myhuaweicloud.com'; - public function __construct() + /** + * Client constructor. + * @param array $config + */ + public function __construct(array $config = []) { - + $this->accessKeyId = $config['accessKey'] ?? ''; + $this->secretKey = $config['secretKey'] ?? ''; + $this->bucketName = $config['bucket'] ?? ''; + $this->region = $config['region'] ?? 'ap-chengdu'; + $this->uploadUrl = $config['uploadUrl'] ?? ''; } - public function putObject() + /** + * 检测 + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + protected function checkOptions() { + if (!$this->bucketName) { + throw new UploadException('请传入桶名'); + } + if (!$this->region) { + throw new UploadException('请传入所属地域'); + } + if (!$this->accessKeyId) { + throw new UploadException('请传入SecretId'); + } + if (!$this->secretKey) { + throw new UploadException('请传入SecretKey'); + } + return $this; + } + + /** + * 上传图片 + * @param string $key + * @param $body + * @return mixed + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function putObject(string $key, $body, string $contentType = 'image/jpeg') + { + $url = $this->uploadUrl ?: $this->getRequestUrl($this->bucketName, $this->region); + + $header = [ + 'Host' => $url, + 'Content-Type' => $contentType, + 'Content-Length' => strlen($body), + ]; + + $res = $this->checkOptions()->request('https://' . $header['Host'] . '/' . $key, 'PUT', [ + 'bucket' => $this->bucketName, + 'body' => $body + ], $header); + + return $this->response($res); + } + + /** + * 删除上传对象 + * @param string $key + * @return mixed + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function deleteObject(string $key) + { + $header = [ + 'Host' => $this->getRequestUrl($this->bucketName, $this->region), + ]; + + $res = $this->request('https://' . $header['Host'] . '/' . $key, 'DELETE', [ + 'bucket' => $this->bucketName + ], $header); + + return $this->response($res); } /** @@ -119,11 +216,22 @@ class Client * @email 136327134@qq.com * @date 2023/5/16 */ - public function getListBuckets() + public function listBuckets() { - return $this->request('https://' . $this->baseUrl . '/', 'GET', [], []); + $res = $this->request('https://' . $this->baseUrl . '/', 'GET', [], []); + return $this->response($res); } + /** + * 创建桶 + * @param string $bucket + * @param string $region + * @param string $acl + * @return mixed + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ public function createBucket(string $bucket, string $region, string $acl = self::DEFAULT_OBS_ACL) { $header = [ @@ -131,9 +239,112 @@ class Client 'Host' => $this->getRequestUrl($bucket, $region), ]; - return $this->request('https://' . $header['Host'] . '/', 'PUT', [], $header); + $res = $this->request('https://' . $header['Host'] . '/', 'PUT', [ + 'bucket' => $bucket + ], $header); + + return $this->response($res); } + /** + * 删除桶 + * @param string $bucket + * @param string $region + * @return mixed + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function deleteBucket(string $bucket, string $region) + { + $header = [ + 'Host' => $this->getRequestUrl($bucket, $region), + ]; + $res = $this->request('https://' . $header['Host'] . '/', 'DELETE', [ + 'bucket' => $bucket + ], $header); + + return $this->response($res); + } + + /** + * 获取桶的自定义域名 + * @param string $bucket + * @param string $region + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function getBucketDomain(string $bucket, string $region) + { + $header = [ + 'Host' => $this->getRequestUrl($bucket, $region), + ]; + $res = $this->request('https://' . $header['Host'] . '/?customdomain', 'GET', [ + 'bucket' => $bucket + ], $header); + + return $this->response($res); + } + + /** + * 设置桶的自定义域名 + * @param string $bucket + * @param string $region + * @param array $data + * @return mixed + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function putBucketDomain(string $bucket, string $region, array $data = []) + { + $header = [ + 'Host' => $this->getRequestUrl($bucket, $region), + ]; + $res = $this->request('https://' . $header['Host'] . '/?customdomain=' . $data['domainname'], 'PUT', [ + 'bucket' => $bucket + ], $header); + + return $this->response($res); + } + + /** + * 设置跨域 + * @return bool + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + public function putBucketCors() + { + return true; + } + + /** + * @param $res + * @return mixed + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ + protected function response($res) + { + if (!empty($res['Code']) && !empty($res['Message'])) { + throw new UploadException($res['Message']); + } + return $res; + } + + /** + * 获取请求域名 + * @param string $bucket + * @param string $region + * @return string + * @author 等风来 + * @email 136327134@qq.com + * @date 2023/5/18 + */ protected function getRequestUrl(string $bucket, string $region) { return $bucket . '.obs.' . $region . '.myhuaweicloud.com'; @@ -153,10 +364,10 @@ class Client 'value' => 'cn-north-1', 'label' => '华北-北京一', ], - [ - 'value' => 'cn-north-4', - 'label' => '华北-北京四', - ], +// [ +// 'value' => 'cn-north-4', +// 'label' => '华北-北京四', +// ], [ 'value' => 'cn-north-9', 'label' => '华北-乌兰察布一', @@ -367,53 +578,28 @@ class Client public function request(string $url, string $method, array $data = [], array $clientHeader = [], int $timeout = 10) { $method = strtoupper($method); + $urlAttr = pathinfo($url); + $urlParse = parse_url($urlAttr['dirname'] ?? ''); + + $uriParam = ''; + if ($urlAttr['dirname'] !== 'https:') { + if (isset($urlParse['path'])) { + $uriParam .= substr($urlParse['path'], 1) . '/'; + } + if (isset($urlAttr['basename'])) { + $uriParam .= $urlAttr['basename']; + } + } $result = $this->getSign([ 'method' => $method, 'headers' => $clientHeader, 'pathArgs' => '', - 'dnsParam' => '', - 'uriParam' => '', + 'dnsParam' => $data['bucket'] ?? '', + 'uriParam' => $uriParam, ]); - $clientHeader = $result['headers']; - $headers = []; - foreach ($clientHeader as $key => $item) { - $headers[] = $key . ':' . $item; - } - $curl = curl_init($url); - //请求方式 - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); - //post请求 - if (!empty($data['body'])) { - curl_setopt($curl, CURLOPT_POSTFIELDS, $data['body']); - } else if (!empty($data['json'])) { - curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data['json'])); - } - //超时时间 - curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); - //设置header头 - curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($curl, CURLOPT_FAILONERROR, false); - //返回抓取数据 - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - //输出header头信息 - curl_setopt($curl, CURLOPT_HEADER, true); - //TRUE 时追踪句柄的请求字符串,从 PHP 5.1.3 开始可用。这个很关键,就是允许你查看请求header - curl_setopt($curl, CURLINFO_HEADER_OUT, true); - //https请求 - if (1 == strpos("$" . $url, "https://")) { - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); - } - [$content, $status] = [curl_exec($curl), curl_getinfo($curl)]; -// dump($status); - $content = trim(substr($content, $status['header_size'])); - $res = XML::parse($content); - if ($res) { - return $res; - } - return (intval($status["http_code"]) === 200) ? $content : false; + return $this->requestClient($url, $method, $data, $result['headers'], $timeout); } }