2
0

ExportZip2.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. namespace App\Console\Commands;
  3. use Illuminate\Console\Command;
  4. use Illuminate\Support\Facades\Storage;
  5. use Illuminate\Support\Facades\Log;
  6. use Illuminate\Support\Facades\Cache;
  7. use Illuminate\Support\Facades\App;
  8. use Symfony\Component\Process\Process;
  9. class ExportZip2 extends Command
  10. {
  11. protected $signature = 'export:zip2
  12. {filename : filename}
  13. {title : title}
  14. {id : 标识符}
  15. {format? : zip file format 7z,lzma,gz }';
  16. protected $description = '压缩导出的文件';
  17. public function handle()
  18. {
  19. Log::debug('export offline: 开始压缩');
  20. $defaultExportPath = storage_path('app/public/export/offline');
  21. $exportFile = $this->argument('filename');
  22. $filename = basename($exportFile);
  23. if ($filename === $exportFile) {
  24. $exportFullFileName = $defaultExportPath . '/' . $filename;
  25. $exportPath = $defaultExportPath;
  26. } else {
  27. $exportFullFileName = $exportFile;
  28. $exportPath = dirname($exportFile);
  29. }
  30. $format = $this->argument('format') ?? 'gz';
  31. if (!file_exists($exportFullFileName)) {
  32. Log::error('export offline: file not exists', [
  33. 'file' => $exportFullFileName
  34. ]);
  35. $this->error('file not exists: ' . $exportFullFileName);
  36. return 1;
  37. }
  38. $zipFile = $this->getZipFileName($filename, $format);
  39. $zipFullFileName = $exportPath . '/' . $zipFile;
  40. if (file_exists($zipFullFileName)) {
  41. unlink($zipFullFileName);
  42. }
  43. $this->info("start compress: {$exportFullFileName}");
  44. Log::debug('export offline zip start', [
  45. 'file' => $exportFullFileName,
  46. 'format' => $format
  47. ]);
  48. $this->compress($exportFullFileName, $zipFullFileName, $format);
  49. $this->info('压缩完成');
  50. Log::debug('zip done', [
  51. 'zip' => $zipFullFileName
  52. ]);
  53. /*
  54. |--------------------------------------------------------------------------
  55. | 上传 S3
  56. |--------------------------------------------------------------------------
  57. */
  58. $bucket = config('mint.attachments.bucket_name.temporary');
  59. $tmpFile = $bucket . '/' . $zipFile;
  60. $this->info('upload file=' . $tmpFile);
  61. Log::debug('export offline upload', [
  62. 'file' => $tmpFile
  63. ]);
  64. Storage::put($tmpFile, fopen($zipFullFileName, 'r'));
  65. $this->info('upload done');
  66. Log::debug('upload done');
  67. /*
  68. |--------------------------------------------------------------------------
  69. | 生成下载链接
  70. |--------------------------------------------------------------------------
  71. */
  72. if (App::environment('local')) {
  73. $link = Storage::url($tmpFile);
  74. } else {
  75. try {
  76. $link = Storage::temporaryUrl(
  77. $tmpFile,
  78. now()->addDays(2)
  79. );
  80. } catch (\Exception $e) {
  81. Log::error('temporaryUrl fail', [
  82. 'exception' => $e
  83. ]);
  84. $this->error('generate temporaryUrl fail');
  85. return 1;
  86. }
  87. }
  88. $this->info('link=' . $link);
  89. /*
  90. |--------------------------------------------------------------------------
  91. | CDN 列表
  92. |--------------------------------------------------------------------------
  93. */
  94. $url = [];
  95. foreach (config('mint.server.cdn_urls') as $key => $cdn) {
  96. $url[] = [
  97. 'link' => $cdn . '/' . $zipFile,
  98. 'hostname' => 'china cdn-' . $key
  99. ];
  100. }
  101. $url[] = [
  102. 'link' => $link,
  103. 'hostname' => 'Amazon cloud storage(Hongkong)'
  104. ];
  105. /*
  106. |--------------------------------------------------------------------------
  107. | Cache 写入
  108. |--------------------------------------------------------------------------
  109. */
  110. $info = Cache::get('/offline/index', []);
  111. if (!is_array($info)) {
  112. $info = [];
  113. }
  114. $id = $this->argument('id');
  115. // 先移除已有相同 id 的记录
  116. $info = array_values(array_filter($info, function ($item) use ($id) {
  117. return !isset($item['id']) || $item['id'] != $id;
  118. }));
  119. // 再追加新数据
  120. $info[] = [
  121. 'id' => $id,
  122. 'title' => $this->argument('title'),
  123. 'filename' => $zipFile,
  124. 'url' => $url,
  125. 'create_at' => now()->toDateTimeString(),
  126. 'chapter' => Cache::get("/export/chapter/count"),
  127. 'filesize' => filesize($zipFullFileName),
  128. 'min_app_ver' => '1.3',
  129. ];
  130. Cache::put('/offline/index', $info);
  131. /*
  132. |--------------------------------------------------------------------------
  133. | 删除原始文件
  134. |--------------------------------------------------------------------------
  135. */
  136. sleep(5);
  137. try {
  138. if (is_file($exportFullFileName)) {
  139. unlink($exportFullFileName);
  140. }
  141. if (file_exists($zipFullFileName)) {
  142. unlink($zipFullFileName);
  143. }
  144. } catch (\Throwable $e) {
  145. Log::error('delete source fail', [
  146. 'exception' => $e
  147. ]);
  148. }
  149. return 0;
  150. }
  151. /*
  152. |--------------------------------------------------------------------------
  153. | 生成压缩文件名
  154. |--------------------------------------------------------------------------
  155. */
  156. protected function getZipFileName(string $filename, string $format): string
  157. {
  158. return match ($format) {
  159. '7z' => $filename . '.7z',
  160. 'lzma' => $filename . '.lzma',
  161. default => $filename . '.tar.gz'
  162. };
  163. }
  164. /*
  165. |--------------------------------------------------------------------------
  166. | 压缩函数
  167. |--------------------------------------------------------------------------
  168. */
  169. protected function compress($source, $target, $format)
  170. {
  171. $isDir = is_dir($source);
  172. switch ($format) {
  173. case '7z':
  174. $command = [
  175. '7z',
  176. 'a',
  177. '-t7z',
  178. '-mx=9',
  179. $target,
  180. $source
  181. ];
  182. break;
  183. case 'lzma':
  184. if ($isDir) {
  185. $tmpTar = $source . '.tar';
  186. $tar = new Process([
  187. 'tar',
  188. '-cf',
  189. $tmpTar,
  190. '-C',
  191. dirname($source),
  192. basename($source)
  193. ]);
  194. $tar->run();
  195. $source = $tmpTar;
  196. }
  197. $command = [
  198. 'xz',
  199. '-k',
  200. '-9',
  201. '--format=lzma',
  202. $source
  203. ];
  204. break;
  205. default:
  206. $command = [
  207. 'tar',
  208. '-czf',
  209. $target,
  210. '-C',
  211. dirname($source),
  212. basename($source)
  213. ];
  214. }
  215. $this->info(implode(' ', $command));
  216. $process = new Process($command);
  217. $process->setTimeout(60 * 60 * 6);
  218. $process->run();
  219. $this->info($process->getOutput());
  220. if (!$process->isSuccessful()) {
  221. Log::error('compress fail', [
  222. 'error' => $process->getErrorOutput()
  223. ]);
  224. throw new \RuntimeException($process->getErrorOutput());
  225. }
  226. }
  227. }