Client.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <?php
  2. /**
  3. * +----------------------------------------------------------------------
  4. * | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  5. * +----------------------------------------------------------------------
  6. * | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
  7. * +----------------------------------------------------------------------
  8. * | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  9. * +----------------------------------------------------------------------
  10. * | Author: CRMEB Team <admin@crmeb.com>
  11. * +----------------------------------------------------------------------
  12. */
  13. namespace crmeb\services\upload\extend\jdoss;
  14. use crmeb\exceptions\UploadException;
  15. use crmeb\services\upload\BaseClient;
  16. /**
  17. * 京东云上传
  18. * Class Client
  19. * @author 等风来
  20. * @email 136327134@qq.com
  21. * @date 2023/5/18
  22. * @package crmeb\services\upload\extend\jdoss
  23. */
  24. class Client extends BaseClient
  25. {
  26. /**
  27. * AK
  28. * @var
  29. */
  30. protected $accessKeyId;
  31. /**
  32. * SK
  33. * @var
  34. */
  35. protected $secretKey;
  36. /**
  37. * 桶名
  38. * @var string
  39. */
  40. protected $bucketName;
  41. /**
  42. * 地区
  43. * @var string
  44. */
  45. protected $region;
  46. /**
  47. * @var mixed|string
  48. */
  49. protected $uploadUrl;
  50. /**
  51. * @var string
  52. */
  53. protected $baseUrl = 's3.<REGION>.jdcloud-oss.com';
  54. //默认地域
  55. const DEFAULT_REGION = 'cn-north-1';
  56. /**
  57. * Client constructor.
  58. * @param array $config
  59. */
  60. public function __construct(array $config = [])
  61. {
  62. $this->accessKeyId = $config['accessKey'] ?? '';
  63. $this->secretKey = $config['secretKey'] ?? '';
  64. $this->bucketName = $config['bucket'] ?? '';
  65. $this->region = $config['region'] ?? self::DEFAULT_REGION;
  66. $this->uploadUrl = $config['uploadUrl'] ?? '';
  67. }
  68. /**
  69. * 检测桶,不存在返回true
  70. * @param string $bucket
  71. * @param string $region
  72. * @return array|bool|\crmeb\services\upload\extend\cos\SimpleXMLElement
  73. * @author 等风来
  74. * @email 136327134@qq.com
  75. * @date 2022/10/17
  76. */
  77. public function headBucket(string $bucket, string $region = '')
  78. {
  79. $url = $this->getRequestUrl($bucket, $region);
  80. $header = [
  81. 'Host' => $url
  82. ];
  83. return $this->request('https://' . $url, 'head', [], $header);
  84. }
  85. /**
  86. * 获取桶列表
  87. * @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement
  88. * @author 等风来
  89. * @email 136327134@qq.com
  90. * @date 2023/5/18
  91. */
  92. public function listBuckets()
  93. {
  94. $url = $this->getRequestUrl();
  95. $header = [
  96. 'Host' => $url,
  97. 'Date' => gmdate('D, d M Y H:i:s \G\M\T')
  98. ];
  99. $res = $this->request('https://' . $url . '/', 'GET', [], $header);
  100. return $res;
  101. }
  102. public function createBucket($name, $region, $acl)
  103. {
  104. $url = $this->getRequestUrl($name, $region);
  105. $header = [
  106. 'Host' => $url,
  107. 'Date' => gmdate('D, d M Y H:i:s \G\M\T'),
  108. 'name' => $name,
  109. 'x-amz-acl' => $acl
  110. ];
  111. $res = $this->request('https://' . $url . '/', 'PUT', [], $header);
  112. return $res;
  113. }
  114. /**
  115. * 获取请求域名
  116. * @param string $bucket
  117. * @param string $region
  118. * @return string
  119. * @author 等风来
  120. * @email 136327134@qq.com
  121. * @date 2023/5/18
  122. */
  123. protected function getRequestUrl(string $bucket = '', string $region = self::DEFAULT_REGION)
  124. {
  125. if (!$this->accessKeyId) {
  126. throw new UploadException('请传入SecretId');
  127. }
  128. if (!$this->secretKey) {
  129. throw new UploadException('请传入SecretKey');
  130. }
  131. return ($bucket ? $bucket . '.' : '') . 's3.' . $region . '.jdcloud-oss.com';
  132. }
  133. /**
  134. * 发起请求
  135. * @param string $url
  136. * @param string $method
  137. * @param array $data
  138. * @param array $clientHeader
  139. * @param int $timeout
  140. * @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement
  141. * @author 等风来
  142. * @email 136327134@qq.com
  143. * @date 2023/5/18
  144. */
  145. protected function request(string $url, string $method, array $data = [], array $clientHeader = [], int $timeout = 10)
  146. {
  147. $canonicalUri = '/';
  148. $canonicalQueryString = '';
  149. $canonicalHeaders = '';
  150. $signedHeaders = '';
  151. $payload = '';
  152. $urlAttr = pathinfo($url);
  153. if ($urlAttr['dirname'] !== 'https:') {
  154. $urlParse = parse_url($urlAttr['dirname'] ?? '');
  155. if (isset($urlParse['path'])) {
  156. $canonicalUri .= substr($urlParse['path'], 1) . '/';
  157. }
  158. if (isset($urlAttr['basename'])) {
  159. $canonicalUri .= $urlAttr['basename'];
  160. }
  161. if (!($pos = strripos($canonicalUri, '/')) || strlen($canonicalUri) - 1 !== $pos) {
  162. $canonicalUri .= '/';
  163. }
  164. }
  165. if (!empty($data['query'])) {
  166. $query = $payload = $data['query'];
  167. ksort($query);
  168. $queryAttr = [];
  169. foreach ($query as $key => $item) {
  170. $queryAttr[urlencode($key)] = urlencode($item);
  171. }
  172. if ($queryAttr) {
  173. $canonicalQueryString = implode('&', $queryAttr);
  174. }
  175. } elseif (!empty($data['body'])) {
  176. $payload = $data['body'];
  177. } elseif (!empty($data['json'])) {
  178. $payload = json_encode($data['json']);
  179. }
  180. if ($clientHeader) {
  181. $canonicalHeadersAtrr = $signedHeadersAttr = [];
  182. ksort($clientHeader);
  183. foreach ($clientHeader as $key => $item) {
  184. $canonicalHeadersAtrr[] = strtolower($key) . ':' . trim($item);
  185. $signedHeadersAttr[] = strtolower($key);
  186. }
  187. if ($canonicalHeadersAtrr) {
  188. $canonicalHeaders = implode("\n", $canonicalHeadersAtrr);
  189. $signedHeaders = implode(';', $signedHeadersAttr);
  190. }
  191. }
  192. $clientHeader['Authorization'] = $this->generateAwsSignatureV4(
  193. $data['region'] ?? self::DEFAULT_REGION,
  194. $method,
  195. $canonicalUri,
  196. $canonicalQueryString,
  197. $canonicalHeaders,
  198. $signedHeaders,
  199. $payload);
  200. return $this->requestClient($url, $method, $data, $clientHeader, $timeout);
  201. }
  202. /**
  203. * 生成签名
  204. * @param string $region
  205. * @param string $httpMethod
  206. * @param string $canonicalUri
  207. * @param string $canonicalQueryString
  208. * @param string $canonicalHeaders
  209. * @param string $signedHeaders
  210. * @param $payload
  211. * @param string $service
  212. * @return string
  213. * @author 等风来
  214. * @email 136327134@qq.com
  215. * @date 2023/5/18
  216. */
  217. protected function generateAwsSignatureV4(string $region, string $httpMethod, string $canonicalUri, string $canonicalQueryString, string $canonicalHeaders, string $signedHeaders, $payload, string $service = 's3')
  218. {
  219. $algorithm = 'AWS4-HMAC-SHA256';
  220. $t = new \DateTime('UTC');
  221. $amzDate = $t->format('Ymd\THis\Z');
  222. $dateStamp = $t->format('Ymd');
  223. $canonicalRequest = $httpMethod . "\n" . $canonicalUri . "\n" . $canonicalQueryString . "\n" . $canonicalHeaders . "\n" . $signedHeaders . "\n" . hash('sha256', $payload);
  224. $credentialScope = $dateStamp . '/' . $region . '/' . $service . '/aws4_request';
  225. dump(compact('region', 'httpMethod', 'canonicalUri', 'canonicalQueryString', 'canonicalHeaders', 'signedHeaders', 'payload', 'service'));
  226. $stringToSign = $algorithm . "\n" . $amzDate . "\n" . $credentialScope . "\n" . hash('sha256', $canonicalRequest);
  227. $signingKey = hash_hmac('sha256', 'aws4_request',
  228. hash_hmac('sha256', $service,
  229. hash_hmac('sha256', $region,
  230. hash_hmac('sha256', $dateStamp, 'AWS4' . $this->secretKey, true),
  231. true),
  232. true),
  233. true);
  234. $signature = hash_hmac('sha256', $stringToSign, $signingKey);
  235. return $algorithm . ' Credential=' . $this->accessKeyId . '/' . $credentialScope . ', SignedHeaders=' . $signedHeaders . ', Signature=' . $signature;
  236. }
  237. public function getRegion()
  238. {
  239. return [
  240. [
  241. 'value' => 'cn-north-1',
  242. 'label' => '华北-北京',
  243. ],
  244. [
  245. 'value' => 'cn-south-1',
  246. 'label' => '华南-广州',
  247. ],
  248. [
  249. 'value' => 'cn-east-2',
  250. 'label' => '华东-上海',
  251. ],
  252. [
  253. 'value' => 'cn-east-1',
  254. 'label' => '华东-宿迁',
  255. ]
  256. ];
  257. }
  258. }