Upload.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. // +---------------------------------------------------------------------
  3. // | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
  4. // +---------------------------------------------------------------------
  5. // | Copyright (c) 2013-2014 http://www.thinkcmf.com All rights reserved.
  6. // +---------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +---------------------------------------------------------------------
  9. // | Author: Dean <zxxjjforever@163.com>
  10. // +---------------------------------------------------------------------
  11. namespace cmf\lib;
  12. use think\exception\HttpResponseException;
  13. use think\facade\Env;
  14. use think\File;
  15. use app\user\model\AssetModel;
  16. use think\Response;
  17. use think\Db;
  18. /**
  19. * ThinkCMF上传类,分块上传
  20. */
  21. class Upload
  22. {
  23. private $request;
  24. private $error = false;
  25. private $fileType;
  26. private $formName = 'file';
  27. public function __construct()
  28. {
  29. $this->request = request();
  30. }
  31. public function getError()
  32. {
  33. return $this->error;
  34. }
  35. public function setFileType($fileType)
  36. {
  37. $this->fileType = $fileType;
  38. }
  39. public function setFormName($name)
  40. {
  41. $this->formName = $name;
  42. }
  43. public function upload()
  44. {
  45. $uploadSetting = cmf_get_upload_setting();
  46. $arrFileTypes = [
  47. 'image' => ['title' => 'Image files', 'extensions' => $uploadSetting['file_types']['image']['extensions']],
  48. 'video' => ['title' => 'Video files', 'extensions' => $uploadSetting['file_types']['video']['extensions']],
  49. 'audio' => ['title' => 'Audio files', 'extensions' => $uploadSetting['file_types']['audio']['extensions']],
  50. 'file' => ['title' => 'Custom files', 'extensions' => $uploadSetting['file_types']['file']['extensions']]
  51. ];
  52. $arrData = $this->request->param();
  53. if (empty($arrData["filetype"])) {
  54. $arrData["filetype"] = "image";
  55. }
  56. $fileType = $this->fileType;
  57. if (empty($this->fileType)) {
  58. $fileType = $arrData["filetype"];
  59. }
  60. if (array_key_exists($arrData["filetype"], $arrFileTypes)) {
  61. $extensions = $uploadSetting['file_types'][$arrData["filetype"]]['extensions'];
  62. $fileTypeUploadMaxFileSize = $uploadSetting['file_types'][$fileType]['upload_max_filesize'];
  63. } else {
  64. $this->error = '上传文件类型配置错误!';
  65. return false;
  66. }
  67. //$strPostMaxSize = ini_get("post_max_size");
  68. //$strUploadMaxFileSize = ini_get("upload_max_filesize");
  69. if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { // other CORS headers if any...
  70. // exit; // finish preflight CORS requests here
  71. }
  72. @set_time_limit(24 * 60 * 60);
  73. $cleanupTargetDir = false; // Remove old files
  74. $maxFileAge = 5 * 3600; // Temp file age in seconds
  75. /**
  76. * 断点续传 end
  77. */
  78. $app = $this->request->param('app');
  79. if (empty($app) || !file_exists(APP_PATH . $app)) {
  80. $app = 'default';
  81. }
  82. $fileImage = $this->request->file($this->formName);
  83. $originalName = $fileImage->getInfo('name');
  84. $arrAllowedExtensions = explode(',', $arrFileTypes[$fileType]['extensions']);
  85. $strFileExtension = strtolower(cmf_get_file_extension($originalName));
  86. if (!in_array($strFileExtension, $arrAllowedExtensions) || $strFileExtension == 'php') {
  87. $this->error = "非法文件类型!";
  88. return false;
  89. }
  90. $fileUploadMaxFileSize = $uploadSetting['upload_max_filesize'][$strFileExtension];
  91. $fileUploadMaxFileSize = empty($fileUploadMaxFileSize) ? 2097152 : $fileUploadMaxFileSize;//默认2M
  92. $strWebPath = "";//"upload" . DS;
  93. $strId = $this->request->param("id");
  94. $strDate = date('Ymd');
  95. $adminId = cmf_get_current_admin_id();
  96. $userId = cmf_get_current_user_id();
  97. $userId = empty($adminId) ? $userId : $adminId;
  98. if (empty($userId)) {
  99. $userId = Db::name('user_token')->where('token', $this->request->header('XX-Token'))->field('user_id,token')->value('user_id');
  100. }
  101. $targetDir = Env::get('runtime_path') . "upload" . DIRECTORY_SEPARATOR . $userId . DIRECTORY_SEPARATOR; // 断点续传 need
  102. if (!file_exists($targetDir)) {
  103. mkdir($targetDir, 0777, true);
  104. }
  105. /**
  106. * 断点续传 need
  107. */
  108. $strFilePath = md5($originalName);
  109. $chunk = $this->request->param("chunk", 0, "intval");// isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
  110. $chunks = $this->request->param("chunks", 1, "intval");//isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 1;
  111. if (!$fileImage->isValid()) {
  112. $this->error = "非法文件!";
  113. return false;
  114. }
  115. if ($cleanupTargetDir) {
  116. if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
  117. $this->error = "Failed to open temp directory!";
  118. return false;
  119. }
  120. while (($file = readdir($dir)) !== false) {
  121. $tmpFilePath = $targetDir . $file;
  122. if ($tmpFilePath == "{$strFilePath}_{$chunk}.part" || $tmpFilePath == "{$strFilePath}_{$chunk}.parttmp") {
  123. continue;
  124. }
  125. if (preg_match('/\.(part|parttmp)$/', $file) && (@filemtime($tmpFilePath) < time() - $maxFileAge)) {
  126. @unlink($tmpFilePath);
  127. }
  128. }
  129. closedir($dir);
  130. }
  131. // Open temp file
  132. if (!$out = @fopen($targetDir . "{$strFilePath}_{$chunk}.parttmp", "wb")) {
  133. $this->error = "上传文件临时目录不可写" . $targetDir;
  134. return false;
  135. }
  136. // Read binary input stream and append it to temp file
  137. if (!$in = @fopen($fileImage->getInfo("tmp_name"), "rb")) {
  138. $this->error = "Failed to open input stream!";
  139. return false;
  140. }
  141. while ($buff = fread($in, 4096)) {
  142. fwrite($out, $buff);
  143. }
  144. @fclose($out);
  145. @fclose($in);
  146. rename($targetDir . "{$strFilePath}_{$chunk}.parttmp", $targetDir . "{$strFilePath}_{$chunk}.part");
  147. $done = true;
  148. for ($index = 0; $index < $chunks; $index++) {
  149. if (!file_exists($targetDir . "{$strFilePath}_{$index}.part")) {
  150. $done = false;
  151. break;
  152. }
  153. }
  154. if (!$done) {
  155. /**
  156. * 断点续传 need
  157. */
  158. // header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  159. // header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
  160. // header("Cache-Control: no-store, no-cache, must-revalidate");
  161. // header("Cache-Control: post-check=0, pre-check=0", false);
  162. // header("Pragma: no-cache");
  163. // header("Access-Control-Allow-Origin: *"); // Support CORS
  164. // die('');//分片没上传完
  165. $response = Response::create();
  166. throw new HttpResponseException($response);
  167. }
  168. $uploadPath = WEB_ROOT . 'upload/';
  169. $fileSaveName = (empty($app) ? '' : $app . '/') . $strDate . '/' . md5(uniqid()) . "." . $strFileExtension;
  170. $strSaveFilePath = $uploadPath . $fileSaveName; //TODO 测试 windows 下
  171. $strSaveFileDir = dirname($strSaveFilePath);
  172. if (!file_exists($strSaveFileDir)) {
  173. mkdir($strSaveFileDir, 0777, true);
  174. }
  175. // 合并临时文件
  176. if (!$out = @fopen($strSaveFilePath, "wb")) {
  177. $this->error = "上传目录不可写";
  178. return false;
  179. }
  180. if (flock($out, LOCK_EX)) {
  181. for ($index = 0; $index < $chunks; $index++) {
  182. if (!$in = @fopen($targetDir . "{$strFilePath}_{$index}.part", "rb")) {
  183. break;
  184. }
  185. while ($buff = fread($in, 4096)) {
  186. fwrite($out, $buff);
  187. }
  188. fclose($in);
  189. unlink("{$targetDir}{$strFilePath}_{$index}.part");
  190. }
  191. flock($out, LOCK_UN);
  192. }
  193. @fclose($out);
  194. $fileImage = new File($strSaveFilePath, 'r');
  195. $arrInfo = [
  196. "name" => $originalName,
  197. "type" => $fileImage->getMime(),
  198. "tmp_name" => $strSaveFilePath,
  199. "error" => 0,
  200. "size" => $fileImage->getSize(),
  201. ];
  202. $fileImage->setSaveName($fileSaveName);
  203. $fileImage->setUploadInfo($arrInfo);
  204. /**
  205. * 断点续传 end
  206. */
  207. if (!$fileImage->validate(['size' => $fileUploadMaxFileSize])->check()) {
  208. $error = $fileImage->getError();
  209. unset($fileImage);
  210. unlink($strSaveFilePath);
  211. $this->error = $error;
  212. return false;
  213. }
  214. // $url=$first['url'];
  215. $storageSetting = cmf_get_cmf_settings('storage');
  216. $qiniuSetting = empty($storageSetting['Qiniu']['setting']) ? [] : $storageSetting['Qiniu']['setting'];
  217. //$url=preg_replace('/^https/', $qiniu_setting['protocol'], $url);
  218. //$url=preg_replace('/^http/', $qiniu_setting['protocol'], $url);
  219. $arrInfo = [];
  220. if (config('FILE_UPLOAD_TYPE') == 'Qiniu' && $qiniuSetting['enable_picture_protect']) {
  221. //todo qiniu code ...
  222. // $previewUrl = $url.$qiniuSetting['style_separator'].$qiniuSetting['styles']['thumbnail300x300'];
  223. // $url= $url.$qiniuSetting['style_separator'].$qiniuSetting['styles']['watermark'];
  224. } else {
  225. if (empty($fileImage)) {
  226. $this->error = $fileImage->getError();
  227. return false;
  228. } else {
  229. $arrInfo["user_id"] = $userId;
  230. $arrInfo["file_size"] = $fileImage->getSize();
  231. $arrInfo["create_time"] = time();
  232. $arrInfo["file_md5"] = md5_file($strSaveFilePath);
  233. $arrInfo["file_sha1"] = sha1_file($strSaveFilePath);
  234. $arrInfo["file_key"] = $arrInfo["file_md5"] . md5($arrInfo["file_sha1"]);
  235. $arrInfo["filename"] = $fileImage->getInfo("name");
  236. $arrInfo["file_path"] = $strWebPath . $fileSaveName;
  237. $arrInfo["suffix"] = $fileImage->getExtension();
  238. }
  239. }
  240. //关闭文件对象
  241. $fileImage = null;
  242. //检查文件是否已经存在
  243. $assetModel = new AssetModel();
  244. $objAsset = $assetModel->where(["user_id" => $userId, "file_key" => $arrInfo["file_key"]])->find();
  245. $storage = cmf_get_option('storage');
  246. if (empty($storage['type'])) {
  247. $storage['type'] = 'Local';
  248. }
  249. $needUploadToRemoteStorage = false;//是否要上传到云存储
  250. if ($objAsset && $storage['type'] == 'Local') {
  251. $arrAsset = $objAsset->toArray();
  252. //$arrInfo["url"] = $this->request->domain() . $arrAsset["file_path"];
  253. $arrInfo["file_path"] = $arrAsset["file_path"];
  254. if (file_exists($uploadPath . $arrInfo["file_path"])) {
  255. @unlink($strSaveFilePath); // 删除已经上传的文件
  256. } else {
  257. $oldFileDir = dirname($uploadPath . $arrInfo["file_path"]);
  258. if (!file_exists($oldFileDir)) {
  259. mkdir($oldFileDir, 0777, true);
  260. }
  261. @rename($strSaveFilePath, $uploadPath . $arrInfo["file_path"]);
  262. }
  263. } else {
  264. $needUploadToRemoteStorage = true;
  265. }
  266. if ($objAsset) {
  267. $assetModel->where('id', $objAsset['id'])->update(['filename' => $arrInfo["filename"]]);
  268. } else {
  269. $assetModel->data($arrInfo)->allowField(true)->save();
  270. }
  271. //删除临时文件
  272. // for ($index = 0; $index < $chunks; $index++) {
  273. // // echo $targetDir . "{$strFilePath}_{$index}.part";
  274. // @unlink($targetDir . "{$strFilePath}_{$index}.part");
  275. // }
  276. @rmdir($targetDir);
  277. if ($storage['type'] != 'Local') { // 增加存储驱动
  278. $watermark = cmf_get_plugin_config($storage['type']);
  279. $storage = new Storage($storage['type'], $storage['storages'][$storage['type']]);
  280. if ($needUploadToRemoteStorage) {
  281. session_write_close();
  282. $result = $storage->upload($arrInfo["file_path"], $uploadPath . $arrInfo["file_path"], $fileType);
  283. if (!empty($result)) {
  284. return array_merge([
  285. 'filepath' => $arrInfo["file_path"],
  286. "name" => $arrInfo["filename"],
  287. 'id' => $strId,
  288. 'preview_url' => cmf_get_root() . '/upload/' . $arrInfo["file_path"],
  289. 'url' => cmf_get_root() . '/upload/' . $arrInfo["file_path"],
  290. ], $result);
  291. }
  292. } else {
  293. $previewUrl = $fileType == 'image' ? $storage->getPreviewUrl($arrInfo["file_path"]) : $storage->getFileDownloadUrl($arrInfo["file_path"]);
  294. $url = $fileType == 'image' ? $storage->getImageUrl($arrInfo["file_path"], $watermark['styles_watermark']) : $storage->getFileDownloadUrl($arrInfo["file_path"]);
  295. //测试ing
  296. return [
  297. 'filepath' => $arrInfo["file_path"],
  298. "name" => $arrInfo["filename"],
  299. 'id' => $strId,
  300. 'preview_url' => $previewUrl,
  301. 'url' => $url,
  302. ];
  303. }
  304. }
  305. return [
  306. 'filepath' => $arrInfo["file_path"],
  307. "name" => $arrInfo["filename"],
  308. 'id' => $strId,
  309. 'preview_url' => cmf_get_root() . '/upload/' . $arrInfo["file_path"],
  310. 'url' => cmf_get_root() . '/upload/' . $arrInfo["file_path"],
  311. ];
  312. }
  313. }