UploadForm.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <?php
  2. namespace common\forms;
  3. use Yii;
  4. use yii\web\NotFoundHttpException;
  5. use yii\web\UnprocessableEntityHttpException;
  6. use yii\web\UploadedFile;
  7. use common\helpers\RegularHelper;
  8. use common\helpers\ArrayHelper;
  9. use common\enums\AttachmentDriveEnum;
  10. use common\enums\AttachmentUploadTypeEnum;
  11. use common\helpers\StringHelper;
  12. use common\helpers\UploadHelper;
  13. use linslin\yii2\curl\Curl;
  14. use League\Flysystem\Adapter\Local;
  15. use League\Flysystem\Filesystem;
  16. use League\Flysystem\Adapter\AbstractAdapter;
  17. use Overtrue\Flysystem\Cos\CosAdapter;
  18. use Overtrue\Flysystem\Qiniu\QiniuAdapter;
  19. use Xxtime\Flysystem\Aliyun\OssAdapter;
  20. use Overtrue\Flysystem\Qiniu\Plugins\FileUrl;
  21. /**
  22. * Class UploadForm
  23. * @package common\forms
  24. */
  25. class UploadForm extends \common\models\common\Attachment
  26. {
  27. public $thumb;
  28. public $chunks;
  29. public $chunk = 1;
  30. public $guid;
  31. public $poster;
  32. public $isCut = false;
  33. public $merge = false;
  34. public $writeTable = true;
  35. public $superAddition = false;
  36. /**
  37. * @var AbstractAdapter
  38. */
  39. public $uploadDrive;
  40. /**
  41. * @var \League\Flysystem\Filesystem
  42. */
  43. public $fileSystem;
  44. /**
  45. * 文件来源
  46. *
  47. * @var string
  48. */
  49. public $fileSource = 'file';
  50. /**
  51. * 上传的文件名
  52. *
  53. * @var string
  54. */
  55. public $fileName = 'file';
  56. /**
  57. * 文件内容
  58. *
  59. * @var string
  60. */
  61. public $fileData;
  62. /**
  63. * 驱动配置
  64. *
  65. * @var array
  66. */
  67. public $driveConfig = [];
  68. /**
  69. * 初始化路径
  70. *
  71. * @var array
  72. */
  73. public $paths = [];
  74. /**
  75. * 文件写入的相对路径
  76. *
  77. * @var
  78. */
  79. public $fileRelativePath;
  80. /**
  81. * @return array
  82. */
  83. public function rules()
  84. {
  85. return ArrayHelper::merge([
  86. [['fileName', 'fileSource'], 'required'],
  87. ['fileSource', 'in', 'range' => ['url', 'file', 'base64']],
  88. [['fileSource'], 'verifyFileSource'],
  89. [['guid', 'fileData'], 'string'],
  90. [['thumb', 'chunks', 'chunk', 'image', 'compress', 'merge', 'writeTable'], 'safe'],
  91. ], parent::rules());
  92. }
  93. /**
  94. * @return array|string[]
  95. */
  96. public function attributeLabels()
  97. {
  98. return ArrayHelper::merge(parent::attributeLabels(), [
  99. 'fileSource' => '文件来源',
  100. 'fileData' => '文件内容',
  101. ]);
  102. }
  103. /**
  104. * 验证来源
  105. *
  106. * @param $attribute
  107. */
  108. public function verifyFileSource($attribute)
  109. {
  110. switch ($this->fileSource) {
  111. case 'url' :
  112. $this->verifyUrl();
  113. break;
  114. case 'base64' :
  115. $this->size = strlen($this->fileData);
  116. empty($this->extension) && $this->extension = 'jpg';
  117. break;
  118. case 'file' :
  119. $this->verifyFile();
  120. break;
  121. }
  122. $this->fileRelativePath = $this->paths['relativePath'] . $this->name . '.' . $this->extension;
  123. $this->verify();
  124. }
  125. /**
  126. * 验证文件
  127. *
  128. * @throws NotFoundHttpException
  129. */
  130. protected function verifyFile()
  131. {
  132. $file = UploadedFile::getInstanceByName($this->fileName);
  133. if (!$file) {
  134. throw new NotFoundHttpException('找不到上传文件');
  135. }
  136. if ($file->getHasError()) {
  137. throw new NotFoundHttpException('上传失败,请检查文件');
  138. }
  139. $this->extension = $file->getExtension();
  140. $this->size = $file->size;
  141. empty($this->name) && $this->name = $file->getBaseName();
  142. }
  143. /**
  144. * 验证 Url
  145. *
  146. * @throws NotFoundHttpException
  147. */
  148. public function verifyUrl()
  149. {
  150. $imgUrl = str_replace("&amp;", "&", htmlspecialchars($this->fileData));
  151. // http开头验证
  152. if (strpos($imgUrl, "http") !== 0) {
  153. throw new NotFoundHttpException('不是一个http地址');
  154. }
  155. preg_match('/(^https?:\/\/[^:\/]+)/', $imgUrl, $matches);
  156. $host_with_protocol = count($matches) > 1 ? $matches[1] : '';
  157. // 判断是否是合法 url
  158. if (!filter_var($host_with_protocol, FILTER_VALIDATE_URL)) {
  159. throw new NotFoundHttpException('Url不合法');
  160. }
  161. preg_match('/^https?:\/\/(.+)/', $host_with_protocol, $matches);
  162. $host_without_protocol = count($matches) > 1 ? $matches[1] : '';
  163. // 此时提取出来的可能是 IP 也有可能是域名,先获取 IP
  164. $ip = gethostbyname($host_without_protocol);
  165. // 获取请求头并检测死链
  166. $heads = get_headers($imgUrl, 1);
  167. if (!(stristr($heads[0], "200") && stristr($heads[0], "OK"))) {
  168. throw new NotFoundHttpException('文件获取失败');
  169. }
  170. // Content-Type验证)
  171. if (!isset($heads['Content-Type']) || !stristr($heads['Content-Type'], "image")) {
  172. throw new NotFoundHttpException('格式验证失败');
  173. }
  174. $extend = StringHelper::clipping($imgUrl, '.', 1);
  175. if (!in_array($extend, Yii::$app->params['uploadConfig']['images']['extensions'])) {
  176. $extend = 'jpg';
  177. }
  178. $img = (new Curl())->get($imgUrl);
  179. $this->extension = $extend;
  180. $this->size = strlen($img);
  181. $this->md5 = md5($img);
  182. $this->fileData = $img;
  183. }
  184. /**
  185. * 验证文件大小及类型
  186. *
  187. * @throws NotFoundHttpException
  188. */
  189. protected function verify()
  190. {
  191. if ($this->size > $this->driveConfig['maxSize']) {
  192. throw new NotFoundHttpException('文件大小超出网站限制');
  193. }
  194. if (!empty($this->driveConfig['extensions']) && !in_array($this->extension, $this->driveConfig['extensions'])) {
  195. throw new NotFoundHttpException('文件类型不允许');
  196. }
  197. // 存储本地进行安全校验
  198. if (
  199. $this->drive == AttachmentDriveEnum::LOCAL &&
  200. $this->upload_type == AttachmentUploadTypeEnum::FILES &&
  201. in_array($this->extension, $this->driveConfig['blacklist'])) {
  202. throw new NotFoundHttpException('上传的文件类型不允许');
  203. }
  204. }
  205. /**
  206. * 获取生成路径信息
  207. *
  208. * @return array
  209. */
  210. public function pathInit()
  211. {
  212. if (!empty($this->paths)) {
  213. return $this->paths;
  214. }
  215. $config = $this->driveConfig;
  216. // 保留原名称
  217. $config['originalName'] == false && $this->name = $config['prefix'] . time() . '_' . StringHelper::random(8);
  218. // 文件路径
  219. $filePath = $config['path'] . date($config['subName'], time()) . "/";
  220. // 缩略图
  221. $thumbPath = Yii::$app->params['uploadConfig']['thumb']['path'] . date($config['subName'], time()) . "/";
  222. empty($config['guid']) && $config['guid'] = StringHelper::random(8);
  223. $tmpPath = 'tmp/' . date($config['subName'], time()) . "/" . $config['guid'] . '/';
  224. $this->paths = [
  225. 'relativePath' => $filePath, // 相对路径
  226. 'thumbRelativePath' => $thumbPath, // 缩略图相对路径
  227. 'tmpRelativePath' => $tmpPath, // 临时相对路径
  228. ];
  229. return $this->paths;
  230. }
  231. /**
  232. * 初始化上传类
  233. */
  234. public function fileSystemInit()
  235. {
  236. $drive = $this->drive;
  237. $config = Yii::$app->services->config->configAll();
  238. switch ($drive) {
  239. // 阿里云
  240. case AttachmentDriveEnum::OSS :
  241. $this->uploadDrive = new OssAdapter([
  242. 'accessId' => $config['storage_aliyun_accesskeyid'],
  243. 'accessSecret' => $config['storage_aliyun_accesskeysecret'],
  244. 'bucket' => $config['storage_aliyun_bucket'],
  245. 'endpoint' => $config['storage_aliyun_is_internal'] == true ? $config['storage_aliyun_endpoint_internal'] : $config['storage_aliyun_endpoint'],
  246. // 'timeout' => 3600,
  247. // 'connectTimeout' => 10,
  248. // 'isCName' => false,
  249. // 'token' => '',
  250. ]);
  251. break;
  252. // 腾讯云
  253. case AttachmentDriveEnum::COS :
  254. $this->uploadDrive = new CosAdapter([
  255. 'region' => $config['storage_cos_region'], // 'ap-guangzhou'
  256. 'credentials' => [
  257. 'appId' => $config['storage_cos_appid'], // 域名中数字部分
  258. 'secretId' => $config['storage_cos_accesskey'],
  259. 'secretKey' => $config['storage_cos_secrectkey'],
  260. ],
  261. 'bucket' => $config['storage_cos_bucket'],
  262. 'timeout' => 60,
  263. 'connect_timeout' => 60,
  264. 'cdn' => $config['storage_cos_cdn'],
  265. 'scheme' => 'https',
  266. 'read_from_cdn' => !empty($config['read_from_cdn']),
  267. ]);
  268. break;
  269. // 七牛
  270. case AttachmentDriveEnum::QINIU :
  271. $this->uploadDrive = new QiniuAdapter(
  272. $config['storage_qiniu_accesskey'],
  273. $config['storage_qiniu_secrectkey'],
  274. $config['storage_qiniu_bucket'],
  275. $config['storage_qiniu_domain']
  276. );
  277. break;
  278. // 本地
  279. default :
  280. // 判断是否追加
  281. if ($this->superAddition) {
  282. $this->uploadDrive = new Local(Yii::getAlias('@attachment'), FILE_APPEND);
  283. } else {
  284. $this->uploadDrive = new Local(Yii::getAlias('@attachment'));
  285. }
  286. break;
  287. }
  288. $this->fileSystem = new Filesystem($this->uploadDrive);
  289. }
  290. /**
  291. * @return array
  292. * @throws UnprocessableEntityHttpException
  293. * @throws \League\Flysystem\FileNotFoundException
  294. * @throws \yii\base\InvalidConfigException
  295. */
  296. public function getInfo()
  297. {
  298. $this->path = $this->fileRelativePath;
  299. $this->isCut == false && $this->specific_type = $this->fileSystem->getMimetype($this->path);
  300. $this->url = $this->getUrl();
  301. $this->year = date('Y');
  302. $this->month = date('m');
  303. $this->day = date('d');
  304. $this->member_id = Yii::$app->services->member->getAutoId();
  305. $this->ip = Yii::$app->services->base->getUserIp();
  306. $this->req_id = Yii::$app->params['uuid'];
  307. $this->format_size = Yii::$app->formatter->asShortSize($this->size, 2);
  308. // 如果是图片且内容是文字类型
  309. if (
  310. in_array($this->extension, Yii::$app->params['uploadConfig']['images']['extensions']) &&
  311. in_array($this->specific_type, ['text/plain'])
  312. ) {
  313. $this->fileSystem->delete($this->path);
  314. Yii::$app->services->actionLog->create('alarm', '用户试图上传病毒文件');
  315. throw new UnprocessableEntityHttpException('警告这是非法文件');
  316. }
  317. if ($this->isCut == false && $this->writeTable && Yii::$app->params['fileWriteTable']) {
  318. $this->superAddition == true ? $this->save(false) : $this->save();
  319. }
  320. return ArrayHelper::merge(ArrayHelper::toArray($this), [
  321. 'merge' => $this->merge,
  322. 'guid' => $this->guid,
  323. 'chunk' => $this->chunk,
  324. 'chunks' => $this->chunks,
  325. 'upload_type' => UploadHelper::formattingFileType($this->specific_type, $this->extension, $this->upload_type)
  326. ]);
  327. }
  328. /**
  329. * @return mixed|string
  330. */
  331. protected function getUrl()
  332. {
  333. $config = Yii::$app->services->config->configAll();
  334. switch ($this->drive) {
  335. // 阿里云
  336. case AttachmentDriveEnum::OSS :
  337. $url = $config['storage_aliyun_user_url'];
  338. if (!empty($url)) {
  339. return $config['storage_aliyun_transport_protocols'] . '://' . $url . '/' . $this->path;
  340. }
  341. $raw = $this->uploadDrive->supports->getFlashData();
  342. return $raw['info']['url'];
  343. // 腾讯云
  344. case AttachmentDriveEnum::COS :
  345. if (empty($config['read_from_cdn'])) {
  346. $bucket = $config['storage_cos_bucket'] ?? '';
  347. $appid = $config['storage_cos_appid'] ?? '';
  348. $region = $config['storage_cos_region'] ?? '';
  349. return 'https://' . $bucket . '-' . $appid . '.cos.' . $region . '.myqcloud.com/' . $this->path;
  350. }
  351. return $config['storage_cos_cdn'] . $this->path;
  352. // 七牛
  353. case AttachmentDriveEnum::QINIU :
  354. $this->fileSystem->addPlugin(new FileUrl());
  355. return $this->fileSystem->getUrl($this->path);
  356. // 本地
  357. default :
  358. $hostInfo = Yii::$app->request->hostInfo ?? '';
  359. $url = Yii::getAlias('@attachurl') . '/' . $this->path;
  360. if ($this->driveConfig['fullPath'] == true && !RegularHelper::verify('url', $url)) {
  361. return $hostInfo . $url;
  362. }
  363. return $url;
  364. }
  365. }
  366. }
粤ICP备19079148号