UploadFileService.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: ywl
  5. * Date: 2017/4/14
  6. * Time: 11:38
  7. */
  8. namespace App\Common\Services;
  9. use App\Exceptions\ApiException;
  10. use App\Models\BaseModel;
  11. use App\Services\CommonBaseService;
  12. use App\Common\Facades\OssFacade;
  13. use FFMpeg\Coordinate\TimeCode;
  14. use FFMpeg\FFMpeg;
  15. use Illuminate\Support\Facades\Log;
  16. use Illuminate\Support\Facades\Request;
  17. class UploadFileService extends CommonBaseService
  18. {
  19. /**
  20. * 文件mineType类型
  21. * @var array
  22. */
  23. private $mineTypeList = [];
  24. /**
  25. * 文件扩展名
  26. * @var array
  27. */
  28. private $fileExt = ['png', 'jpg', 'jpeg', 'gif', 'mp4', 'mp3', 'ico', '3gpp', 'ac3', '3gpp', 'ac3',
  29. 'asf', 'au', 'css', 'csv', 'doc', 'dot', 'dtd', 'dwg', 'dxf', 'gif', 'jp2', 'jpe',
  30. 'mp2', 'mp3', 'mp4', 'mpeg', 'mpg', 'mpp', 'ogg', 'pdf', 'pot', 'pps', 'ppt', 'rtf', 'svf',
  31. 'tif', 'tiff', 'txt', 'wdb', 'wps', 'xhtml', 'xlc', 'xlm', 'xls', 'xlt', 'xlw', 'xml',
  32. 'zip', 'xlsx','docx','webp'];
  33. /**
  34. * 文件扩展名
  35. * @var string
  36. */
  37. private $nowFileExt = '';
  38. /**
  39. * 文件限制大小,默认20M
  40. * @var int
  41. */
  42. private $limitSize = 1024 * 1024 * 100;
  43. /**
  44. * 保存的路径
  45. * @var string
  46. */
  47. private $basePath = './';
  48. /**
  49. * 设置文件路径
  50. * @var string
  51. */
  52. private $filePath = '/files';
  53. /**
  54. * 上传文件字段
  55. * @var string
  56. */
  57. private $fileField = 'file';
  58. public function __construct(BaseModel $model)
  59. {
  60. parent::__construct($model);
  61. if(config('app.upload_limit_size')) {
  62. $this->limitSize = 1024 * 1024 * config('app.upload_limit_size');
  63. }
  64. }
  65. /**
  66. * 获取本地路径
  67. * @param $file
  68. * @return string
  69. */
  70. public function getLocalPath($file = '')
  71. {
  72. $resourcesPath = 'upload';
  73. if(config('app.language_path')) {
  74. if(config('app.language_path') != 'zh-cn') {
  75. $resourcesPath = config('app.language_path').'/upload';
  76. }
  77. }
  78. return resource_path($resourcesPath) . $file;
  79. }
  80. /**
  81. * 上传图片文件
  82. * @param $request
  83. * @param string $fileField
  84. * @param array|string $fileExt
  85. * @param bool $isUploadOss
  86. * @return array
  87. */
  88. public function upload($request, $fileField = 'file', $fileExt = [], $isUploadOss = true,$isReName=false)
  89. {
  90. $this->fileField = $fileField;
  91. if (!empty($fileExt)) {
  92. $this->setFileExt($fileExt);
  93. }
  94. $this->validate($request);
  95. $result = $this->saveLocal($request,$isReName);
  96. $localPath=$this->getLocalPath().$result['path'];
  97. if (strpos($result['mimeType'], 'image') !== false) {
  98. list($width, $height) = getimagesize($localPath);
  99. $result['resolution'] = $width . '*' . $height;
  100. }
  101. $resourcesPath = '/resources';
  102. if(config('app.language_path')) {
  103. if(config('app.language_path') != 'zh-cn') {
  104. $resourcesPath = $resourcesPath.'/'.config('app.language_path');
  105. }
  106. }
  107. $result['url'] = $request->root() .$resourcesPath. $result['path'];
  108. $path = $this->getLocalPath() . $this->filePath;
  109. $useFfmpeg= config('app.USE_FFMPEG');
  110. if ($useFfmpeg&&!empty($result['mimeType'])&&strpos($result['mimeType'], 'video') !== false) {
  111. $ffmpeg=FFMpeg::create(array(
  112. 'ffmpeg.binaries' => config('app.ffmpeg.ffmpeg_binaries'),
  113. 'ffprobe.binaries' => config('app.ffmpeg.ffprobe_binaries'),
  114. 'timeout' =>config('app.ffmpeg.timeout'), // The timeout for the underlying process
  115. 'ffmpeg.threads' => config('app.ffmpeg.ffmpeg_threads'), // The number of threads that FFMpeg should use
  116. ));
  117. $picFilename = $this->getFilename($path,'jpg');
  118. $video = $ffmpeg->open($localPath);
  119. $fileSavePath=$path.'/'.$picFilename;
  120. $video->frame(TimeCode::fromSeconds(1))->save($fileSavePath);
  121. if(file_exists($fileSavePath)){
  122. $checkFileNameStr=str_replace('.'.$result['ext'],'',$picFilename);
  123. if (preg_match('/^.*[,\.#%\'\+\*\:;^`\{\}\(\)\[\]\s]/', $checkFileNameStr)) {
  124. $picFilename= preg_replace('/[,\.#%\'\+\*\:;^`\{\}\(\)\[\]\s]/','-', $checkFileNameStr).'.'.$result['ext'];
  125. }
  126. $coverOssUrl = OssFacade::uploadToOss($fileSavePath, $picFilename, md5_file($fileSavePath),'share_center',$isReName);
  127. $result['pic']=$coverOssUrl;//封面图
  128. }
  129. }
  130. if ($isUploadOss) {
  131. $filename = $result['original_filename'];
  132. $checkFileNameStr=str_replace('.'.$result['ext'],'',$filename);
  133. if (preg_match('/^.*[,\.#%\'\+\*\:;^`\{\}\(\)\[\]\s]/', $checkFileNameStr)) {
  134. $filename= preg_replace('/[,\.#%\'\+\*\:;^`\{\}\(\)\[\]\s]/','-', $checkFileNameStr).'.'.$result['ext'];
  135. }
  136. $ossUrl = OssFacade::uploadToOss($localPath, $filename, md5(file_get_contents($localPath)),'share_center',$isReName);
  137. $result['url'] = $ossUrl;
  138. //Log::info(json_encode($result));
  139. if (!empty($result['mimeType'])&&strpos($result['mimeType'], 'video') !== false) {
  140. // 视频截图
  141. $picFilename = $this->getFilename($path,'jpg');
  142. $fileSavePath = $path.'/'.$picFilename;
  143. downloadFile($ossUrl.'?x-oss-process=video/snapshot,t_1000,f_jpg,w_800,h_600,m_fast', $fileSavePath);
  144. if (file_exists($fileSavePath)) {
  145. $coverOssUrl = OssFacade::uploadToOss($fileSavePath, $picFilename, md5_file($fileSavePath),'share_center',$isReName);
  146. $result['pic']=$coverOssUrl;//封面图
  147. }
  148. }
  149. }
  150. return $result;
  151. }
  152. /**
  153. * 上传Base64图片
  154. * @param $request
  155. * @param string $fileField
  156. * @param array|string $fileExt
  157. * @param bool $isUploadOss
  158. * @return array
  159. */
  160. public function uploadImgBase64($data, $fileExt = 'jpg', $host='',$selectFileName='',$isUploadOss = true)
  161. {
  162. if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $data, $result)) {
  163. $type = $result[2];
  164. if (in_array($type, array('pjpeg', 'jpeg', 'jpg', 'gif', 'bmp', 'png'))) {
  165. $this->nowFileExt = strtolower($type);
  166. $path = $this->filePath;//$this->basePath.$this->filePath;
  167. $path.="/" .date('md') . "/".uniqid()."/";
  168. $localPath=$this->getLocalPath().$path;
  169. if(empty($selectFileName)){
  170. $filename = $this->getFilename($localPath);
  171. }else{
  172. $filename= $selectFileName.'.'.$this->nowFileExt;
  173. }
  174. $filepath = $localPath . '/' . $filename;
  175. if (!file_exists($localPath)) {
  176. mkdir($localPath, 0777, true);
  177. }
  178. if (file_put_contents($filepath, base64_decode(str_replace($result[1], '', $data)))) {
  179. $size = filesize($filepath);
  180. $s = getimagesize($filepath);
  181. $width = empty($s[0]) ? '' : $s[0];
  182. $height = empty($s[1]) ? '' : $s[1];
  183. $result = [
  184. 'path' => $path.$filename,
  185. 'size' => $size,
  186. 'filename' => $filename,
  187. 'original_filename' => $filename,
  188. 'ext' => $this->nowFileExt,
  189. 'resolution' => $width . '*' . $height,
  190. 'url' => $host.'/resources'.$path.$filename,
  191. 'mimeType' => 'image/' . $this->nowFileExt
  192. ];
  193. if ($isUploadOss) {
  194. $ossUrl = OssFacade::uploadToOss($filepath, $filename, md5(file_get_contents($filepath)));
  195. $result['url'] = $ossUrl;
  196. }
  197. return $result;
  198. // echo '图片上传成功</br><img src="' .$img_path. '">';
  199. } else {
  200. throw new ApiException(1101);
  201. }
  202. } else {
  203. //文件类型错误
  204. throw new ApiException(1101);
  205. }
  206. } else {
  207. //文件错误
  208. throw new ApiException(1101);
  209. }
  210. return false;
  211. }
  212. /**
  213. * 上传文本转为文件
  214. * @param $request
  215. * @param string $fileField
  216. * @param array|string $fileExt
  217. * @param bool $isUploadOss
  218. * @return array
  219. */
  220. public function uploadTextToFile($data, $fileExt = '', $isUploadOss = true)
  221. {
  222. if (!empty($fileExt)) {
  223. $this->nowFileExt = strtolower($fileExt);
  224. $path = $this->getLocalPath() . $this->filePath;
  225. $filename = $this->getFilename($path);
  226. $filepath = $path . '/' . $filename;
  227. if (!file_exists($path)) {
  228. mkdir($path, 0777, true);
  229. }
  230. if (file_put_contents($filepath, $data)) {
  231. $size = filesize($filepath);
  232. $s = getimagesize($filepath);
  233. $result = [
  234. 'path' => $filepath,
  235. 'size' => $size,
  236. 'filename' => $filename,
  237. 'original_filename' => $filename,
  238. 'ext' => $this->nowFileExt,
  239. 'url' => ''
  240. ];
  241. if ($isUploadOss) {
  242. $ossUrl = OssFacade::uploadToOss($filepath, $filename, md5(file_get_contents($filepath)));
  243. $result['url'] = $ossUrl;
  244. }
  245. return $result;
  246. }
  247. }
  248. return false;
  249. }
  250. /**
  251. * 设置扩展名
  252. * @param array $fileExt
  253. * @return $this
  254. */
  255. public function setFileExt($fileExt)
  256. {
  257. if (empty($fileExt)) {
  258. return;
  259. }
  260. if (!is_array($fileExt)) {
  261. $fileExt = explode(',', $fileExt);
  262. }
  263. $this->fileExt = $fileExt;
  264. return $this;
  265. }
  266. /**
  267. * 上传验证
  268. * @param $request
  269. * @return bool
  270. * @throws ApiException
  271. */
  272. private function validate($request)
  273. {
  274. $fileInfo = $request->file($this->fileField);
  275. // if (!$request->hasFile($this->fileField) || !$fileInfo->isValid()) {
  276. // throw new ApiException(30002);
  277. // }
  278. if (!$this->checkFileExt($fileInfo)) {
  279. throw new ApiException(1101);
  280. }
  281. if (!$this->checkLimitSize($fileInfo)) {
  282. $limit = floor($this->limitSize / (1024 * 1024)) . 'M';
  283. throw new ApiException(1102, ['limit' => $limit]);
  284. }
  285. $this->checkDirectory($this->filePath);
  286. return true;
  287. }
  288. /**
  289. * 检查文件扩展名
  290. * @param $fileInfo
  291. * @return bool
  292. */
  293. private function checkFileExt($fileInfo)
  294. {
  295. $fileType = strtolower($fileInfo->getClientOriginalExtension());
  296. if (!$fileType || !in_array($fileType, $this->fileExt)) {
  297. return false;
  298. }
  299. $this->nowFileExt = strtolower($fileType);
  300. return true;
  301. }
  302. /**
  303. * 验证文件大小
  304. * @param $fileInfo
  305. * @return bool
  306. */
  307. private function checkLimitSize($fileInfo)
  308. {
  309. $clientSize = $fileInfo->getSize();
  310. if ($clientSize > $this->limitSize) {
  311. return false;
  312. } else {
  313. return true;
  314. }
  315. }
  316. /**
  317. * 检验文件夹,不存在则创建
  318. * @param $path
  319. */
  320. private function checkDirectory($path)
  321. {
  322. $folders = explode('/', $path);
  323. $basePath = $this->getLocalPath();//$this->basePath;
  324. foreach ($folders as $item) {
  325. if (empty($item)) {
  326. continue;
  327. }
  328. if (strrpos($basePath, '/') !== strlen($basePath) - 1) {
  329. $basePath .= '/';
  330. }
  331. $basePath .= $item;
  332. $this->makeDir($basePath);
  333. }
  334. }
  335. /**
  336. * 新增文件目录
  337. * @param $path
  338. * @return mixed
  339. */
  340. private function makeDir($path)
  341. {
  342. if (is_dir($path)) {
  343. return $path;
  344. } else {
  345. mkdir($path, 0777, true);
  346. return $path;
  347. }
  348. }
  349. /**
  350. * 上传保存到本地
  351. * @param $request
  352. * @return array
  353. */
  354. private function saveLocal($request,$isReName=false)
  355. {
  356. $fileInfo = $request->file($this->fileField);
  357. $path = $this->filePath;//$this->basePath.$this->filePath;
  358. $path.="/" .date('md') . "/";
  359. $OldFilename = $fileInfo->getClientOriginalName();
  360. if($isReName){
  361. $localPath=$this->getLocalPath().$path;
  362. $filename = $this->getFilename($localPath);
  363. }else{
  364. $path.= uniqid()."/";
  365. $localPath=$this->getLocalPath().$path;
  366. $filename= $OldFilename;
  367. }
  368. $fileSize = $fileInfo->getSize();
  369. $mimeType = $fileInfo->getMimeType();
  370. $fileInfo->move($localPath, $filename);
  371. // Log::info($this->filePath.'/'.$filename);
  372. return [
  373. 'path' => $path.$filename,
  374. 'size' => $fileSize,
  375. 'filename' => $filename,
  376. 'original_filename' => $OldFilename,
  377. 'ext' => $this->nowFileExt,
  378. 'mimeType' => $mimeType
  379. ];
  380. }
  381. /**
  382. * 获取文件名
  383. * @param $path
  384. * @return string
  385. */
  386. private function getFilename($path,$ext='')
  387. {
  388. $suffix=empty($ext)? $this->nowFileExt:$ext;
  389. $fileName = date('YmdHis') . mt_rand(100, 999) . '.' . $suffix;
  390. if (file_exists($path .'/'. $fileName)) {
  391. return $this->getFileName($path,$ext);
  392. }
  393. return $fileName;
  394. }
  395. /**
  396. * 设置文件路径
  397. * @param string $path
  398. * @return $this
  399. */
  400. public function setFilePath($path)
  401. {
  402. $this->filePath = $path;
  403. return $this;
  404. }
  405. /**
  406. * 设置文件大小
  407. * @param string $maxSize
  408. * @return $this
  409. */
  410. public function setMaxSize($maxSize = '')
  411. {
  412. if ($maxSize && is_numeric($maxSize)) {
  413. $this->limitSize = $maxSize;
  414. }
  415. return $this;
  416. }
  417. /**
  418. * 设置文件mineType
  419. * @param array $mineType
  420. * @return $this
  421. */
  422. public function setMineType($mineType = [])
  423. {
  424. if ($mineType) {
  425. $this->mineTypeList = $mineType;
  426. }
  427. return $this;
  428. }
  429. /**
  430. * 上传远程文件
  431. * */
  432. public function uploadRemoteImg($remoteUrl, $isReName = false)
  433. {
  434. $ossUrl = '';
  435. $ext = getExtFromUrl($remoteUrl);
  436. if (!empty($remoteUrl) && in_array($ext, $this->fileExt)) {
  437. $path = $this->filePath;
  438. $path .= "/" . date('md') . "/";
  439. $OldFilename = basename($remoteUrl);
  440. //如果包含中文则重命名
  441. if(preg_match('/[\x{4e00}-\x{9fa5}]/u', $OldFilename)) {
  442. $py = app()->make(PinYinService::class);
  443. $newName = strtolower($py->getAllPY($OldFilename));
  444. if(!empty($newName)) {
  445. $OldFilename = preg_replace('/[^a-zA-Z0-9_u4e00-u9fa5]/', '-', $newName).'.'.$ext;
  446. } else {
  447. $isReName = true;
  448. }
  449. }
  450. if ($isReName) {
  451. $localPath = $this->getLocalPath().$this->filePath;
  452. $path .= uniqid() . "/";
  453. $filename = $this->getFilename($localPath, $ext);
  454. } else {
  455. $path .= uniqid() . "/";
  456. $filename = $OldFilename;
  457. }
  458. $realPath = $this->getLocalPath().$path;
  459. if (!file_exists($realPath)) {
  460. mkdir($realPath, 0777, true);
  461. }
  462. $saveToUrl = $realPath . $filename;
  463. if (str_contains($remoteUrl, 'data:image/png;')) {
  464. try {
  465. // 分离出数据
  466. list($type, $data) = explode(';', $remoteUrl);
  467. list(, $data) = explode(',', $data);
  468. // 将Base64字符串解码成二进制数据
  469. $image = base64_decode($data);
  470. // 指定文件名和保存路径
  471. // 保存文件
  472. file_put_contents($saveToUrl, $image);
  473. $ossUrl = $path.$filename;
  474. } catch (\Exception $e) {
  475. }
  476. } else {
  477. if ((strpos($remoteUrl, "http://") !== false || strpos($remoteUrl, "https://") !== false)) {
  478. $remoteUrl = preg_replace('/\?v=.*/', '', $remoteUrl);
  479. $saveToUrl = mb_convert_encoding($saveToUrl, "UTF-8");
  480. downloadRemoteFile($remoteUrl, $saveToUrl);
  481. $ossUrl = $path.$filename;
  482. }
  483. }
  484. }
  485. if ($ossUrl) {
  486. $ossUrl = Request::root() . '/resources' . $ossUrl;
  487. }
  488. return $ossUrl;
  489. }
  490. /**
  491. * 上传图片文件
  492. * @param $request
  493. * @param string $fileField
  494. * @param array|string $fileExt
  495. * @param bool $isUploadOss
  496. * @return array
  497. */
  498. public function uploadCustomImage($request, $fileField = 'file', $fileExt = [], $isUploadOss = true, $isReName = false)
  499. {
  500. $this->fileField = $fileField;
  501. if (!empty($fileExt)) {
  502. $this->setFileExt($fileExt);
  503. }
  504. $this->validateCustom($request);
  505. $result = $this->saveCustomLocal($request);
  506. $resourcesPath = '/resources';
  507. if(config('app.language_path')) {
  508. if(config('app.language_path') != 'zh-cn') {
  509. $resourcesPath = $resourcesPath.'/'.config('app.language_path');
  510. }
  511. }
  512. $result['url'] = $request->root() . $resourcesPath . $result['path'];
  513. return $result;
  514. }
  515. private function validateCustom($request)
  516. {
  517. $fileInfo = $request->file($this->fileField);
  518. if (!$this->checkFileExt($fileInfo)) {
  519. throw new ApiException(1101);
  520. }
  521. $clientSize = $fileInfo->getSize();
  522. $limitSize = 1024 * 1024 * 5;
  523. if ($clientSize > $limitSize) {
  524. throw new ApiException(1102, ['limit' => 5]);
  525. }
  526. $this->checkDirectory($this->filePath);
  527. return true;
  528. }
  529. private function saveCustomLocal($request)
  530. {
  531. $fileInfo = $request->file($this->fileField);
  532. $path = '/custom_files';//$this->basePath.$this->filePath;
  533. $path .= "/" . date('Ymd') . "/".strtolower(uniqid())."/";
  534. $OldFilename = $fileInfo->getClientOriginalName();
  535. $localPath = $this->getLocalPath() . $path;
  536. $filename = $this->getFilename($localPath);
  537. $fileSize = $fileInfo->getSize();
  538. $mimeType = $fileInfo->getMimeType();
  539. $fileInfo->move($localPath, $filename);
  540. return [
  541. 'path' => $path . $filename,
  542. 'size' => $fileSize,
  543. 'filename' => $filename,
  544. 'original_filename' => $OldFilename,
  545. 'ext' => $this->nowFileExt,
  546. 'mimeType' => $mimeType
  547. ];
  548. }
  549. }