UploadService.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * Name: 芸众商城系统
  5. * Author: 广州市芸众信息科技有限公司
  6. * Profile: 广州市芸众信息科技有限公司位于国际商贸中心的广州,专注于移动电子商务生态系统打造,拥有芸众社交电商系统、区块链数字资产管理系统、供应链管理系统、电子合同等产品/服务。官网 :www.yunzmall.com www.yunzshop.com
  7. * Date: 2021-04-26
  8. * Time: 15:59
  9. */
  10. namespace app\common\services\upload;
  11. use app\common\exceptions\ShopException;
  12. use app\common\services\ImageZip;
  13. use app\platform\modules\system\models\SystemSetting;
  14. class UploadService
  15. {
  16. private $setting;
  17. private $originalName;
  18. private $realPath;
  19. private $ext;
  20. private $mime_type;
  21. private $fileSize;
  22. private $diyFileName;
  23. private $is_remote;
  24. private $dir;
  25. private $fileNewName;
  26. private $file_type;
  27. private $relative_path;
  28. private $harm_type = array('asp', 'php', 'jsp', 'js', 'css', 'php3', 'php4', 'php5', 'ashx', 'aspx', 'exe', 'cgi');
  29. private $default_audio_types = array(
  30. 'avi', 'asf', 'wmv', 'avs', 'flv', 'mkv', 'mov', '3gp', 'mp4', 'mpg', 'mpeg', 'dat', 'ogm', 'vob', 'rm', 'rmvb', 'ts', 'tp', 'ifo', 'nsv',
  31. );
  32. private $default_video_types = array(
  33. 'mp3', 'aac', 'wav', 'wma', 'cda', 'flac', 'm4a', 'mid', 'mka', 'mp2', 'mpa', 'mpc', 'ape', 'ofr', 'ogg', 'ra', 'wv', 'tta', 'ac3', 'dts',
  34. );
  35. private $default_image_types = array(
  36. 'jpg', 'bmp', 'eps', 'gif', 'mif', 'miff', 'png', 'tif', 'tiff', 'svg', 'wmf', 'jpe', 'jpeg', 'dib', 'ico', 'tga', 'cut', 'pic'
  37. );
  38. private $default_file_types = array(
  39. 'pdf', 'xlsx', 'xls', 'doc', 'docx', 'txt', 'ppt', 'pptx', 'xml', 'wps', 'rtf', 'md', 'rar', 'zip', 'et', 'json',
  40. );
  41. private $default_file_mime_type = [
  42. 'audio/aac', 'video/x-msvideo', 'image/bmp', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/gif',
  43. 'image/vnd.microsoft.icon', 'image/jpeg', 'audio/midi', 'audio/x-midi', 'audio/mpeg', 'video/mpeg', 'image/png', 'application/pdf', 'application/vnd.ms-powerpoint',
  44. 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/x-rar-compressed', 'application/rtf', 'image/svg+xml', 'image/tiff',
  45. 'text/plain', 'audio/wav', 'image/webp', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/xml', 'text/xml',
  46. 'video/3gpp', 'audio/3gpp', 'video/x-ms-asf', 'video/x-ms-wmv', 'video/x-flv', 'video/quicktime', 'video/mp4', 'audio/x-wav', 'audio/x-m4a', 'audio/mid', 'audio/ogg',
  47. 'audio/x-realaudio', 'application/postscript', 'application/x-msmetafile', 'image/x-icon', 'application/vnd.ms-works', 'application/rar', 'application/zip', 'application/x-rar',
  48. 'application/octet-stream', 'application/x-font-gdos'
  49. ];
  50. public function __construct()
  51. {
  52. }
  53. public function upload($file, $file_type = 'image', $dir = '', $diy_file_name = '', $is_remote = true)
  54. {
  55. if (!$dir) {
  56. $this->dir = $this->getDirByType($file_type);
  57. } else {
  58. $this->dir = $dir;
  59. }
  60. $this->file_type = $file_type;
  61. $this->diyFileName = $diy_file_name;
  62. $this->is_remote = $is_remote;
  63. $this->setting = $this->getSetting();
  64. $this->initFile($file);
  65. $this->checkFile();
  66. $this->handleFile();
  67. $this->localUpload();
  68. $this->rotatePic();
  69. if ($this->setting['remote']['type'] != 0 && $is_remote) {
  70. $this->remoteUpload();
  71. }
  72. $url = $this->getUrl();
  73. $this->examine($url);
  74. return [
  75. 'relative_path' => $this->getDiskUrl(),
  76. 'absolute_path' => $url,
  77. 'file_name' => $this->getFileName(),
  78. ];
  79. }
  80. private function getDirByType($upload_type)
  81. {
  82. switch ($upload_type) {
  83. case 'video' :
  84. $dir = 'videos';
  85. break;
  86. case 'audio' :
  87. $dir = 'audios';
  88. break;
  89. case 'file' :
  90. $dir = 'files';
  91. break;
  92. default :
  93. $dir = 'image';
  94. break;
  95. }
  96. return $dir;
  97. }
  98. private function getFileName()
  99. {
  100. if ($this->diyFileName) {
  101. return $this->diyFileName;
  102. }
  103. if (isset($this->fileNewName)) {
  104. return $this->fileNewName;
  105. } else {
  106. $this->fileNewName = md5($this->originalName.str_random(6)).'.'.$this->ext;
  107. }
  108. return $this->fileNewName;
  109. }
  110. private function getDiskUrl()
  111. {
  112. return $this->relative_path;
  113. }
  114. private function localUpload()
  115. {
  116. $uniacid = intval(\YunShop::app()->uniacid);
  117. $path = $this->file_type.'s/'.$uniacid.'/'.date('Y/m/');
  118. $dir = $this->basePath().'/'.$path;
  119. $this->mkDir($dir);
  120. $file_name = $this->getFileName();
  121. $save_path = $dir.$file_name;
  122. $relative_path = $path.$file_name;
  123. $this->relative_path = $relative_path;
  124. if (!$this->fileMove($this->realPath, $save_path)) {
  125. return false;
  126. }
  127. return true;
  128. }
  129. private function remoteUpload()
  130. {
  131. if (config('app.framework') == 'platform') {
  132. if ($this->setting['remote']['type'] != 0) {
  133. file_remote_upload($this->getDiskUrl(), true, $this->setting['remote']);
  134. }
  135. } else {
  136. if ($this->setting['remote']['type'] != 0) {
  137. file_remote_upload_wq($this->getDiskUrl(), true, $this->setting['remote']);
  138. }
  139. }
  140. }
  141. private function handleFile()
  142. {
  143. if ($this->file_type != 'image') {
  144. return;
  145. }
  146. if ($this->setting['upload']['thumb'] == 1 && $this->setting['upload']['width'] && $this->ext != 'gif') {
  147. ImageZip::makeThumb($this->realPath, $this->setting['upload']['width'], 2);
  148. }
  149. if ($this->setting['upload']['percent'] && $this->setting['upload']['percent'] != 100 && $this->ext != 'gif') {
  150. ImageZip::makeThumb($this->realPath, $this->setting['upload']['percent'], 1);
  151. }
  152. }
  153. private function getUrl()
  154. {
  155. if ($this->is_remote) {
  156. return yz_tomedia($this->getDiskUrl());
  157. } else {
  158. return change_to_local_url($this->getDiskUrl());
  159. }
  160. }
  161. public static function getSetting()
  162. {
  163. if (config('app.framework') == 'platform') {
  164. $global_setting = SystemSetting::settingLoad('global', 'system_global');
  165. $remote = SystemSetting::settingLoad('remote', 'system_remote');
  166. $upload['image_ext'] = $global_setting['image_extentions'];//图片文件拓展名
  167. $upload['image_limit'] = $global_setting['image_limit'];//图片文件限制大小
  168. $upload['percent'] = $global_setting['zip_percentage'];//图片压缩比例
  169. $upload['thumb'] = $global_setting['thumb'];//是否开启缩略
  170. $upload['width'] = $global_setting['thumb_width'];//缩略图最大宽度
  171. $upload['audio_ext'] = $global_setting['audio_extentions'];//音频文件拓展名
  172. $upload['audio_limit'] = $global_setting['audio_limit'];//音频文件限制大小
  173. } else {
  174. //全局配置
  175. global $_W;
  176. $global_upload = $_W['setting']['upload'];
  177. //公众号独立配置信息 优先使用公众号独立配置
  178. $uni_setting = app('WqUniSetting')->get()->toArray();
  179. if (!empty($uni_setting['remote']) && iunserializer($uni_setting['remote'])['type'] != 0) {
  180. $remote = iunserializer($uni_setting['remote']);
  181. } else {
  182. $remote = $_W['setting']['remote'];
  183. }
  184. $upload['image_ext'] = $global_upload['image']['extentions'];//图片文件拓展名
  185. $upload['image_limit'] = $global_upload['image']['limit'];//文件限制大小
  186. $upload['percent'] = $global_upload['image']['zip_percentage'];//压缩比例
  187. $upload['thumb'] = $global_upload['image']['thumb'];//是否开启缩略
  188. $upload['width'] = $global_upload['image']['width'];//缩略图最大宽度
  189. $upload['audio_ext'] = $global_upload['audio']['extentions'];//音频文件拓展名
  190. $upload['audio_limit'] = $global_upload['audio']['limit'];//音频文件限制大小
  191. }
  192. return array('upload' => $upload, 'remote' => $remote);
  193. }
  194. private function initFile($file)
  195. {
  196. $this->originalName = $file->getClientOriginalName(); // 文件原名
  197. $this->realPath = $file->getRealPath(); //临时文件的绝对路径
  198. $this->ext = strtolower($file->getClientOriginalExtension()); //文件后缀
  199. $this->handelMimeType($file);
  200. if ($this->file_type == 'image') {
  201. $this->ext = strtolower($file->getClientOriginalExtension()) ?: 'png';
  202. }
  203. if (!$this->ext && !empty($file->getMimeType())) {//兼容上传文件为前端转过格式的文件,获取不了后缀名情况
  204. $type = explode('/',$file->getMimeType());
  205. !empty($type[1]) || $type[1] = '';
  206. switch ($type[1]) {
  207. case 'x-wav':
  208. $this->ext = 'wav';break;
  209. }
  210. }
  211. $this->fileSize = $file->getClientSize(); //文件大小
  212. $this->mime_type = $file->getMimeType();
  213. }
  214. private function handelMimeType($file)
  215. {
  216. $mime_type = $file->getClientMimeType(); //获取文件类型
  217. if (strexists($mime_type, 'image')) {
  218. $this->file_type = 'image';
  219. }
  220. if (strexists($mime_type, 'video')) {
  221. $this->file_type = 'video';
  222. }
  223. if (strexists($mime_type, 'audio')) {
  224. $this->file_type = 'audio';
  225. }
  226. }
  227. private function checkFile()
  228. {
  229. if (!in_array($this->mime_type, $this->default_file_mime_type)) {
  230. throw new ShopException('无法识别的文件mime类型:'.$this->mime_type);
  231. }
  232. if (in_array($this->ext, $this->harm_type)) {
  233. throw new ShopException('请上传正确的文件格式');
  234. }
  235. if (!in_array($this->ext, array_merge($this->default_image_types, $this->default_video_types, $this->default_audio_types, $this->default_file_types))) {
  236. throw new ShopException('非规定类型的文件默认格式.');
  237. }
  238. if ($this->file_type == 'image' && !in_array($this->ext, $this->setting['upload']['image_ext'])) {
  239. throw new ShopException('非规定类型的图片文件格式.');
  240. }
  241. if (($this->file_type == 'video' || $this->file_type == 'audio') && !in_array($this->ext, $this->setting['upload']['audio_ext'])) {
  242. throw new ShopException('非规定类型的音频文件格式.');
  243. }
  244. $default_img_size = $this->setting['upload']['image_limit'] ? $this->setting['upload']['image_limit'] * 1024 : 1024 * 1024 * 5;
  245. if ($this->file_type == 'image' && $this->fileSize > $default_img_size) {
  246. throw new ShopException('图片文件大小超出规定值.');
  247. }
  248. $default_audio_size = $this->setting['upload']['audio_limit'] ? $this->setting['upload']['audio_limit'] * 1024 : 1024 * 1024 * 25;
  249. if (($this->file_type == 'video' || $this->file_type == 'audio') && $this->fileSize > $default_audio_size) {
  250. throw new ShopException('音频文件大小超出规定值.');
  251. }
  252. return true;
  253. }
  254. private function examine($url)
  255. {
  256. if (app('plugins')->isEnabled('upload-verification')) {
  257. if (in_array($this->ext, ['png','jpg','jpeg','bmp','gif','webp','tiff'])) {
  258. $uploadResult = do_upload_verificaton($url, 'img');
  259. if (0 === $uploadResult[0]['status']) {
  260. throw new ShopException($uploadResult[0]['msg']);
  261. }
  262. }
  263. if ($this->file_type == 'audio') {
  264. $uploadResult = do_upload_verificaton($url, 'audio');
  265. if (0 === $uploadResult[0]['status']) {
  266. throw new ShopException($uploadResult[0]['msg']);
  267. }
  268. }
  269. if ($this->file_type == 'video') {
  270. $uploadResult = do_upload_verificaton($url, 'video');
  271. if (0 === $uploadResult[0]['status']) {
  272. throw new ShopException($uploadResult[0]['msg']);
  273. }
  274. }
  275. }
  276. }
  277. private function rotatePic()
  278. {
  279. $url = change_to_local_url($this->getDiskUrl());
  280. if (!in_array($this->ext, ['png','jpg','jpeg','bmp','gif','webp','tiff'])) {
  281. return false;
  282. }
  283. $img_size = getimagesize($url);
  284. list($src_width, $src_height) = $img_size;
  285. $memory_limit = trim(ini_get('memory_limit'), 'M');
  286. $img_memory = $src_width * $src_height * 3 * 1.7;
  287. if ($img_memory > $memory_limit * 1024 * 1024) { //imagecreatetruecolor方法生成图片资源时会占用大量的服务器内存,所以在上传大图、长图时不能使用
  288. return false;
  289. }
  290. if (function_exists('exif_read_data')) {
  291. $exif = exif_read_data($url);
  292. if (!$exif) {
  293. return false;
  294. }
  295. $image = imagecreatefromstring(file_get_contents($url));
  296. if (!empty($exif['Orientation'])) {
  297. switch ($exif['Orientation']) {
  298. case 8:
  299. $image = imagerotate($image, 90, 0);
  300. break;
  301. case 3:
  302. $image = imagerotate($image, 180, 0);
  303. break;
  304. case 6:
  305. $image = imagerotate($image, -90, 0);
  306. break;
  307. }
  308. if ($exif['Orientation'] != 1) {
  309. if ($exif['MimeType'] == 'image/gif') {
  310. imagegif($image, $url);
  311. } else if($exif['MimeType'] == 'image/png') {
  312. imagepng($image, $url);
  313. } else {
  314. imagejpeg($image, $url);
  315. }
  316. }
  317. }
  318. }
  319. return true;
  320. }
  321. private function basePath()
  322. {
  323. if (config('app.framework') == 'platform') {
  324. $path = base_path('static/upload');
  325. } else {
  326. $path = dirname(dirname(base_path())).'/attachment';
  327. }
  328. return $path;
  329. }
  330. private function fileMove($filename, $dest)
  331. {
  332. $this->mkDir(dirname($dest));
  333. if (is_uploaded_file($filename)) {
  334. move_uploaded_file($filename, $dest);
  335. } else {
  336. rename($filename, $dest);
  337. }
  338. @chmod($filename, 0777);
  339. return is_file($dest);
  340. }
  341. private function mkDir($dir)
  342. {
  343. return is_dir($dir) or self::mkDir(dirname($dir)) and mkdir($dir, 0777);
  344. }
  345. }