Просмотр исходного кода

Merge pull request #1727 from visuddhinanda/laravel

支持aritcle导出
visuddhinanda 2 лет назад
Родитель
Сommit
e7611b81a0

+ 1 - 1
app/Console/Commands/CopyUserBook.php

@@ -64,7 +64,7 @@ class CopyUserBook extends Command
             $this->info('doing book='.$customBook->book_id);
             if(empty($customBook->channel_id)){
                 $bookLang = $customBook->lang;
-                if(empty($bookLang) || $bookLang === 'false' || $bookLang === 'null'){
+                if(empty($bookLang) || $bookLang === 'false' || $bookLang === 'null'  || $bookLang === 'none'){
                     $this->info('language can not be empty change to pa, book='.$customBook->book_id);
                     Log::warning('copy:user.book language can not be empty ,change to pa, book='.$customBook->book_id);
                     $bookLang = 'pa';

+ 158 - 0
app/Console/Commands/ExportArticle.php

@@ -0,0 +1,158 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
+use Illuminate\Support\Facades\Storage;
+
+use App\Tools\RedisClusters;
+use App\Tools\ExportDownload;
+use App\Http\Api\MdRender;
+
+
+class ExportArticle extends Command
+{
+    /**
+     * The name and signature of the console command.
+     * php artisan export:article 78c22ad3-58e2-4cf0-b979-67783ca3a375 123 --channel=7fea264d-7a26-40f8-bef7-bc95102760fb --format=html
+     * php artisan export:article 4732bcae-fb9d-4db4-b6b7-e8d0aa882f30 1234 --channel=7fea264d-7a26-40f8-bef7-bc95102760fb --anthology=eb9e3f7f-b942-4ca4-bd6f-b7876b59a523 --format=html --token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJuYmYiOjE2OTc3Mjg2ODUsImV4cCI6MTcyOTI2NDY4NSwidWlkIjoiYmE1NDYzZjMtNzJkMS00NDEwLTg1OGUtZWFkZDEwODg0NzEzIiwiaWQiOjR9.fiXhnY2LczZ9kKVHV0FfD3AJPZt-uqM5wrDe4EhToVexdd007ebPFYssZefmchfL0mx9nF0rgHSqjNhx4P0yDA
+     * @var string
+     */
+    protected $signature = 'export:article {id} {query_id} {--token=} {--anthology=} {--channel=}  {--origin=false} {--translation=true} {--format=tex} {--debug}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->info('task export chapter start');
+        Log::debug('task export chapter start');
+        if(\App\Tools\Tools::isStop()){
+            return 0;
+        }
+        $upload = new ExportDownload([
+            'queryId'=>$this->argument('query_id'),
+            'format'=>$this->option('format'),
+            'debug'=>$this->option('debug'),
+            'filename'=>'article',
+        ]);
+
+        MdRender::init();
+        $m = new \Mustache_Engine(array('entity_flags'=>ENT_QUOTES,
+                                        'delimiters' => '[[ ]]',
+                                        'escape'=>function ($value){
+                                            return $value;
+                                        }));
+
+
+        $sections = array();
+        $articles = array();
+
+
+        $article = $this->fetch($this->argument('id'));
+        if(!$article){
+            return 1;
+        }
+
+        $bookMeta = array();
+        $bookMeta['book_author'] = "";
+        $bookMeta['book_title'] = $article['title_text'];
+
+        $articles[] = [
+            'level'=>1,
+            'title'=>$article['title_text'],
+            'content'=>isset($article['html'])?$article['html']:'',
+        ];
+        $progress = 0.1;
+        $this->info($upload->setStatus($progress,'export article content title='.$article['title_text']));
+
+        if(isset($article['toc']) && count($article['toc'])>0){
+            $this->info('has sub article '. count($article['toc']));
+            $step = 0.8 / count($article['toc']);
+            $baseLevel = 0;
+            foreach ($article['toc'] as $key => $value) {
+                if($baseLevel === 0){
+                    $baseLevel = $value['level'] - 2;
+                }
+                $progress += $step;
+                $this->info($upload->setStatus($progress,'exporting article title='.$value['title']));
+                $article = $this->fetch($value['key']);
+                if(!$article){
+                    $this->info($upload->setStatus($progress,'exporting article fail title='.$value['title']));
+                    continue;
+                }
+                $this->info($upload->setStatus($progress,'exporting article success title='.$article['title_text']));
+                $articles[] = [
+                    'level'=>$value['level']-$baseLevel,
+                    'title'=>$article['title_text'],
+                    'content'=>isset($article['html'])?$article['html']:'',
+                ];
+            }
+        }
+
+        $sections[] = [
+            'name'=>'articles',
+            'body'=>['articles'=>$articles],
+        ];
+        $this->info($upload->setStatus(0.9,'export article content done'));
+        Log::debug('导出结束');
+
+
+        $upload->upload('article',$sections,$bookMeta);
+        $this->info($upload->setStatus(1,'export article done'));
+        return 0;
+    }
+
+    private function fetch($articleId){
+        $api = config('mint.server.api.bamboo');
+        $basicUrl = $api . '/v2/article/';
+        $url =  $basicUrl . $articleId;;
+        $this->info('http request url='.$url);
+        $urlParam = [
+                'mode' => 'read',
+                'format' => 'html',
+                'anthology'=> $this->option('anthology'),
+                'channel' => $this->option('channel'),
+        ];
+
+        if($this->option('token')){
+            $response = Http::withToken($this->option('token'))->get($url,$urlParam);
+        }else{
+            $response = Http::get($url,$urlParam);
+        }
+
+        if($response->failed()){
+            $this->error('http request error'.$response->json('message'));
+            Log::error('http request error'.$response->json('message'));
+            return false;
+        }
+        if(!$response->json('ok')){
+            $this->error('http request error'.$response->json('message'));
+            return false;
+        }
+        $article = $response->json('data');
+        return $article;
+    }
+}

+ 26 - 130
app/Console/Commands/ExportChapter.php

@@ -4,23 +4,23 @@ namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
 use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
+
 use App\Models\ProgressChapter;
 use App\Models\Channel;
 use App\Models\PaliText;
 use App\Models\Sentence;
+
 use App\Http\Api\ChannelApi;
 use App\Http\Api\MdRender;
 use App\Tools\Export;
-use Illuminate\Support\Facades\Log;
 use App\Tools\RedisClusters;
-use Illuminate\Support\Str;
+use App\Tools\ExportDownload;
+
 
 class ExportChapter extends Command
 {
-    protected $exportStatusKey = 'export/status';
-    protected $exportStatusExpiry = 3600;
-    protected $currExportStatusKey = '';
-    protected $realFilename = null;
     /**
      * The name and signature of the console command.
      * php artisan export:chapter 213 3 a19eaf75-c63f-4b84-8125-1bce18311e23 213-3.html --format=html --origin=true
@@ -28,7 +28,7 @@ class ExportChapter extends Command
      * php artisan export:chapter 168 915 19f53a65-81db-4b7d-8144-ac33f1217d34 168-915.html --format=html --origin=true
      * @var string
      */
-    protected $signature = 'export:chapter {book} {para} {channel} {filename} {--origin=false} {--translation=true} {--debug} {--format=tex} ';
+    protected $signature = 'export:chapter {book} {para} {channel} {query_id} {--token=} {--origin=false} {--translation=true} {--debug} {--format=tex} ';
 
     /**
      * The console command description.
@@ -47,28 +47,6 @@ class ExportChapter extends Command
         parent::__construct();
     }
 
-    /**
-     * progress: 0-1, error -1
-     * message: string
-     */
-    protected function setStatus($progress,$message=''){
-        $data = [
-                    'progress'=>$progress,
-                    'message'=>$message,
-                    'content-type'=>'application/zip',
-                ];
-        if($this->realFilename){
-            $data['filename'] = $this->realFilename;
-        }
-        RedisClusters::put($this->currExportStatusKey,
-                            $data,
-                            $this->exportStatusExpiry);
-        $percent = (int)($progress * 100);
-        $this->info("[{$percent}%]".$message);
-    }
-    public function getStatus($filename){
-        return RedisClusters::get($this->exportStatusKey.'/'.$filename);
-    }
     /**
      * Execute the console command.
      *
@@ -81,17 +59,26 @@ class ExportChapter extends Command
         if(\App\Tools\Tools::isStop()){
             return 0;
         }
+        $book = $this->argument('book');
+        $para = $this->argument('para');
+
+        $upload = new ExportDownload([
+            'queryId'=>$this->argument('query_id'),
+            'format'=>$this->option('format'),
+            'debug'=>$this->option('debug'),
+            'filename'=>$book.'-'.$para,
+        ]);
+
         $m = new \Mustache_Engine(array('entity_flags'=>ENT_QUOTES,
                                         'delimiters' => '[[ ]]',
                                         'escape'=>function ($value){
                                             return $value;
                                         }));
-        $tplParagraph = file_get_contents(resource_path("mustache/".$this->option('format')."/paragraph.".$this->option('format')));
+        $tplFile = resource_path("mustache/chapter/".$this->option('format')."/paragraph.".$this->option('format'));
+        $tplParagraph = file_get_contents($tplFile);
 
         MdRender::init();
 
-        $this->currExportStatusKey = $this->exportStatusKey . '/' . $this->argument('filename');
-        $this->realFilename = $this->argument('filename').'.zip';
 
         switch ($this->option('format')) {
             case 'md':
@@ -104,10 +91,7 @@ class ExportChapter extends Command
                 $renderFormat=$this->option('format');
                 break;
         }
-        $book = $this->argument('book');
-        $para = $this->argument('para');
-        //zip压缩包里面的文件名
-        $realFileName = "{$book}-{$para}.".$this->option('format');
+
         //获取原文channel
         $orgChannelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
 
@@ -135,7 +119,7 @@ class ExportChapter extends Command
         }
 
         $currProgress = 0;
-        $this->setStatus($currProgress,'start');
+        $this->info($upload->setStatus($currProgress,'start'));
 
 
         if(empty($chapter->toc)){
@@ -227,7 +211,7 @@ class ExportChapter extends Command
 
             foreach ($chapterBody as $body) {
                 $currProgress += $step;
-                $this->setStatus($currProgress,'export chapter '.$body->paragraph);
+                $this->info($upload->setStatus($currProgress,'export chapter '.$body->paragraph));
                 $paraData = array();
                 $paraData['translations'] = array();
                 foreach ($outputChannelsId as $key => $channelId) {
@@ -296,101 +280,13 @@ class ExportChapter extends Command
                 ];
         }
 
-        $this->setStatus(0.9,'export content done');
-        Log::debug('导出结束');
-
-        $tex = array();
-
-        $tpl = file_get_contents(resource_path("mustache/".$this->option('format')."/main.".$this->option('format')));
-        $texContent = $m->render($tpl,$bookMeta);
-        $tex[] = ['name'=>'main.'.$this->option('format'),
-                  'content'=>$texContent
-                 ];
-        foreach ($sections as $key => $section) {
-            $tpl = file_get_contents(resource_path("mustache/".$this->option('format')."/section.".$this->option('format')));
-            $texContent = $m->render($tpl,$section['body']);
-            $tex[] = ['name'=>$section['name'],
-                  'content'=>$texContent
-                 ];
-        }
-
-        Log::debug('footnote start');
-        //footnote
-        $tplFile = resource_path("mustache/".$this->option('format')."/footnote.".$this->option('format'));
-        if(isset($GLOBALS['note']) &&
-            is_array($GLOBALS['note']) &&
-            count($GLOBALS['note'])>0 &&
-            file_exists($tplFile)){
-            $tpl = file_get_contents($tplFile);
-            $texContent = $m->render($tpl,['footnote'=>$GLOBALS['note']]);
-            $tex[] = ['name'=>'footnote.'.$this->option('format'),
-                        'content'=>$texContent
-                        ];
-        }
-        if($this->option('debug')){
-            $dir = "export/".$this->option('format')."/{$book}-{$para}-{$channelId}/";
-            foreach ($tex as $key => $section) {
-                Storage::disk('local')->put($dir.$section['name'], $section['content']);
-            }
-        }
+        $this->info($upload->setStatus(0.9,'export content done'));
 
-        Log::debug('footnote finished');
-        $this->setStatus(0.95,'export content done.');
-
-        //upload
-        $fileDate = '';
-        switch ($this->option('format')) {
-            case 'tex':
-                $data = Export::ToPdf($tex);
-                if($data['ok']){
-                    $this->info($data['content-type']);
-                    $fileDate = $data['data'];
-                }else{
-                    $this->error($data['code'].'-'.$data['message']);
-                }
-                break;
-            case 'html':
-                $file = array();
-                foreach ($tex as $key => $section) {
-                    $file[] = $section['content'];
-                }
-                $fileDate = implode('',$file);
-                break;
-        }
-
-
-        $zipDir = storage_path('app/export/zip');
-        if(!is_dir($zipDir)){
-            $res = mkdir($zipDir,0755,true);
-            if(!$res){
-                Log::error('mkdir fail path='.$zipDir);
-                return 1;
-            }
-        }
-        $zipFile = $zipDir.'/'.Str::uuid().'.zip';
+        Log::debug('导出结束');
 
-        Log::debug('export chapter start zip  file='.$zipFile);
+        $upload->upload('chapter',$sections,$bookMeta);
+        $this->info($upload->setStatus(1,'export chapter done'));
 
-        $zipOk = \App\Tools\Tools::zip($zipFile,[$realFileName=>$fileDate]);
-        if(!$zipOk){
-            Log::error('export chapter zip fail zip file='.$zipFile);
-            $this->setStatus(0.99,'export chapter zip fail');
-            $this->error('export chapter zip fail zip file='.$zipFile);
-            //TODO 给客户端返回错误状态
-            return 1;
-        }
-        $this->setStatus(0.96,'export chapter zip success');
-
-        $bucket = config('mint.attachments.bucket_name.temporary');
-        $tmpFile =  $bucket.'/'. $this->realFilename ;
-        Log::debug('upload start filename='.$tmpFile);
-        $this->setStatus(0.97,'upload start ');
-        $zipData = file_get_contents($zipFile);
-        Storage::put($tmpFile, $zipData);
-        $this->setStatus(1,'export chapter done');
-        Log::debug('export chapter done, upload filename='.$tmpFile);
-
-        unlink($zipFile);
         return 0;
     }
 }

+ 73 - 0
app/Console/Commands/MqExportArticle.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Http\Api\Mq;
+use Illuminate\Support\Facades\Log;
+
+class MqExportArticle extends Command
+{
+    /**
+     * The name and signature of the console command.
+     * php artisan mq:export.article
+     * @var string
+     */
+    protected $signature = 'mq:export.article';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '导出文章';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $exchange = 'router';
+        $queue = 'export_article';
+        $this->info(" [*] Waiting for {$queue}. To exit press CTRL+C");
+        Log::debug("mq:export_article start.");
+        Mq::worker($exchange,$queue,function ($message){
+            $data = [
+                        'id'=>$message->id,
+                        '--format'=>$message->format,
+                        'query_id'=>$message->queryId,
+                    ];
+            if(isset($message->token) && is_string($message->token)){
+                $data['--token'] = $message->token;
+            }
+            if(isset($message->anthology) && is_string($message->anthology)){
+                $data['--anthology'] = $message->anthology;
+            }
+            if(isset($message->channel) && is_string($message->channel)){
+                $data['--channel'] = $message->channel;
+            }
+            $ok = $this->call('export:article',$data);
+            if($ok !== 0){
+                Log::error('mq:export.article fail',$data);
+            }else{
+                $this->info("Received article id=".$message->id.' result='.$ok);
+                Log::debug("mq:export.article done ",$data);
+                return $ok;
+            }
+        });
+
+        return 0;
+    }
+}

+ 8 - 5
app/Console/Commands/MqExportPaliChapter.php

@@ -40,16 +40,16 @@ class MqExportPaliChapter extends Command
     public function handle()
     {
         $exchange = 'router';
-        $queue = 'export';
+        $queue = 'export_pali_chapter';
         $this->info(" [*] Waiting for {$queue}. To exit press CTRL+C");
-        Log::debug("mq:progress start.");
+        Log::debug("mq:export_pali_chapter start.");
         Mq::worker($exchange,$queue,function ($message){
             $data = [
                         'book'=>$message->book,
                         'para'=>$message->para,
                         'channel'=>$message->channel,
                         '--format'=>$message->format,
-                        'filename'=>$message->filename,
+                        'query_id'=>$message->queryId,
                     ];
             if(isset($message->origin) && is_string($message->origin)){
                 $data['--origin'] = $message->origin;
@@ -57,12 +57,15 @@ class MqExportPaliChapter extends Command
             if(isset($message->translation) && is_string($message->translation)){
                 $data['--translation'] = $message->translation;
             }
+            if(isset($message->token) && is_string($message->token)){
+                $data['--token'] = $message->token;
+            }
             $ok = $this->call('export:chapter',$data);
             if($ok !== 0){
-                Log::error('mq:progress upgrade:progress fail',$data);
+                Log::error('mq:export.pali.chapter upgrade:progress fail',$data);
             }else{
                 $this->info("Received book=".$message->book.' result='.$ok);
-                Log::debug("mq:export: done ",$data);
+                Log::debug("mq:export.pali.chapter done ",$data);
                 return $ok;
             }
         });

+ 13 - 0
app/Http/Api/AuthApi.php

@@ -7,6 +7,19 @@ use Firebase\JWT\JWT;
 use Firebase\JWT\Key;
 
 class AuthApi{
+    public static function getToken(Request $request){
+        $token = false;
+        if($request->hasHeader('Authorization')){
+            $token = $request->header('Authorization');
+            if(\substr($token,0,6) === 'Bearer'){
+                $token = trim(substr($token,6));
+                if($token === "null"){
+                    return false;
+                }
+            }
+        }
+        return $token;
+    }
     public static function current(Request $request){
         if($request->hasHeader('Authorization')){
             $token = $request->header('Authorization');

+ 0 - 1
app/Http/Api/MdRender.php

@@ -592,7 +592,6 @@ class MdRender{
                 $output = htmlspecialchars_decode($html,ENT_QUOTES);
                 break;
             case 'html':
-                $html = str_replace(['<p>','</p>'],['',''],$html);
                 $output = htmlspecialchars_decode($html,ENT_QUOTES);
                 break;
         }

+ 8 - 0
app/Http/Api/TemplateRender.php

@@ -580,6 +580,14 @@ class TemplateRender{
                     }
                 }
                 break;
+            case 'html':
+                $output = '';
+                if(isset($props['translation']) && is_array($props['translation'])){
+                    foreach ($props['translation'] as $key => $value) {
+                        $output .= '<span class="sentence">'.$value['html'].'</span>';
+                    }
+                }
+                break;
             case 'tex':
                 $output = '';
                 if(isset($props['translation']) && is_array($props['translation'])){

+ 1 - 1
app/Http/Controllers/ArticleController.php

@@ -340,7 +340,7 @@ class ArticleController extends Controller
 
         $canRead = ArticleController::userCanRead($user_uid,$article);
         if(!$canRead){
-            return $this->error(__('auth.failed'),[],403);
+            return $this->error(__('auth.failed'),403,403);
         }
         return $this->ok(new ArticleResource($article));
     }

+ 47 - 35
app/Http/Controllers/ExportController.php

@@ -4,12 +4,14 @@ namespace App\Http\Controllers;
 
 use Illuminate\Http\Request;
 use Illuminate\Support\Str;
-use App\Http\Api\Mq;
 use Illuminate\Support\Facades\Log;
-use App\Tools\RedisClusters;
 use Illuminate\Support\Facades\App;
 use Illuminate\Support\Facades\Storage;
-use App\Console\Commands\ExportChapter;
+
+use App\Http\Api\AuthApi;
+use App\Http\Api\Mq;
+use App\Tools\RedisClusters;
+use App\Tools\ExportDownload;
 
 class ExportController extends Controller
 {
@@ -20,22 +22,46 @@ class ExportController extends Controller
      */
     public function index(Request $request)
     {
-        //
-        $filename = $request->get('book').'-'.
-                    $request->get('par').'-'.
-                    Str::uuid().'.'.$request->get('format');
-
+        $queryId = Str::uuid();
+        $token = AuthApi::getToken($request);
+        switch ($request->get('type','chapter')) {
+            case 'chapter':
+                $data = [
+                    'book'=>$request->get('book'),
+                    'para'=>$request->get('par'),
+                    'channel'=>$request->get('channel'),
+                    'format'=>$request->get('format'),
+                    'origin'=>$request->get('origin'),
+                    'translation'=>$request->get('translation'),
+                    'queryId'=>$queryId,
+                ];
+                if($token){
+                    $data['token'] = $token;
+                }
+                Mq::publish('export_pali_chapter',$data);
+                break;
+            case 'article':
+                $data = [
+                    'id'=>$request->get('id'),
+                    'channel'=>$request->get('channel'),
+                    'format'=>$request->get('format'),
+                    'origin'=>$request->get('origin'),
+                    'translation'=>$request->get('translation'),
+                    'queryId'=>$queryId,
+                    'anthology'=>$request->get('anthology'),
+                    'channel'=>$request->get('channel'),
+                ];
+                if($token){
+                    $data['token'] = $token;
+                }
+                Mq::publish('export_article',$data);
+                break;
+            default:
+                return $this->error('unknown type '.$request->get('type'),400,400);
+                break;
+        }
 
-        Mq::publish('export',[
-            'book'=>$request->get('book'),
-            'para'=>$request->get('par'),
-            'channel'=>$request->get('channel'),
-            'format'=>$request->get('format'),
-            'origin'=>$request->get('origin'),
-            'translation'=>$request->get('translation'),
-            'filename'=>$filename,
-        ]);
-        return $this->ok($filename);
+        return $this->ok($queryId);
     }
 
     /**
@@ -58,8 +84,8 @@ class ExportController extends Controller
     public function show($filename)
     {
         //
-        $exportChapter = new ExportChapter();
-        $exportStatus = $exportChapter->getStatus($filename);
+        $exportChapter = new ExportDownload(['queryId'=>$filename]);
+        $exportStatus = $exportChapter->getStatus();
         if(empty($exportStatus)){
             return $this->error('no file',200,200);
         };
@@ -67,21 +93,7 @@ class ExportController extends Controller
         $output = array();
         $output['status'] = $exportStatus;
         if($exportStatus['progress']===1){
-            $realFilename = $exportStatus['filename'];
-            $bucket = config('mint.attachments.bucket_name.temporary');
-            $tmpFile =  $bucket.'/'. $realFilename ;
-            Log::debug('export download filename='.$tmpFile);
-            if (App::environment('local')) {
-                $s3Link = Storage::url($tmpFile);
-            }else{
-                try{
-                    $s3Link = Storage::temporaryUrl($tmpFile, now()->addDays(7));
-                }catch(\Exception $e){
-                    Log::error('export {Exception}',['exception'=>$e]);
-                    return $this->error('temporaryUrl fail',404,404);
-                }
-            }
-            $output['url'] = $s3Link;
+            $output['url'] = $exportStatus['url'];
         }
         return $this->ok($output);
 

+ 7 - 2
app/Http/Resources/ArticleResource.php

@@ -193,9 +193,14 @@ class ArticleResource extends JsonResource
             }
 
             $mode = $request->get('mode','read');
-            $data["html"] = MdRender::render($this->content,$channels,$query_id,$mode);
+            $format = $request->get('format','react');
+            $data["html"] = MdRender::render($this->content,
+                                            $channels,$query_id,$mode,
+                                            'translation','markdown',$format);
             if(empty($this->summary)){
-                $data["_summary"] = MdRender::render($this->content,$channels,$query_id,$mode,'translation','markdown','text');
+                $data["_summary"] = MdRender::render($this->content,
+                                                    $channels,$query_id,$mode,
+                                                    'translation','markdown','text');
             }
         }
         return $data;

+ 194 - 0
app/Tools/ExportDownload.php

@@ -0,0 +1,194 @@
+<?php
+namespace App\Tools;
+
+use Illuminate\Support\Str;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\App;
+
+use App\Tools\RedisClusters;
+use App\Tools\Export;
+
+class ExportDownload
+{
+    protected $statusKey = 'export/status';
+    protected $statusExpiry = 3600;
+    protected $currStatusKey = '';
+
+    protected $queryId = 'id';
+    protected $realFilename = 'index';//压缩包里的文件名
+    protected $zipFilename = 'file.zip';
+    protected $downloadUrl = null;
+
+    protected $format = 'tex';
+    protected $debug = false;
+
+    protected $logs = [];
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct($options=[])
+    {
+        if(isset($options['format'])){
+            $this->format = $options['format'];
+        }
+        if(isset($options['debug'])){
+            $this->debug = $options['debug'];
+        }
+        if(isset($options['filename'])){
+            $this->realFilename = $options['filename'];
+        }
+        $this->queryId = $options['queryId'];
+        $this->zipFilename = $this->queryId.'.zip';
+
+        $this->currStatusKey = $this->statusKey . '/' . $this->queryId;
+    }
+
+    /**
+     * progress: 0-1, error -1
+     * message: string
+     */
+    public function setStatus($progress,$message=''){
+        $this->logs[] = $message;
+        $data = [
+                    'progress'=>$progress,
+                    'message'=>$message,
+                    'log'=>$this->logs,
+                    'content-type'=>'application/zip',
+                ];
+        if($this->downloadUrl){
+            $data['url'] = $this->downloadUrl;
+        }
+        RedisClusters::put($this->currStatusKey,
+                           $data,
+                           $this->statusExpiry);
+        $percent = (int)($progress * 100);
+        return "[{$percent}%]".$message;
+    }
+
+
+    public function getStatus(){
+        return RedisClusters::get($this->currStatusKey);
+    }
+
+    public function upload(string $type,$sections,$bookMeta){
+        $m = new \Mustache_Engine(array('entity_flags'=>ENT_QUOTES,
+                    'delimiters' => '[[ ]]',
+                    'escape'=>function ($value){
+                        return $value;
+                    }));
+
+        $tex = array();
+
+        $tplFile = resource_path("mustache/".$type.'/'.$this->format."/main.".$this->format);
+        $tpl = file_get_contents($tplFile);
+        $texContent = $m->render($tpl,$bookMeta);
+        $tex[] = ['name'=>'main.'.$this->format,
+                  'content'=>$texContent
+                 ];
+        foreach ($sections as $key => $section) {
+            $tplFile = resource_path("mustache/".$type.'/'.$this->format."/section.".$this->format);
+            $tpl = file_get_contents($tplFile);
+            $texContent = $m->render($tpl,$section['body']);
+            $tex[] = ['name'=>$section['name'],
+                  'content'=>$texContent
+                 ];
+        }
+
+        Log::debug('footnote start');
+        //footnote
+        $tplFile = resource_path("mustache/".$this->format."/footnote.".$this->format);
+        if(isset($GLOBALS['note']) &&
+            is_array($GLOBALS['note']) &&
+            count($GLOBALS['note'])>0 &&
+            file_exists($tplFile)){
+            $tpl = file_get_contents($tplFile);
+            $texContent = $m->render($tpl,['footnote'=>$GLOBALS['note']]);
+            $tex[] = ['name'=>'footnote.'.$this->format,
+                        'content'=>$texContent
+                        ];
+        }
+        if($this->debug){
+            $dir = "export/{$type}/".$this->format."/".$this->zipFilename."/";
+            foreach ($tex as $key => $section) {
+                Storage::disk('local')->put($dir.$section['name'], $section['content']);
+            }
+        }
+
+        Log::debug('footnote finished');
+        $this->setStatus(0.95,'export content done.');
+
+        //upload
+        $fileDate = '';
+        switch ($this->format) {
+            case 'tex':
+                $data = Export::ToPdf($tex);
+                if($data['ok']){
+                    $this->info($data['content-type']);
+                    $fileDate = $data['data'];
+                }else{
+                    $this->error($data['code'].'-'.$data['message']);
+                }
+                break;
+            case 'html':
+                $file = array();
+                foreach ($tex as $key => $section) {
+                    $file[] = $section['content'];
+                }
+                $fileDate = implode('',$file);
+                break;
+        }
+
+
+        $zipDir = storage_path('app/export/zip');
+        if(!is_dir($zipDir)){
+            $res = mkdir($zipDir,0755,true);
+            if(!$res){
+                Log::error('mkdir fail path='.$zipDir);
+                return 1;
+            }
+        }
+
+        $zipFile = $zipDir.'/'.Str::uuid().'.zip';
+
+        Log::debug('export chapter start zip  file='.$zipFile);
+        //zip压缩包里面的文件名
+        $realFilename = $this->realFilename.".".$this->format;
+
+        $zipOk = \App\Tools\Tools::zip($zipFile,[$realFilename=>$fileDate]);
+        if(!$zipOk){
+            Log::error('export chapter zip fail zip file='.$zipFile);
+            $this->setStatus(0.99,'export chapter zip fail');
+            $this->error('export chapter zip fail zip file='.$zipFile);
+            //TODO 给客户端返回错误状态
+            return 1;
+        }
+        $this->setStatus(0.96,'export chapter zip success');
+
+        $bucket = config('mint.attachments.bucket_name.temporary');
+        $tmpFile =  $bucket.'/'. $this->zipFilename ;
+        Log::debug('upload start filename='.$tmpFile);
+        $this->setStatus(0.97,'upload start ');
+        $zipData = file_get_contents($zipFile);
+        Storage::put($tmpFile, $zipData);
+
+        if (App::environment('local')) {
+            $s3Link = Storage::url($tmpFile);
+        }else{
+            try{
+                $s3Link = Storage::temporaryUrl($tmpFile, now()->addDays(7));
+            }catch(\Exception $e){
+                Log::error('export {Exception}',['exception'=>$e]);
+                return false;
+            }
+        }
+        $this->downloadUrl = $s3Link;
+        $this->setStatus(1,'export chapter done');
+        Log::debug('export chapter done, upload filename='.$tmpFile);
+        unlink($zipFile);
+        return true;
+    }
+}

+ 4 - 1
config/mint.php

@@ -32,7 +32,10 @@ return [
                 'port' => env('TULIP_GRPC_PORT', 9990),
             ],
         ],
-
+        'api' => [
+            'default' => env('APP_API', "http://localhost:8000/api"),
+            'bamboo' => env('BAMBOO_API_HOST', "http://localhost:8000/api"),
+        ],
         'assets' => env('ASSETS_SERVER', "localhost:9999"),
 
         'dashboard_base_path' => env('DASHBOARD_BASE_PATH', "http://127.0.0.1:3000/my"),

+ 0 - 0
resources/mustache/html/footnote.html → resources/mustache/article/html/footnote.html


+ 0 - 0
resources/mustache/html/glossary.html → resources/mustache/article/html/glossary.html


+ 0 - 0
resources/mustache/html/main.html → resources/mustache/article/html/main.html


+ 5 - 0
resources/mustache/article/html/section.html

@@ -0,0 +1,5 @@
+[[#articles]]
+<h[[level]]>[[title]]</h[[level]]>
+[[#subtitle]]<div class="subtitle">[[subtitle]]</div>[[/subtitle]]
+<div class="content">[[content]]</div>
+[[/articles]]

+ 6 - 0
resources/mustache/chapter/html/footnote.html

@@ -0,0 +1,6 @@
+<h2 id="footnote">脚注</h2>
+<div class="footnote">
+[[#footnote]]
+<div class="item" id="footnote-[[sn]]"><a href="#note-[[sn]]" name="footnote-[[sn]]">[[sn]]</a>[[trigger]]:[[content]]</div>
+[[/footnote]]
+</div>

+ 9 - 0
resources/mustache/chapter/html/glossary.html

@@ -0,0 +1,9 @@
+<h2>glossary</h2>
+<div class="glossary">
+[[#pali]]
+<div class="item pali"><span class="head">[[pali]]</span><span class="content">[[meaning]]</span></div>
+[[/pali]]
+[[#meaning]]
+<div class="item meaning"><span class="head">[[meaning]]</span><span  class="content">[[pali]]</span></div>
+[[/meaning]]
+</div>

+ 48 - 0
resources/mustache/chapter/html/main.html

@@ -0,0 +1,48 @@
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <title>[[book_title]]</title>
+    <style>
+        p {
+            text-indent: 2em;
+            line-height: 1.7em;
+        }
+        a {
+            text-decoration: none;
+        }
+        .paragraph {
+            margin-bottom: 1em;
+        }
+
+        .row {
+            display: flex;
+        }
+        @media screen and (max-width: 960px){
+            .row {
+                flex-direction: column;
+            }
+        }
+        .col {
+            flex: 5;
+        }
+        code {
+            background-color: #ffe4c478;
+            padding: 2px 6px;
+            border-radius: 4px;
+        }
+        .origin {
+            font-family: times;
+        }
+    </style>
+</head>
+<body>
+<h1>[[book_title]]</h1>
+
+<h2>目录</h2>
+<ul>
+    [[#sections]]
+    <li><a href="#[[filename]]">[[filename]]</a></li>
+    [[/sections]]
+</ul>
+

+ 0 - 0
resources/mustache/html/paragraph.html → resources/mustache/chapter/html/paragraph.html


+ 0 - 0
resources/mustache/html/section.html → resources/mustache/chapter/html/section.html


+ 0 - 0
resources/mustache/html/sentence.html → resources/mustache/chapter/html/sentence.html


+ 0 - 0
resources/mustache/tex/main.tex → resources/mustache/chapter/tex/main.tex


+ 0 - 0
resources/mustache/tex/paragraph.tex → resources/mustache/chapter/tex/paragraph.tex


+ 0 - 0
resources/mustache/tex/section.tex → resources/mustache/chapter/tex/section.tex


+ 0 - 0
resources/mustache/tex/sentence.tex → resources/mustache/chapter/tex/sentence.tex