UploadHelper.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. <?php
  2. namespace common\helpers;
  3. use Yii;
  4. use yii\imagine\Image;
  5. use yii\web\UploadedFile;
  6. use yii\web\NotFoundHttpException;
  7. use yii\helpers\Json;
  8. use linslin\yii2\curl;
  9. use common\models\common\Attachment;
  10. use common\components\uploaddrive\DriveInterface;
  11. /**
  12. * 上传辅助类
  13. *
  14. * Class UploadHelper
  15. * @package common\helpers
  16. * @author qimall
  17. */
  18. class UploadHelper
  19. {
  20. /**
  21. * 切片合并缓存前缀
  22. */
  23. const PREFIX_MERGE_CACHE = 'upload-file-guid:';
  24. /**
  25. * 上传配置
  26. *
  27. * @var array
  28. */
  29. public $config = [];
  30. /**
  31. * 上传路径
  32. *
  33. * @var array
  34. */
  35. public $paths = [];
  36. /**
  37. * 默认取 $_FILE['file']
  38. *
  39. * @var string
  40. */
  41. public $uploadFileName = 'file';
  42. /**
  43. * 上传驱动
  44. *
  45. * @var
  46. */
  47. protected $drive = 'local';
  48. /**
  49. * 拿取需要的数据
  50. *
  51. * @var array
  52. */
  53. protected $filter = [
  54. 'thumb',
  55. 'drive',
  56. 'chunks',
  57. 'chunk',
  58. 'guid',
  59. 'image',
  60. 'compress',
  61. 'width',
  62. 'height',
  63. 'md5',
  64. 'poster',
  65. 'writeTable',
  66. 'mall_id'
  67. ];
  68. /**
  69. * 上传文件基础信息
  70. *
  71. * @var array
  72. */
  73. protected $baseInfo = [
  74. 'domain_name' => '',
  75. 'name' => '',
  76. 'old_name' => '',
  77. 'width' => '',
  78. 'height' => '',
  79. 'size' => 0,
  80. 'extension' => 'jpg',
  81. 'url' => '',
  82. 'merge' => false,
  83. 'guid' => '',
  84. 'type' => 'image/jpeg',
  85. 'thumb_url' => [],
  86. 'duration' => '', // 视频时长
  87. ];
  88. /**
  89. * 是否切片上传
  90. *
  91. * @var bool
  92. */
  93. protected $isCut = false;
  94. /**
  95. * @var DriveInterface
  96. */
  97. protected $uploadDrive;
  98. /**
  99. * @var \League\Flysystem\Filesystem
  100. */
  101. protected $filesystem;
  102. /**
  103. * UploadHelper constructor.
  104. * @param array $config
  105. * @param string $type 文件类型
  106. * @param bool $superaddition 追加写入
  107. * @throws \Exception
  108. */
  109. public function __construct(array $config, $type, $superaddition = false)
  110. {
  111. // 过滤数据
  112. $this->filter($config, $type);
  113. // 设置文件类型
  114. $this->type = $type;
  115. // 初始化上传地址
  116. $this->initPaths();
  117. // 判断是否切片上传
  118. if (isset($this->config['chunks']) && isset($this->config['guid'])) {
  119. $this->drive = 'local';
  120. $this->isCut = true;
  121. }
  122. $drive = $this->drive;
  123. $config['superaddition'] = $superaddition;
  124. $this->uploadDrive = Yii::$app->uploadDrive->$drive($config);
  125. $this->filesystem = $this->uploadDrive->entity();
  126. }
  127. /**
  128. * 验证文件
  129. *
  130. * @throws NotFoundHttpException
  131. */
  132. public function verifyFile()
  133. {
  134. $file = UploadedFile::getInstanceByName($this->uploadFileName);
  135. if (!$file) {
  136. throw new NotFoundHttpException('找不到上传文件');
  137. }
  138. if ($file->getHasError()) {
  139. throw new NotFoundHttpException('上传失败,请检查文件');
  140. }
  141. $this->baseInfo['extension'] = $file->getExtension();
  142. $this->baseInfo['size'] = $file->size;
  143. empty($this->baseInfo['name']) && $this->baseInfo['name'] = $file->getBaseName();
  144. $this->baseInfo['old_name'] = $file->baseName;
  145. $baseInfoName = $this->baseInfo['name'];
  146. if($file->getExtension() === "mp3"){ // 如果类型是MP3,作特殊处理,因为部分苹果手机用小程序,插件识别不了中文名称的音乐
  147. $baseInfoName = $this->zhToAbbr($this->baseInfo['name']);
  148. }
  149. $this->baseInfo['url'] = $this->paths['relativePath'] . $baseInfoName . '.' . $file->getExtension();
  150. $this->baseInfo['path_dir'] = $this->paths['relativePath'];
  151. unset($file);
  152. $this->verify();
  153. }
  154. /**
  155. * MP3中文转首字母,如“我123”,转为“W123”
  156. */
  157. public function zhToAbbr($str){
  158. $result = '';
  159. for ($i = 0; $i < mb_strlen($str); $i++) {
  160. $char = mb_substr($str, $i, 1, 'UTF-8');
  161. if (preg_match('/[a-zA-Z0-9]/', $char)) {
  162. $result .= strtoupper($char); // 字母/数字直接保留
  163. } else {
  164. $result .= $this->getFirstCharter($char); // 中文转首字母
  165. }
  166. }
  167. return $result.time();
  168. }
  169. /**
  170. * 中文转首字母
  171. */
  172. public function getFirstCharter($str) {
  173. if (empty($str)) { return '';}
  174. // 静态拼音首字母映射表(GB2312编码区间 → 字母)
  175. static $pinyinMap = [
  176. [-20319, -20284, 'A'],[-20283, -19776, 'B'],[-19775, -19219, 'C'],[-19218, -18711, 'D'],[-18710, -18527, 'E'],[-18526, -18240, 'F'],[-18239, -17923, 'G'],
  177. [-17922, -17418, 'H'],[-17417, -16475, 'J'],[-16474, -16213, 'K'],[-16212, -15641, 'L'],[-15640, -15166, 'M'],[-15165, -14923, 'N'],[-14922, -14915, 'O'],
  178. [-14914, -14631, 'P'],[-14630, -14150, 'Q'],[-14149, -14091, 'R'],[-14090, -13319, 'S'],[-13318, -12839, 'T'],[-12838, -12557, 'W'],[-12556, -11848, 'X'],
  179. [-11847, -11056, 'Y'],[-11055, -10247, 'Z'],
  180. ];
  181. // 如果是A-Z或a-z直接返回大写
  182. $firstChar = $str[0];
  183. $fchar = ord($firstChar);
  184. if (($fchar >= ord('A') && $fchar <= ord('Z')) || ($fchar >= ord('a') && $fchar <= ord('z'))) {
  185. return strtoupper($firstChar);
  186. }
  187. // 转换为GB2312编码计算区间
  188. $s1 = iconv('UTF-8', 'gb2312//IGNORE', $str);
  189. $s2 = iconv('gb2312', 'UTF-8//IGNORE', $s1);
  190. $s = ($s2 == $str) ? $s1 : $str;
  191. if (strlen($s) < 2) { return '';}
  192. $asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
  193. // 遍历映射表匹配区间
  194. foreach ($pinyinMap as $range) {
  195. if ($asc >= $range[0] && $asc <= $range[1]) {
  196. return $range[2];
  197. }
  198. }
  199. return '';
  200. }
  201. /**
  202. * 验证Url
  203. *
  204. * @param $url
  205. * @return bool
  206. * @throws NotFoundHttpException
  207. */
  208. public function verifyUrl($url)
  209. {
  210. $imgUrl = str_replace("&amp;", "&", htmlspecialchars($url));
  211. // http开头验证
  212. if (strpos($imgUrl, "http") !== 0) {
  213. throw new NotFoundHttpException('不是一个http地址');
  214. }
  215. preg_match('/(^https?:\/\/[^:\/]+)/', $imgUrl, $matches);
  216. $host_with_protocol = count($matches) > 1 ? $matches[1] : '';
  217. // 判断是否是合法 url
  218. if (!filter_var($host_with_protocol, FILTER_VALIDATE_URL)) {
  219. throw new NotFoundHttpException('Url不合法');
  220. }
  221. preg_match('/^https?:\/\/(.+)/', $host_with_protocol, $matches);
  222. $host_without_protocol = count($matches) > 1 ? $matches[1] : '';
  223. // 此时提取出来的可能是 IP 也有可能是域名,先获取 IP
  224. $ip = gethostbyname($host_without_protocol);
  225. // 获取请求头并检测死链
  226. $heads = get_headers($imgUrl, 1);
  227. if (!(stristr($heads[0], "200") && stristr($heads[0], "OK"))) {
  228. throw new NotFoundHttpException('文件获取失败');
  229. }
  230. // Content-Type验证
  231. if (!isset($heads['Content-Type']) || !stristr($heads['Content-Type'], "image")) {
  232. throw new NotFoundHttpException('格式验证失败');
  233. }
  234. $extend = StringHelper::clipping($imgUrl, '.', 1);
  235. if (!in_array($extend, Yii::$app->params['uploadConfig']['images']['extensions'])) {
  236. $extend = 'jpg';
  237. }
  238. $curl = new curl\Curl();
  239. $img = $curl->get($imgUrl);
  240. $this->baseInfo['extension'] = $extend;
  241. $this->baseInfo['size'] = strlen($img);
  242. $this->config['md5'] = md5($img);
  243. $this->baseInfo['url'] = $this->paths['relativePath'] . $this->baseInfo['name'] . '.' . $extend;
  244. $this->verify();
  245. return $img;
  246. }
  247. /**
  248. * 验证base64格式的内容
  249. *
  250. * @param $data
  251. * @param $extend
  252. * @throws NotFoundHttpException
  253. */
  254. public function verifyBase64($data, $extend)
  255. {
  256. $this->baseInfo['extension'] = $extend;
  257. $this->baseInfo['size'] = strlen($data);
  258. $this->baseInfo['url'] = $this->paths['relativePath'] . $this->baseInfo['name'] . '.' . $extend;
  259. $this->verify();
  260. unset($data, $extend);
  261. }
  262. /**
  263. * 验证文件大小及类型
  264. *
  265. * @throws NotFoundHttpException
  266. */
  267. protected function verify()
  268. {
  269. if ($this->baseInfo['size'] > $this->config['maxSize'] && $this->config['maxSize'] > 0) {
  270. throw new NotFoundHttpException('文件大小超出网站限制');
  271. }
  272. if (!empty($this->config['extensions']) && !in_array($this->baseInfo['extension'],
  273. $this->config['extensions'])) {
  274. throw new NotFoundHttpException('文件类型不允许');
  275. }
  276. // 存储本地进行安全校验
  277. if ($this->drive == Attachment::DRIVE_LOCAL) {
  278. if ($this->type == Attachment::UPLOAD_TYPE_FILES && in_array($this->baseInfo['extension'],
  279. $this->config['blacklist'])) {
  280. throw new NotFoundHttpException('上传的文件类型不允许');
  281. }
  282. }
  283. }
  284. /**
  285. * 写入
  286. *
  287. * @param bool $data
  288. * @throws NotFoundHttpException
  289. * @throws \League\Flysystem\FileExistsException
  290. * @throws \League\Flysystem\FileNotFoundException
  291. */
  292. public function save($data = false)
  293. {
  294. // 获取域名
  295. $this->baseInfo = $this->uploadDrive->getDomainName($this->baseInfo, $this->drive, $this->config['fullPath']);
  296. // 拦截 如果是切片上传就接管
  297. if ($this->isCut == true) {
  298. $this->cut();
  299. return;
  300. }
  301. // 判断如果文件存在就重命名文件名
  302. // if ($this->filesystem->has($this->baseInfo['url'])) {
  303. // $name = explode('_', $this->baseInfo['name']);
  304. // $this->baseInfo['name'] = $name[0] . '_' . time() . '_' . StringHelper::random(8);
  305. // $this->baseInfo['url'] = $this->paths['relativePath'] . $this->baseInfo['name'] . '.' . $this->baseInfo['extension'];
  306. // }
  307. // 视频上传获取封面图
  308. if ($this->type == Attachment::UPLOAD_TYPE_VIDEOS) {
  309. // $this->getVideoPoster();
  310. $this->getVideoInfo();
  311. }
  312. // 判断是否直接写入
  313. if (false === $data) {
  314. $file = UploadedFile::getInstanceByName($this->uploadFileName);
  315. if (!$file->getHasError()) {
  316. $stream = fopen($file->tempName, 'r+');
  317. $result = Yii::$app->uploadDrive->local([])->entity()->writeStream($this->baseInfo['url'], $stream);
  318. $this->compress();
  319. if($this->drive != 'local'){
  320. $stream = fopen(Yii::getAlias("@attachment/") . $this->baseInfo['url'], 'r+');
  321. $result = $this->filesystem->writeStream($this->baseInfo['url'], $stream);
  322. unlink(Yii::getAlias("@attachment/") . $this->baseInfo['url']);
  323. }
  324. if (!$result) {
  325. throw new NotFoundHttpException('文件写入失败');
  326. }
  327. if (is_resource($stream)) {
  328. fclose($stream);
  329. }
  330. } else {
  331. throw new NotFoundHttpException('上传失败,可能文件太大了');
  332. }
  333. } else {
  334. $result = $this->filesystem->write($this->baseInfo['url'], $data);
  335. if (!$result) {
  336. throw new NotFoundHttpException('文件写入失败');
  337. }
  338. }
  339. // 本地的图片才可执行
  340. if ($this->type == 'images' && $this->drive == 'local') {
  341. // 图片水印
  342. $this->watermark();
  343. // 图片压缩
  344. $this->compress();
  345. // 创建缩略图
  346. $this->thumb();
  347. // 获取图片信息
  348. if (empty($this->baseInfo['width']) && empty($this->baseInfo['height']) && $this->filesystem->has($this->baseInfo['url'])) {
  349. $imgInfo = getimagesize(Yii::getAlias('@attachment') . '/' . $this->baseInfo['url']);
  350. $this->baseInfo['width'] = $imgInfo[0] ?? 0;
  351. $this->baseInfo['height'] = $imgInfo[1] ?? 0;
  352. }
  353. }
  354. return;
  355. }
  356. /**
  357. * 获取视频封面图
  358. *
  359. * @return bool
  360. * @throws \League\Flysystem\FileExistsException
  361. * @throws NotFoundHttpException
  362. */
  363. public function getVideoPoster()
  364. {
  365. // use `ffmpeg` get first frame as video poster
  366. // save poster local and upload to cloud, return the cloud url
  367. $file = UploadedFile::getInstanceByName($this->uploadFileName);
  368. if ($file->error === UPLOAD_ERR_OK) {
  369. $this->type = Attachment::UPLOAD_TYPE_IMAGES;
  370. $name = $this->baseInfo['name'];
  371. $this->baseInfo['name'] = $this->baseInfo['name'] . '_poster';
  372. $extension = $this->baseInfo['extension'];
  373. $url = $this->baseInfo['url'];
  374. $this->baseInfo['extension'] = 'jpg';
  375. $this->baseInfo['url'] = $this->paths['relativePath'] . $this->baseInfo['name'] . '.' . $this->baseInfo['extension'];
  376. $tmpPosterFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $this->baseInfo['name'] . '.' . $this->baseInfo['extension'];
  377. FfmpegHelper::imageResize($file->tempName, $tmpPosterFilePath, 1);
  378. if (file_exists($tmpPosterFilePath)) {
  379. $stream = fopen($tmpPosterFilePath, 'r+');
  380. $result = $this->filesystem->writeStream($this->baseInfo['url'], $stream);
  381. if (!$result) {echo '文件写入失败';
  382. throw new NotFoundHttpException('文件写入失败');
  383. }
  384. if (is_resource($stream)) {
  385. fclose($stream);
  386. }
  387. $imgInfo = getimagesize($tmpPosterFilePath);
  388. $this->baseInfo['width'] = $imgInfo[0] ?? 0;
  389. $this->baseInfo['height'] = $imgInfo[1] ?? 0;
  390. unlink($tmpPosterFilePath); // delete tmp file
  391. // 还原
  392. $this->type = Attachment::UPLOAD_TYPE_VIDEOS;
  393. $this->baseInfo['thumb_url'][] = $this->baseInfo['domain_name'] . $this->baseInfo['url'];
  394. $this->baseInfo['url'] = $url;
  395. $this->baseInfo['name'] = $name;
  396. $this->baseInfo['extension'] = $extension;
  397. return true;
  398. }
  399. }
  400. return false;
  401. }
  402. /**
  403. * @name:
  404. * @msg: 获取视频时长
  405. * @param {*}
  406. * @return {*}
  407. */
  408. public function getVideoInfo()
  409. {
  410. $file = UploadedFile::getInstanceByName($this->uploadFileName);
  411. if ($file->error === UPLOAD_ERR_OK) {
  412. $tmp_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $this->baseInfo['name'] . '.' . $this->baseInfo['extension'];
  413. copy($file->tempName, $tmp_file);
  414. $video_info = FfmpegHelper::getVideoInfo($tmp_file);
  415. // dd($video_info);
  416. $this->baseInfo['duration_str'] = $video_info['duration']??'0';
  417. $this->baseInfo['duration_seconds'] = $video_info['seconds']??'0';
  418. @unlink($tmp_file);
  419. }
  420. }
  421. /**
  422. * 水印
  423. *
  424. * @param $fullPathName
  425. * @return bool
  426. */
  427. protected function watermark()
  428. {
  429. return true;
  430. if (Yii::$app->debris->backendConfig('sys_image_watermark_status') != true) {
  431. return true;
  432. }
  433. // 原图路径
  434. $absolutePath = Yii::getAlias("@attachment/") . $this->baseInfo['url'];
  435. $local = Yii::$app->debris->backendConfig('sys_image_watermark_location');
  436. $watermarkImg = StringHelper::getLocalFilePath(Yii::$app->debris->backendConfig('sys_image_watermark_img'));
  437. if ($coordinate = DebrisHelper::getWatermarkLocation($absolutePath, $watermarkImg, $local)) {
  438. // $aliasName = StringHelper::getAliasUrl($fullPathName, 'watermark');
  439. Image::watermark($absolutePath, $watermarkImg, $coordinate)
  440. ->save($absolutePath, ['quality' => 100]);
  441. }
  442. return true;
  443. }
  444. /**
  445. * 压缩
  446. *
  447. * @param $fullPathName
  448. * @return bool
  449. */
  450. protected function compress()
  451. {
  452. $compress = false;
  453. $mall_id = $this->config['mall_id']??0;
  454. $mall_limit = \Yii::$app->services->setting->mall->get($mall_id,'limit');
  455. if(!empty($mall_limit['upload_images_compress'])){
  456. $compress = true;
  457. }
  458. if ($compress != true) {
  459. return true;
  460. }
  461. // 原图路径
  462. $absolutePath = Yii::getAlias("@attachment/") . $this->baseInfo['url'];
  463. $imgInfo = getimagesize($absolutePath);
  464. $compressibility = $this->config['compressibility'];
  465. $tmpMinSize = 0;
  466. if (empty($compressibility)) return true;
  467. foreach ($compressibility as $key => $item) {
  468. if ($this->baseInfo['size'] >= $tmpMinSize && $this->baseInfo['size'] < $key && $item < 100) {
  469. // $aliasName = StringHelper::getAliasUrl($fullPathName, 'compress');
  470. if($imgInfo['mime']=='image/png'){
  471. $imgInfo[0] = ($item+10)/100*$imgInfo[0];
  472. $imgInfo[1] = ($item+10)/100*$imgInfo[1];
  473. }
  474. Image::thumbnail($absolutePath, $imgInfo[0], $imgInfo[1])
  475. ->save($absolutePath, ['quality' => $item]);
  476. break;
  477. }
  478. $tmpMinSize = $key;
  479. }
  480. return true;
  481. }
  482. /**
  483. * 缩略图
  484. *
  485. * @return bool
  486. */
  487. protected function thumb()
  488. {
  489. if (empty($this->config['thumb'])) {
  490. \Yii::error('thumb empty');
  491. return true;
  492. }
  493. // 原图路径
  494. $absolutePath = Yii::getAlias("@attachment/") . $this->baseInfo['url'];
  495. // 缩略图路径
  496. $path = Yii::getAlias("@attachment/") . $this->paths['thumbRelativePath'];
  497. FileHelper::mkdirs($path);
  498. $thumbPath = $path . $this->baseInfo['name'] . '.' . $this->baseInfo['extension'];
  499. foreach ($this->config['thumb'] as $value) {
  500. $thumbFullPath = StringHelper::createThumbUrl($thumbPath, $value['width'], $value['height']);
  501. // 裁剪从坐标0,60 裁剪一张300 x 20 的图片,并保存 不设置坐标则从坐标0,0开始
  502. // Image::crop($originalPath, $thumbWidth , $thumbHeight, [0, 60])->save($thumbOriginalPath), ['quality' => 100]);
  503. Image::thumbnail($absolutePath, $value['width'], $value['height'])->save($thumbFullPath);
  504. $this->baseInfo['thumb_url'][] = $thumbFullPath;
  505. }
  506. return true;
  507. }
  508. /**
  509. * 切片
  510. *
  511. * @throws \League\Flysystem\FileExistsException
  512. * @throws \League\Flysystem\FileNotFoundException
  513. */
  514. public function cut()
  515. {
  516. // 切片参数
  517. $chunk = $this->config['chunk'] + 1;
  518. $guid = $this->config['guid'];
  519. // 临时文件夹路径
  520. $url = $this->paths['tmpRelativePath'] . $chunk . '.' . $this->baseInfo['extension'];
  521. // 上传
  522. $file = UploadedFile::getInstanceByName($this->uploadFileName);
  523. if ($file->error === UPLOAD_ERR_OK) {
  524. $stream = fopen($file->tempName, 'r+');
  525. $result = $this->filesystem->writeStream($url, $stream);
  526. fclose($stream);
  527. // 判断如果上传成功就去合并文件
  528. $this->baseInfo['chunk'] = $chunk;
  529. if ($this->config['chunks'] == $chunk) {
  530. // 缓存上传信息等待回调
  531. Yii::$app->cache->set(self::PREFIX_MERGE_CACHE . $guid, [
  532. 'type' => $this->type,
  533. 'drive' => $this->drive,
  534. 'paths' => $this->paths,
  535. 'baseInfo' => $this->baseInfo,
  536. 'config' => $this->config,
  537. ], 3600);
  538. }
  539. $this->baseInfo['merge'] = true;
  540. $this->baseInfo['guid'] = $guid;
  541. }
  542. }
  543. /**
  544. * 切片合并
  545. *
  546. * @param int $name
  547. * @throws \League\Flysystem\FileExistsException
  548. * @throws \League\Flysystem\FileNotFoundException
  549. */
  550. public function merge($name = 1)
  551. {
  552. // 由于合并会附带上一次切片的信息,取消切片判断
  553. $this->isCut = false;
  554. $filePath = $this->paths['tmpRelativePath'] . $name . '.' . $this->baseInfo['extension'];
  555. if ($this->filesystem->has($filePath) && ($content = $this->filesystem->read($filePath))) {
  556. if ($this->filesystem->has($this->baseInfo['url'])) {
  557. $this->filesystem->update($this->baseInfo['url'], $content);
  558. } else {
  559. $this->filesystem->write($this->baseInfo['url'], $content);
  560. }
  561. unset($content);
  562. $this->filesystem->delete($filePath);
  563. $name += 1;
  564. self::merge($name);
  565. } else {
  566. // 删除文件夹,如果删除失败重新去合并
  567. $this->filesystem->deleteDir($this->paths['tmpRelativePath']);
  568. }
  569. }
  570. /**
  571. * 获取生成路径信息
  572. *
  573. * @return array
  574. */
  575. protected function initPaths()
  576. {
  577. if (!empty($this->paths)) {
  578. return $this->paths;
  579. }
  580. $config = $this->config;
  581. // 保留原名称
  582. $config['originalName'] == false && $this->baseInfo['name'] = $config['prefix'] . time() . '_' . StringHelper::random(8);
  583. // 文件路径
  584. $mall_config = !empty($config['mall_id']) ? $config['mall_id'] . '/' : '';
  585. $filePath = $config['path'] . $mall_config . date($config['subName'], time()) . "/";
  586. // 缩略图
  587. $thumbPath = Yii::$app->params['uploadConfig']['thumb']['path'] . $config['mall_id'] . '/' . date($config['subName'], time()) . "/";
  588. empty($config['guid']) && $config['guid'] = StringHelper::random(8);
  589. $tmpPath = 'tmp/' . $config['mall_id'] . '/' . date($config['subName'], time()) . "/" . $config['guid'] . '/';
  590. $this->paths = [
  591. 'relativePath' => $filePath, // 相对路径
  592. 'thumbRelativePath' => $thumbPath, // 缩略图相对路径
  593. 'tmpRelativePath' => $tmpPath, // 临时相对路径
  594. ];
  595. return $this->paths;
  596. }
  597. /**
  598. * 过滤数据
  599. *
  600. * @param $config
  601. */
  602. protected function filter($config, $type)
  603. {
  604. $mall_id = $config['mall_id'];
  605. try {
  606. // 解密json
  607. foreach ($config as $key => &$item) {
  608. if (!empty($item) && !is_numeric($item) && !is_array($item)) {
  609. !empty(json_decode($item)) && $item = Json::decode($item);
  610. }
  611. }
  612. $config = ArrayHelper::filter($config, $this->filter);
  613. $this->config = ArrayHelper::merge(Yii::$app->params['uploadConfig'][$type], $config);
  614. // 参数
  615. $this->baseInfo['width'] = $this->config['width'] ?? 0;
  616. $this->baseInfo['height'] = $this->config['height'] ?? 0;
  617. } catch (\Exception $e) {
  618. $this->config = Yii::$app->params['uploadConfig'][$type];
  619. }
  620. !empty($this->config['drive']) && $this->drive = $this->config['drive'];
  621. $platform_limit = \Yii::$app->services->setting->platform->get('limit');
  622. $mall_limit = \Yii::$app->services->setting->mall->get($mall_id,'limit');
  623. switch ($type){
  624. case 'videos':
  625. $upload_type = 'video';
  626. break;
  627. case 'images':
  628. $upload_type = 'img';
  629. break;
  630. }
  631. if(isset($upload_type)){
  632. if(!empty($mall_limit[$upload_type])){
  633. $this->config['maxSize'] = $mall_limit[$upload_type]*1024 * 1024;
  634. }else{
  635. if(!empty($platform_limit[$upload_type])){
  636. $this->config['maxSize'] = $platform_limit[$upload_type]*1024 * 1024;
  637. }
  638. }
  639. }
  640. }
  641. /**
  642. * 写入目录
  643. *
  644. * @param array $paths
  645. */
  646. public function setPaths(array $paths)
  647. {
  648. $this->paths = $paths;
  649. }
  650. /**
  651. * 写入基础信息
  652. *
  653. * @param array $baseInfo
  654. */
  655. public function setBaseInfo(array $baseInfo)
  656. {
  657. $this->baseInfo = $baseInfo;
  658. }
  659. /**
  660. * @param mixed $drive
  661. */
  662. public function setDrive($drive)
  663. {
  664. $this->drive = $drive;
  665. }
  666. /**
  667. * @return array
  668. * @throws NotFoundHttpException
  669. * @throws \League\Flysystem\FileNotFoundException
  670. */
  671. public function getBaseInfo($mall_id,$mch_id=0,$group_id=0,$store_id=0,$addons_name='')
  672. {
  673. // 是否切片
  674. if ($this->isCut == true) {
  675. return $this->baseInfo;
  676. }
  677. // 处理上传的文件信息
  678. $this->baseInfo['type'] = $this->filesystem->getMimetype($this->baseInfo['url']);
  679. $this->baseInfo['size'] = $this->filesystem->getSize($this->baseInfo['url']);
  680. $this->baseInfo['url'] = $this->baseInfo['domain_name'] . $this->baseInfo['url'];
  681. $path = $this->baseInfo['url'];
  682. $data = [
  683. 'mall_id' => $mall_id,
  684. 'mch_id' => $mch_id,
  685. 'group_id' => $group_id,
  686. 'store_id' => $store_id,
  687. 'drive' => $this->drive,
  688. 'upload_type' => $this->type,
  689. 'size' => $this->baseInfo['size'],
  690. 'width' => $this->baseInfo['width'],
  691. 'height' => $this->baseInfo['height'],
  692. 'extension' => $this->baseInfo['extension'],
  693. 'name' => $this->baseInfo['name'],
  694. 'old_name' => $this->baseInfo['old_name'],
  695. 'md5' => $this->config['md5'] ?? '',
  696. 'url' => $this->baseInfo['url'],
  697. 'path' => $path,
  698. 'thumb_url' => !empty($this->baseInfo['thumb_url']) ? implode(',',$this->baseInfo['thumb_url']) : '',
  699. 'duration_str' => $this->baseInfo['duration_str'] ?? '',
  700. 'duration_seconds' => $this->baseInfo['duration_seconds'] ?? 0,
  701. 'addons_name' => $addons_name
  702. ];
  703. //阿里云oss 设置了cdn:修改图片地址为cdn
  704. $oss_cdn = \Yii::$app->params['oss_cdn'] ?? '';
  705. if($oss_cdn && $this->drive == Attachment::DRIVE_OSS){
  706. //替换oss 地址 => cdn 地址
  707. $data['url'] = str_replace($this->baseInfo['domain_name'],$oss_cdn,$data['url']);
  708. $data['path'] = str_replace($this->baseInfo['domain_name'],$oss_cdn,$data['path']);
  709. $this->baseInfo['url'] = $data['url'];
  710. }
  711. // 写入数据库
  712. $attachment_id = Yii::$app->services->attachment->create($data);
  713. $this->baseInfo['id'] = $attachment_id;
  714. $this->baseInfo['formatter_size'] = Yii::$app->formatter->asShortSize($this->baseInfo['size'], 2);
  715. $this->baseInfo['upload_type'] = self::formattingFileType($this->baseInfo['type'], $this->baseInfo['extension'], $this->type);
  716. return $this->baseInfo;
  717. }
  718. /**
  719. * @param $specific_type
  720. * @param $extension
  721. * @return string
  722. */
  723. public static function formattingFileType($specific_type, $extension, $upload_type)
  724. {
  725. if (preg_match("/^image/", $specific_type) && $extension != 'psd') {
  726. return Attachment::UPLOAD_TYPE_IMAGES;
  727. }
  728. return $upload_type;
  729. }
  730. /**
  731. * 删除文件
  732. * @param $path
  733. * @throws
  734. * @return
  735. */
  736. public function delete($path)
  737. {
  738. return $this->filesystem->delete($path);
  739. }
  740. /**
  741. * 本地上传
  742. * @Author: hua
  743. * @DateTime: 2021/10/14 10:30
  744. * @Copyright: copyright (c) 2021 广东七件事集团
  745. * @param $relative_filename //相对路径资源
  746. * @return mixed|void
  747. * @throws NotFoundHttpException
  748. * @throws \League\Flysystem\FileExistsException
  749. * @throws \League\Flysystem\FileNotFoundException
  750. */
  751. public function upload($relative_filename)
  752. {
  753. $file = Yii::getAlias('@attachment') . '/' .$relative_filename;
  754. $path_info = pathinfo($file);
  755. $this->paths['extension'] = $path_info['extension'];
  756. $this->baseInfo['url'] = $relative_filename;
  757. $stream = fopen($file, 'r+');
  758. $result = $this->filesystem->writeStream($this->baseInfo['url'], $stream);
  759. if (!$result) {
  760. throw new NotFoundHttpException('文件写入失败');
  761. }
  762. if (is_resource($stream)) {
  763. fclose($stream);
  764. }
  765. return $this->baseInfo['url'];
  766. }
  767. }