Index.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <?php
  2. namespace addons\cos\controller;
  3. use addons\cos\library\Auth;
  4. use app\common\exception\UploadException;
  5. use app\common\library\Upload;
  6. use app\common\model\Attachment;
  7. use Qcloud\Cos\Client;
  8. use Qcloud\Cos\Signature;
  9. use think\addons\Controller;
  10. use think\Config;
  11. /**
  12. * COS云储存
  13. *
  14. */
  15. class Index extends Controller
  16. {
  17. protected $cosConfig = [];
  18. public function _initialize()
  19. {
  20. //跨域检测
  21. check_cors_request();
  22. parent::_initialize();
  23. Config::set('default_return_type', 'json');
  24. $config = get_addon_config('cos');
  25. $this->cosConfig = array(
  26. 'region' => $config['region'],
  27. 'schema' => 'https', //协议头部,默认为http
  28. 'credentials' => array(
  29. 'secretId' => $config['secretId'],
  30. 'secretKey' => $config['secretKey']
  31. )
  32. );
  33. }
  34. public function index()
  35. {
  36. Config::set('default_return_type', 'html');
  37. $this->error("当前插件暂无前台页面");
  38. }
  39. public function params()
  40. {
  41. $this->check();
  42. $config = get_addon_config('cos');
  43. $name = $this->request->post('name');
  44. $md5 = $this->request->post('md5');
  45. $chunk = $this->request->post('chunk');
  46. $key = (new Upload())->getSavekey($config['savekey'], $name, $md5);
  47. $key = ltrim($key, "/");
  48. $params = [
  49. 'key' => $key,
  50. 'md5' => $md5
  51. ];
  52. if ($chunk) {
  53. $fileSize = $this->request->post('size');
  54. $oss = new Client($this->cosConfig);
  55. $result = $oss->createMultipartUpload(array(
  56. 'Bucket' => $config['bucket'],
  57. 'Key' => $key,
  58. ));
  59. $uploadId = $result['UploadId'];
  60. $sig = new Signature($config['secretId'], $config['secretKey'], ['signHost' => true]);
  61. $partSize = $this->request->post("chunksize");
  62. $i = 0;
  63. $size_count = $fileSize;
  64. $values = array();
  65. while ($size_count > 0) {
  66. $size_count -= $partSize;
  67. $values[] = array(
  68. $partSize * $i,
  69. ($size_count > 0) ? $partSize : ($size_count + $partSize),
  70. );
  71. $i++;
  72. }
  73. $params['key'] = $key;
  74. $params['uploadId'] = $uploadId;
  75. $params['partsAuthorization'] = [];
  76. $date = gmdate('D, d M Y H:i:s \G\M\T');
  77. foreach ($values as $index => $part) {
  78. $partNumber = $index + 1;
  79. $options = array(
  80. 'Bucket' => $config['bucket'],
  81. 'Key' => $key,
  82. 'UploadId' => $uploadId,
  83. 'PartNumber' => $partNumber,
  84. 'Body' => ''
  85. );
  86. $command = $oss->getCommand('uploadPart', $options);
  87. $request = $oss->commandToRequestTransformer($command);
  88. $authorization = $sig->createAuthorization($request);
  89. $params['partsAuthorization'][$index] = $authorization;
  90. }
  91. $params['date'] = $date;
  92. } else {
  93. if ($config['uploadmode'] == 'client') {
  94. $expiretime = time() + $config['expire'];
  95. $expiration = gmdate("Y-m-d\TH:i:s.414\Z", $expiretime);
  96. $keytime = (time() - 60) . ';' . $expiretime;
  97. $policy = json_encode([
  98. 'expiration' => $expiration,
  99. 'conditions' => [
  100. ['q-sign-algorithm' => 'sha1'],
  101. ['q-ak' => $config['secretId']],
  102. ['q-sign-time' => $keytime]
  103. ]
  104. ]);
  105. $signature = hash_hmac('sha1', sha1($policy), hash_hmac('sha1', $keytime, $config['secretKey']));
  106. $params = [
  107. 'key' => $key,
  108. 'policy' => base64_encode($policy),
  109. 'q-sign-algorithm' => 'sha1',
  110. 'q-ak' => $config['secretId'],
  111. 'q-key-time' => $keytime,
  112. 'q-sign-time' => $keytime,
  113. 'q-signature' => $signature
  114. ];
  115. }
  116. }
  117. $this->success('', null, $params);
  118. return;
  119. }
  120. /**
  121. * 服务器中转上传文件
  122. * 上传分片
  123. * 合并分片
  124. * @param bool $isApi
  125. */
  126. public function upload($isApi = false)
  127. {
  128. if ($isApi === true) {
  129. if (!Auth::isModuleAllow()) {
  130. $this->error("请登录后再进行操作");
  131. }
  132. } else {
  133. $this->check();
  134. }
  135. $config = get_addon_config('cos');
  136. $oss = new Client($this->cosConfig);
  137. //检测删除文件或附件
  138. $checkDeleteFile = function ($attachment, $upload, $force = false) use ($config) {
  139. //如果设定为不备份则删除文件和记录 或 强制删除
  140. if ((isset($config['serverbackup']) && !$config['serverbackup']) || $force) {
  141. if ($attachment && !empty($attachment['id'])) {
  142. $attachment->delete();
  143. }
  144. if ($upload) {
  145. //文件绝对路径
  146. $filePath = $upload->getFile()->getRealPath() ?: $upload->getFile()->getPathname();
  147. @unlink($filePath);
  148. }
  149. }
  150. };
  151. $chunkid = $this->request->post("chunkid");
  152. if ($chunkid) {
  153. $action = $this->request->post("action");
  154. $chunkindex = $this->request->post("chunkindex/d");
  155. $chunkcount = $this->request->post("chunkcount/d");
  156. $filesize = $this->request->post("filesize");
  157. $filename = $this->request->post("filename");
  158. $method = $this->request->method(true);
  159. $key = $this->request->post("key");
  160. $uploadId = $this->request->post("uploadId");
  161. if ($action == 'merge') {
  162. $attachment = null;
  163. $upload = null;
  164. //合并分片
  165. if ($config['uploadmode'] == 'server') {
  166. //合并分片文件
  167. try {
  168. $upload = new Upload();
  169. $attachment = $upload->merge($chunkid, $chunkcount, $filename);
  170. } catch (UploadException $e) {
  171. $this->error($e->getMessage());
  172. }
  173. }
  174. $etags = $this->request->post("etags/a", []);
  175. if (count($etags) != $chunkcount) {
  176. $checkDeleteFile($attachment, $upload, true);
  177. $this->error("分片数据错误");
  178. }
  179. $listParts = [];
  180. for ($i = 0; $i < $chunkcount; $i++) {
  181. $listParts[] = array("PartNumber" => $i + 1, "ETag" => $etags[$i]);
  182. }
  183. try {
  184. $result = $oss->completeMultipartUpload(
  185. array(
  186. 'Bucket' => $config['bucket'],
  187. 'Key' => $key,
  188. 'UploadId' => $uploadId,
  189. 'Parts' => $listParts
  190. )
  191. );
  192. } catch (\Exception $e) {
  193. $checkDeleteFile($attachment, $upload, true);
  194. $this->error($e->getMessage());
  195. }
  196. if (!isset($result['Key'])) {
  197. $checkDeleteFile($attachment, $upload, true);
  198. $this->error("上传失败");
  199. } else {
  200. $checkDeleteFile($attachment, $upload);
  201. $this->success("上传成功", '', ['url' => "/" . $key, 'fullurl' => cdnurl("/" . $key, true)]);
  202. }
  203. } else {
  204. //默认普通上传文件
  205. $file = $this->request->file('file');
  206. try {
  207. $upload = new Upload($file);
  208. $file = $upload->chunk($chunkid, $chunkindex, $chunkcount);
  209. } catch (UploadException $e) {
  210. $this->error($e->getMessage());
  211. }
  212. try {
  213. $params = array(
  214. 'Bucket' => $config['bucket'],
  215. 'Key' => $key,
  216. 'UploadId' => $uploadId,
  217. 'PartNumber' => $chunkindex + 1,
  218. 'Body' => $file->fread($file->getSize())
  219. );
  220. $ret = $oss->uploadPart($params);
  221. $etag = $ret['ETag'];
  222. } catch (\Exception $e) {
  223. $this->error($e->getMessage());
  224. }
  225. $this->success("上传成功", "", [], 3, ['ETag' => $etag]);
  226. }
  227. } else {
  228. $attachment = null;
  229. //默认普通上传文件
  230. $file = $this->request->file('file');
  231. try {
  232. $upload = new Upload($file);
  233. $attachment = $upload->upload();
  234. } catch (UploadException $e) {
  235. $this->error($e->getMessage());
  236. }
  237. //文件绝对路径
  238. $filePath = $upload->getFile()->getRealPath() ?: $upload->getFile()->getPathname();
  239. $url = $attachment->url;
  240. try {
  241. $ret = $oss->upload($config['bucket'], ltrim($attachment->url, "/"), $upload->getFile());
  242. //成功不做任何操作
  243. } catch (\Exception $e) {
  244. $checkDeleteFile($attachment, $upload, true);
  245. $this->error("上传失败");
  246. }
  247. $checkDeleteFile($attachment, $upload);
  248. // 记录云存储记录
  249. $data = $attachment->toArray();
  250. unset($data['id']);
  251. $data['storage'] = 'cos';
  252. Attachment::create($data, true);
  253. $this->success("上传成功", '', ['url' => $url, 'fullurl' => cdnurl($url, true)]);
  254. }
  255. return;
  256. }
  257. /**
  258. * 回调
  259. */
  260. public function notify()
  261. {
  262. $this->check();
  263. $config = get_addon_config('cos');
  264. if ($config['uploadmode'] != 'client') {
  265. $this->error("无需执行该操作");
  266. }
  267. $this->request->filter('trim,strip_tags,htmlspecialchars,xss_clean');
  268. $size = $this->request->post('size/d');
  269. $name = $this->request->post('name', '');
  270. $md5 = $this->request->post('md5', '');
  271. $type = $this->request->post('type', '');
  272. $url = $this->request->post('url', '');
  273. $width = $this->request->post('width/d');
  274. $height = $this->request->post('height/d');
  275. $category = $this->request->post('category', '');
  276. $category = array_key_exists($category, config('site.attachmentcategory') ?? []) ? $category : '';
  277. $suffix = strtolower(pathinfo($name, PATHINFO_EXTENSION));
  278. $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
  279. $attachment = Attachment::where('url', $url)->where('storage', 'cos')->find();
  280. if (!$attachment) {
  281. $params = array(
  282. 'category' => $category,
  283. 'admin_id' => (int)session('admin.id'),
  284. 'user_id' => (int)cookie('uid'),
  285. 'filesize' => $size,
  286. 'filename' => $name,
  287. 'imagewidth' => $width,
  288. 'imageheight' => $height,
  289. 'imagetype' => $suffix,
  290. 'imageframes' => 0,
  291. 'mimetype' => $type,
  292. 'url' => $url,
  293. 'uploadtime' => time(),
  294. 'storage' => 'cos',
  295. 'sha1' => $md5,
  296. );
  297. Attachment::create($params, true);
  298. }
  299. $this->success();
  300. return;
  301. }
  302. /**
  303. * 检查签名是否正确或过期
  304. */
  305. protected function check()
  306. {
  307. $costoken = $this->request->post('costoken', '', 'trim');
  308. if (!$costoken) {
  309. $this->error("参数不正确(code:1)");
  310. }
  311. $config = get_addon_config('cos');
  312. list($appId, $sign, $data) = explode(':', $costoken);
  313. if (!$appId || !$sign || !$data) {
  314. $this->error("参数不正确(code:2)");
  315. }
  316. if ($appId !== $config['appId']) {
  317. $this->error("参数不正确(code:3)");
  318. }
  319. if ($sign !== base64_encode(hash_hmac('sha1', base64_decode($data), $config['secretKey'], true))) {
  320. $this->error("签名不正确");
  321. }
  322. $json = json_decode(base64_decode($data), true);
  323. if ($json['deadline'] < time()) {
  324. $this->error("请求已经超时");
  325. }
  326. }
  327. }