Jdoss.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <?php
  2. namespace crmeb\services\upload\storage;
  3. use Aws\Acm\Exception\AcmException;
  4. use Aws\S3\S3Client;
  5. use crmeb\exceptions\AdminException;
  6. use crmeb\exceptions\UploadException;
  7. use crmeb\services\upload\BaseUpload;
  8. use Guzzle\Http\EntityBody;
  9. /**
  10. * 京东云COS文件上传
  11. * Class Jdoss
  12. * @package crmeb\services\upload\storage
  13. */
  14. class Jdoss extends BaseUpload
  15. {
  16. /**
  17. * 应用id
  18. * @var string
  19. */
  20. protected $appid;
  21. /**
  22. * accessKey
  23. * @var mixed
  24. */
  25. protected $accessKey;
  26. /**
  27. * secretKey
  28. * @var mixed
  29. */
  30. protected $secretKey;
  31. /**
  32. * 句柄
  33. * @var S3Client
  34. */
  35. protected $handle;
  36. /**
  37. * 空间域名 Domain
  38. * @var mixed
  39. */
  40. protected $uploadUrl;
  41. /**
  42. * 存储空间名称 公开空间
  43. * @var mixed
  44. */
  45. protected $storageName;
  46. /**
  47. * COS使用 所属地域
  48. * @var mixed|null
  49. */
  50. protected $storageRegion;
  51. /**
  52. * @var string
  53. */
  54. protected $cdn;
  55. /**
  56. * 水印位置
  57. * @var string[]
  58. */
  59. protected $position = [
  60. '1' => 'northwest',//:左上
  61. '2' => 'north',//:中上
  62. '3' => 'northeast',//:右上
  63. '4' => 'west',//:左中
  64. '5' => 'center',//:中部
  65. '6' => 'east',//:右中
  66. '7' => 'southwest',//:左下
  67. '8' => 'south',//:中下
  68. '9' => 'southeast',//:右下
  69. ];
  70. /**
  71. * 初始化
  72. * @param array $config
  73. * @return mixed|void
  74. */
  75. public function initialize(array $config)
  76. {
  77. parent::initialize($config);
  78. $this->accessKey = $config['accessKey'] ?? null;
  79. $this->secretKey = $config['secretKey'] ?? null;
  80. $this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '');
  81. $this->storageName = $config['storageName'] ?? null;
  82. $this->storageRegion = $config['storageRegion'] ?? null;
  83. $this->cdn = $config['cdn'] ?? null;
  84. $this->waterConfig['watermark_text_font'] = 'simfang仿宋.ttf';
  85. }
  86. /**
  87. * @return S3Client
  88. *
  89. * @date 2023/06/05
  90. * @author yyw
  91. */
  92. protected function app()
  93. {
  94. if (!$this->accessKey || !$this->secretKey) {
  95. throw new UploadException(400721);
  96. }
  97. $this->handle = new S3Client([
  98. 'version' => 'latest',
  99. 'region' => $this->storageRegion,
  100. 'endpoint' => "http://s3.{$this->storageRegion}.jdcloud-oss.com",
  101. 'signature_version' => 'v4',
  102. 'use_path_style_endpoint' => true,
  103. 'credentials' => [
  104. 'key' => $this->accessKey,
  105. 'secret' => $this->secretKey,
  106. ],
  107. ]);
  108. return $this->handle;
  109. }
  110. public function move(string $file = 'file')
  111. {
  112. $fileHandle = app()->request->file($file);
  113. if (!$fileHandle) {
  114. return $this->setError('上传的文件不存在');
  115. }
  116. if ($this->validate) {
  117. if (!in_array(strtolower(pathinfo($fileHandle->getOriginalName(), PATHINFO_EXTENSION)), $this->validate['fileExt'])) {
  118. return $this->setError('不合法的文件后缀');
  119. }
  120. if (filesize($fileHandle) > $this->validate['filesize']) {
  121. return $this->setError('文件过大');
  122. }
  123. if (!in_array($fileHandle->getOriginalMime(), $this->validate['fileMime'])) {
  124. return $this->setError('不合法的文件类型');
  125. }
  126. }
  127. $key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
  128. $key = $this->getUploadPath($key);
  129. try {
  130. $uploadInfo = $this->app()->putObject([
  131. 'Bucket' => $this->storageName,
  132. 'Key' => $key,
  133. 'SourceFile' => $fileHandle->getRealPath()
  134. ]);
  135. if (!isset($uploadInfo['ObjectURL'])) {
  136. return $this->setError('Upload failure');
  137. }
  138. $this->fileInfo->uploadInfo = $uploadInfo;
  139. $this->fileInfo->realName = $fileHandle->getOriginalName();
  140. $this->fileInfo->filePath = ($this->cdn ?: $this->uploadUrl) . '/' . $key;
  141. $this->fileInfo->fileName = $key;
  142. $this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
  143. $this->authThumb && $this->thumb($this->fileInfo->filePath);
  144. return $this->fileInfo;
  145. } catch (\Throwable $e) {
  146. return $this->setError($e->getMessage());
  147. }
  148. }
  149. public function stream($fileContent, string $key = null)
  150. {
  151. try {
  152. if (!$key) {
  153. $key = $this->saveFileName();
  154. }
  155. $key = $this->getUploadPath($key);
  156. $fileContent = (string)EntityBody::factory($fileContent);
  157. $uploadInfo = $this->app()->putObject([
  158. 'Bucket' => $this->storageName,
  159. 'Key' => $key,
  160. 'Body' => $fileContent
  161. ]);
  162. $uploadInfo = $uploadInfo->toArray();
  163. if (isset($uploadInfo['@metadata']['statusCode']) && $uploadInfo['@metadata']['statusCode'] !== 200) {
  164. return $this->setError('Upload failure');
  165. }
  166. $this->fileInfo->uploadInfo = $uploadInfo;
  167. $this->fileInfo->realName = $key;
  168. $this->fileInfo->filePath = ($this->cdn ?: $this->uploadUrl) . '/' . $key;
  169. $this->fileInfo->fileName = $key;
  170. $this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
  171. $this->thumb($this->fileInfo->filePath);
  172. return $this->fileInfo;
  173. } catch (\Throwable $e) {
  174. return $this->setError($e->getMessage());
  175. }
  176. }
  177. public function delete(string $key)
  178. {
  179. try {
  180. return $this->app()->deleteObject([
  181. 'Bucket' => $this->storageName,
  182. 'Key' => $key
  183. ]);
  184. } catch (\Throwable $e) {
  185. return $this->setError($e->getMessage());
  186. }
  187. }
  188. public function listbuckets(string $region, bool $line = false, bool $shared = false)
  189. {
  190. try {
  191. $res = $this->app()->listBuckets();
  192. return $res ?? [];
  193. } catch (\Throwable $e) {
  194. return [];
  195. }
  196. }
  197. public function createBucket(string $name, string $region = '', string $acl = 'public-read')
  198. {
  199. $regionData = $this->getRegion();
  200. $regionData = array_column($regionData, 'value');
  201. if (!in_array($region, $regionData)) {
  202. return $this->setError('COS:无效的区域!');
  203. }
  204. $this->storageRegion = $region;
  205. $app = $this->app();
  206. //检测桶
  207. try {
  208. $app->headBucket([
  209. 'Bucket' => $name
  210. ]);
  211. } catch (\Throwable $e) {
  212. //桶不存在返回404
  213. if (strstr('404', $e->getMessage())) {
  214. return $this->setError('COS:' . $e->getMessage());
  215. }
  216. }
  217. //创建桶
  218. try {
  219. $res = $app->createBucket([
  220. 'Bucket' => $name,
  221. 'ACL' => $acl,
  222. ]);
  223. } catch (\Throwable $e) {
  224. if (strstr('[curl] 6', $e->getMessage())) {
  225. return $this->setError('COS:无效的区域!!');
  226. } else if (strstr('Access Denied.', $e->getMessage())) {
  227. return $this->setError('COS:无权访问');
  228. }
  229. return $this->setError('COS:' . $e->getMessage());
  230. }
  231. return $res;
  232. }
  233. public function getRegion()
  234. {
  235. return [
  236. [
  237. 'value' => 'cn-north-1',
  238. 'label' => '华北-北京'
  239. ],
  240. [
  241. 'value' => 'cn-east-1',
  242. 'label' => '华东-宿迁'
  243. ],
  244. [
  245. 'value' => 'cn-east-2',
  246. 'label' => '华东-上海'
  247. ],
  248. [
  249. 'value' => 'cn-south-1',
  250. 'label' => '华南-广州'
  251. ]
  252. ];
  253. }
  254. public function deleteBucket(string $name, string $region = '')
  255. {
  256. try {
  257. $this->storageRegion = $region;
  258. $this->app()->deleteBucket([
  259. 'Bucket' => $name, // REQUIRED
  260. ]);
  261. return true;
  262. } catch (AcmException $e) {
  263. return $this->setError($e->getMessage());
  264. }
  265. }
  266. public function getDomian(string $name, string $region = null)
  267. {
  268. try {
  269. $this->storageRegion = $region;
  270. $res = $this->app()->getBucketPolicy([
  271. 'Bucket' => $name
  272. ]);
  273. return $res['DomainName'] ?? [];
  274. } catch (\Throwable $e) {
  275. return $this->setError($e->getMessage());
  276. }
  277. }
  278. public function bindDomian(string $name, string $domain, string $region = null)
  279. {
  280. try {
  281. $this->storageRegion = $region;
  282. $this->app()->putBucketWebsite([
  283. 'Bucket' => $name,
  284. 'WebsiteConfiguration' => [
  285. 'RedirectAllRequestsTo' => [
  286. 'HostName' => $domain,
  287. 'Protocol' => 'http'
  288. ]
  289. ]
  290. ]);
  291. return true;
  292. } catch (\Throwable $e) {
  293. return $this->setError($e->getMessage());
  294. }
  295. }
  296. public function setBucketCors(string $name, string $region)
  297. {
  298. $this->storageRegion = $region;
  299. try {
  300. $this->app()->putBucketCors([
  301. 'Bucket' => $name, // REQUIRED
  302. 'CORSConfiguration' => [ // REQUIRED
  303. 'CORSRules' => [ // REQUIRED
  304. [
  305. 'AllowedHeaders' => ['*'],
  306. 'AllowedMethods' => ['POST', 'GET', 'PUT', 'DELETE', 'HEAD'], // REQUIRED
  307. 'AllowedOrigins' => ['*'], // REQUIRED
  308. 'ExposeHeaders' => ['Etag'],
  309. 'MaxAgeSeconds' => 0
  310. ],
  311. ],
  312. ]
  313. ]);
  314. return true;
  315. } catch (\Throwable $e) {
  316. return $this->setError($e->getMessage());
  317. }
  318. }
  319. /**
  320. * 获取OSS上传密钥
  321. * @return mixed|void
  322. */
  323. public function getTempKeys($key = '', $path = '', $contentType = '', $expires = '+10 minutes')
  324. {
  325. try {
  326. $app = $this->app();
  327. $cmd = $app->getCommand(
  328. 'PutObject', [
  329. 'Bucket' => $this->storageName,
  330. 'Key' => $key,
  331. // 'SourceFile' => $path,
  332. 'ContentType' => $contentType
  333. ]
  334. );
  335. $request = $app->createPresignedRequest($cmd, $expires, ['Scheme' => 'https']);
  336. return [
  337. 'upload_url' => (string)$request->getUri(),
  338. 'type' => 'JDOSS',
  339. 'url' => $this->uploadUrl . '/' . $key
  340. ];
  341. } catch (\Throwable $e) {
  342. return $this->setError($e->getMessage());
  343. }
  344. }
  345. /**
  346. * 缩略图
  347. * @param string $filePath
  348. * @param string $fileName
  349. * @param string $type
  350. * @return array|mixed
  351. */
  352. public function thumb(string $filePath = '', string $fileName = '', string $type = 'all')
  353. {
  354. $filePath = $this->getFilePath($filePath);
  355. $data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
  356. $this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
  357. if ($filePath) {
  358. $config = $this->thumbConfig;
  359. foreach ($this->thumb as $v) {
  360. if ($type == 'all' || $type == $v) {
  361. $height = 'thumb_' . $v . '_height';
  362. $width = 'thumb_' . $v . '_width';
  363. $key = 'filePath' . ucfirst($v);
  364. if (sys_config('image_thumbnail_status', 1) && isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
  365. $this->fileInfo->$key = $filePath . '?x-oss-process=image/resize,h_' . $config[$height] . ',w_' . $config[$width];
  366. $this->fileInfo->$key = $this->water($this->fileInfo->$key);
  367. $data[$v] = $this->fileInfo->$key;
  368. } else {
  369. $this->fileInfo->$key = $this->water($this->fileInfo->$key);
  370. $data[$v] = $this->fileInfo->$key;
  371. }
  372. }
  373. }
  374. }
  375. return $data;
  376. }
  377. /**
  378. * 水印
  379. * @param string $filePath
  380. * @return mixed|string
  381. */
  382. public function water(string $filePath = '')
  383. {
  384. $filePath = $this->getFilePath($filePath);
  385. $waterConfig = $this->waterConfig;
  386. $waterPath = $filePath;
  387. if ($waterConfig['image_watermark_status'] && $filePath) {
  388. if (strpos($filePath, '?x-oss-process') === false) {
  389. $filePath .= '?x-oss-process=image';
  390. }
  391. switch ($waterConfig['watermark_type']) {
  392. case 1://图片
  393. if (!$waterConfig['watermark_image']) {
  394. throw new AdminException(400722);
  395. }
  396. $waterPath = $filePath .= '/watermark,image_' . base64_encode($waterConfig['watermark_image']) . ',t_' . $waterConfig['watermark_opacity'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
  397. break;
  398. case 2://文字
  399. if (!$waterConfig['watermark_text']) {
  400. throw new AdminException(400723);
  401. }
  402. $waterConfig['watermark_text_color'] = str_replace('#', '', $waterConfig['watermark_text_color']);
  403. $waterPath = $filePath .= '/watermark,text_' . base64_encode($waterConfig['watermark_text']) . ',color_' . $waterConfig['watermark_text_color'] . ',size_' . $waterConfig['watermark_text_size'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
  404. break;
  405. }
  406. }
  407. return $waterPath;
  408. }
  409. }