Selaa lähdekoodia

Merge pull request #1668 from visuddhinanda/laravel

s3 add bucket
visuddhinanda 2 vuotta sitten
vanhempi
sitoutus
640888dd7b

+ 4 - 3
app/Console/Commands/CacheDictPreference.php

@@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Cache;
 use App\Models\UserDict;
 use App\Models\UserDict;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\DB;
+use App\Tools\RedisClusters;
 
 
 class CacheDictPreference extends Command
 class CacheDictPreference extends Command
 {
 {
@@ -48,9 +49,9 @@ class CacheDictPreference extends Command
         $words = UserDict::select(['word','language'])
         $words = UserDict::select(['word','language'])
                        ->groupBy(['word','language'])
                        ->groupBy(['word','language'])
                        ->cursor();
                        ->cursor();
-        $count = DB::select('SELECT count(*) from (
+        $wordCount = DB::select('SELECT count(*) from (
                      SELECT word,language from user_dicts group by word,language) T');
                      SELECT word,language from user_dicts group by word,language) T');
-        $bar = $this->output->createProgressBar($count[0]->count);
+        $bar = $this->output->createProgressBar($wordCount[0]->count);
         $count = 0;
         $count = 0;
         foreach ($words as $key => $word) {
         foreach ($words as $key => $word) {
             $meaning = UserDict::where('word',$word->word)
             $meaning = UserDict::where('word',$word->word)
@@ -61,7 +62,7 @@ class CacheDictPreference extends Command
             $meaning = trim($meaning," $");
             $meaning = trim($meaning," $");
             if(!empty($meaning)){
             if(!empty($meaning)){
                 $m = explode('$',$meaning);
                 $m = explode('$',$meaning);
-                Cache::put("{$prefix}/{$word->word}/{$word->language}",$m[0]);
+                RedisClusters::put("{$prefix}/{$word->word}/{$word->language}",$m[0]);
             }
             }
             $bar->advance();
             $bar->advance();
             $count++;
             $count++;

+ 5 - 4
app/Console/Commands/CacheWbwPreference.php

@@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Cache;
 use App\Models\WbwAnalysis;
 use App\Models\WbwAnalysis;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\DB;
+use App\Tools\RedisClusters;
 
 
 class CacheWbwPreference extends Command
 class CacheWbwPreference extends Command
 {
 {
@@ -53,24 +54,24 @@ class CacheWbwPreference extends Command
              * 最新优先
              * 最新优先
              */
              */
             $wbw = WbwAnalysis::select(['wbw_word','type','editor_id']);
             $wbw = WbwAnalysis::select(['wbw_word','type','editor_id']);
-            $count = DB::select('SELECT count(*) from (
+            $wbwCount = DB::select('SELECT count(*) from (
                 SELECT wbw_word,type,editor_id from wbw_analyses group by wbw_word,type,editor_id) T');
                 SELECT wbw_word,type,editor_id from wbw_analyses group by wbw_word,type,editor_id) T');
             if($this->option('editor')){
             if($this->option('editor')){
                 $wbw = $wbw->where('editor_id',$this->option('editor'));
                 $wbw = $wbw->where('editor_id',$this->option('editor'));
-                $count = DB::select('SELECT count(*) from (
+                $wbwCount = DB::select('SELECT count(*) from (
                     SELECT wbw_word,type,editor_id from wbw_analyses where editor_id=? group by wbw_word,type,editor_id) T',
                     SELECT wbw_word,type,editor_id from wbw_analyses where editor_id=? group by wbw_word,type,editor_id) T',
                     [$this->option('editor')]);
                     [$this->option('editor')]);
             }
             }
             $wbw = $wbw->groupBy(['wbw_word','type','editor_id'])->cursor();
             $wbw = $wbw->groupBy(['wbw_word','type','editor_id'])->cursor();
+            $bar = $this->output->createProgressBar($wbwCount[0]->count);
             $count = 0;
             $count = 0;
-            $bar = $this->output->createProgressBar($count[0]->count);
             foreach ($wbw as $key => $value) {
             foreach ($wbw as $key => $value) {
                 $data = WbwAnalysis::where('wbw_word',$value->wbw_word)
                 $data = WbwAnalysis::where('wbw_word',$value->wbw_word)
                                     ->where('type',$value->type)
                                     ->where('type',$value->type)
                                     ->where('editor_id',$value->editor_id)
                                     ->where('editor_id',$value->editor_id)
                                     ->orderBy('updated_at','desc')
                                     ->orderBy('updated_at','desc')
                                     ->value('data');
                                     ->value('data');
-                Cache::put("{$prefix}/{$value->wbw_word}/{$value->type}/{$value->editor_id}",$data);
+                RedisClusters::put("{$prefix}/{$value->wbw_word}/{$value->type}/{$value->editor_id}",$data);
                 $bar->advance();
                 $bar->advance();
                 $count++;
                 $count++;
                 if($count%1000 === 0){
                 if($count%1000 === 0){

+ 77 - 19
app/Console/Commands/ExportChapter.php

@@ -17,10 +17,10 @@ class ExportChapter extends Command
 {
 {
     /**
     /**
      * The name and signature of the console command.
      * The name and signature of the console command.
-     * php artisan export:chapter 213 1849 a19eaf75-c63f-4b84-8125-1bce18311e23
+     * php artisan export:chapter 213 1913 a19eaf75-c63f-4b84-8125-1bce18311e23 --format=html
      * @var string
      * @var string
      */
      */
-    protected $signature = 'export:chapter {book} {para} {channel} {--debug}';
+    protected $signature = 'export:chapter {book} {para} {channel} {--debug} {--format=tex}';
 
 
     /**
     /**
      * The console command description.
      * The console command description.
@@ -50,6 +50,17 @@ class ExportChapter extends Command
         if(\App\Tools\Tools::isStop()){
         if(\App\Tools\Tools::isStop()){
             return 0;
             return 0;
         }
         }
+        switch ($this->option('format')) {
+            case 'md':
+                $renderFormat='markdown';
+                break;
+            case 'html':
+                $renderFormat='html';
+                break;
+            default:
+                $renderFormat=$this->option('format');
+                break;
+        }
         $book = $this->argument('book');
         $book = $this->argument('book');
         $para = $this->argument('para');
         $para = $this->argument('para');
         $channelId = $this->argument('channel');
         $channelId = $this->argument('channel');
@@ -82,7 +93,7 @@ class ExportChapter extends Command
                                     ->where('channel_id',$channelId)
                                     ->where('channel_id',$channelId)
                                     ->first();
                                     ->first();
             if($chapter){
             if($chapter){
-                $filename = "{$sub->paragraph}.tex";
+                $filename = "{$sub->paragraph}.".$this->option('format');
                 $bookMeta['sections'][] = ['filename'=>$filename];
                 $bookMeta['sections'][] = ['filename'=>$filename];
                 $paliTitle = PaliText::where('book',$book)->where('paragraph',$sub->paragraph)->value('toc');
                 $paliTitle = PaliText::where('book',$book)->where('paragraph',$sub->paragraph)->value('toc');
                 $title = $chapter->title?$chapter->title:$paliTitle;
                 $title = $chapter->title?$chapter->title:$paliTitle;
@@ -107,13 +118,24 @@ class ExportChapter extends Command
                                                         'read',
                                                         'read',
                                                         $channel['type'],
                                                         $channel['type'],
                                                         $sent->content_type,
                                                         $sent->content_type,
-                                                        'tex'
+                                                        $renderFormat
                                                         );
                                                         );
                         $sentContent[] = trim($texText);
                         $sentContent[] = trim($texText);
                     }
                     }
                     $paraContent = implode(' ',$sentContent);
                     $paraContent = implode(' ',$sentContent);
                     if($body->level > 7){
                     if($body->level > 7){
-                        $content[] = '\par '.$paraContent;
+                        switch ($this->option('format')) {
+                            case 'tex':
+                                $content[] = '\par '.$paraContent;
+                                break;
+                            case 'html':
+                                $content[] = '<p>'.$paraContent.'</p>';
+                                break;
+                            case 'md':
+                                $content[] = "\n\n".$paraContent;
+                                break;
+                        }
+
                     }else{
                     }else{
                         $currLevel = $body->level - $sub->level;
                         $currLevel = $body->level - $sub->level;
                         if($currLevel>0){
                         if($currLevel>0){
@@ -124,13 +146,26 @@ class ExportChapter extends Command
                             }else{
                             }else{
                                 $subSessionTitle = $paraContent;
                                 $subSessionTitle = $paraContent;
                             }
                             }
-                            $subStr = array_fill(0,$currLevel,'sub');
-                            $content[] = '\\'. implode('',$subStr) . "section{".$subSessionTitle.'}';
+                            switch ($this->option('format')) {
+                                case 'tex':
+                                    $subStr = array_fill(0,$currLevel,'sub');
+                                    $content[] = '\\'. implode('',$subStr) . "section{".$subSessionTitle.'}';
+                                    break;
+                                case 'md':
+                                    $subStr = array_fill(0,$currLevel,'#');
+                                    $content[] = implode('',$subStr) . " ".$subSessionTitle;
+                                    break;
+                                case 'html':
+                                    $level = $currLevel+2;
+                                    $content[] = "<h{$currLevel}".$subSessionTitle."</h{$currLevel}";
+                                    break;
+                            }
+
                         }else{
                         }else{
                             $content[] = '\par '.$paraContent;
                             $content[] = '\par '.$paraContent;
                         }
                         }
                     }
                     }
-                    $content[] = "\n";
+                    $content[] = "\n\n";
                 }
                 }
 
 
                 $sections[] = [
                 $sections[] = [
@@ -148,32 +183,55 @@ class ExportChapter extends Command
                                         'escape'=>function ($value){
                                         'escape'=>function ($value){
                                             return $value;
                                             return $value;
                                         }));
                                         }));
-        $tpl = file_get_contents(resource_path("mustache/tex/main.tex"));
+        $tpl = file_get_contents(resource_path("mustache/".$this->option('format')."/main.".$this->option('format')));
         $texContent = $m->render($tpl,$bookMeta);
         $texContent = $m->render($tpl,$bookMeta);
-        $tex[] = ['name'=>'main.tex',
+        $tex[] = ['name'=>'main.'.$this->option('format'),
                   'content'=>$texContent
                   'content'=>$texContent
                  ];
                  ];
         foreach ($sections as $key => $section) {
         foreach ($sections as $key => $section) {
-            $tpl = file_get_contents(resource_path("mustache/tex/section.tex"));
+            $tpl = file_get_contents(resource_path("mustache/".$this->option('format')."/section.".$this->option('format')));
             $texContent = $m->render($tpl,$section['body']);
             $texContent = $m->render($tpl,$section['body']);
             $tex[] = ['name'=>$section['name'],
             $tex[] = ['name'=>$section['name'],
                   'content'=>$texContent
                   'content'=>$texContent
                  ];
                  ];
         }
         }
 
 
+        //脚注
+        $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')){
         if($this->option('debug')){
-            $dir = "export/{$book}-{$para}-{$channelId}/";
+            $dir = "export/".$this->option('format')."/{$book}-{$para}-{$channelId}/";
             foreach ($tex as $key => $section) {
             foreach ($tex as $key => $section) {
                 Storage::disk('local')->put($dir.$section['name'], $section['content']);
                 Storage::disk('local')->put($dir.$section['name'], $section['content']);
             }
             }
         }
         }
-        $data = Export::ToPdf($tex);
-        if($data['ok']){
-            $filename = "export/{$book}-{$para}-{$channelId}.pdf";
-            $this->info($data['content-type']);
-            Storage::disk('local')->put($filename, $data['data']);
-        }else{
-            $this->error($data['code'].'-'.$data['message']);
+        switch ($this->option('format')) {
+            case 'tex':
+                $data = Export::ToPdf($tex);
+                if($data['ok']){
+                    $filename = "export/{$book}-{$para}-{$channelId}.pdf";
+                    $this->info($data['content-type']);
+                    Storage::disk('local')->put($filename, $data['data']);
+                }else{
+                    $this->error($data['code'].'-'.$data['message']);
+                }
+                break;
+            case 'html':
+                $fHtml = "export/".$this->option('format')."/{$book}-{$para}-{$channelId}.html";
+                Storage::disk('local')->put($fHtml, '');
+                foreach ($tex as $key => $section) {
+                    Storage::disk('local')->append($fHtml, $section['content']);
+                }
+                break;
         }
         }
 
 
         Log::debug('task export offline chapter-table finished');
         Log::debug('task export offline chapter-table finished');

+ 19 - 17
app/Console/Commands/ExportOffline.php

@@ -16,7 +16,7 @@ class ExportOffline extends Command
      * php artisan export:offline lzma
      * php artisan export:offline lzma
      * @var string
      * @var string
      */
      */
-    protected $signature = 'export:offline {format?  : zip file format 7z,lzma,gz }';
+    protected $signature = 'export:offline {format?  : zip file format 7z,lzma,gz } {--shortcut}';
 
 
     /**
     /**
      * The console command description.
      * The console command description.
@@ -69,22 +69,24 @@ class ExportOffline extends Command
         $this->info('channel');
         $this->info('channel');
         $this->call('export:channel');
         $this->call('export:channel');
 
 
-        //tag
-        $this->info('tag');
-        $this->call('export:tag');
-        $this->call('export:tag.map');
-        //
-        $this->info('pali text');
-        $this->call('export:pali.text');
-        //导出章节索引
-        $this->info('chapter');
-        $this->call('export:chapter.index');
-        //导出译文
-        $this->info('sentence');
-        $this->call('export:sentence',['--type'=>'translation']);
-        $this->call('export:sentence',['--type'=>'nissaya']);
-        //导出原文
-        $this->call('export:sentence',['--type'=>'original']);
+        if(!$this->option('shortcut')){
+            //tag
+            $this->info('tag');
+            $this->call('export:tag');
+            $this->call('export:tag.map');
+            //
+            $this->info('pali text');
+            $this->call('export:pali.text');
+            //导出章节索引
+            $this->info('chapter');
+            $this->call('export:chapter.index');
+            //导出译文
+            $this->info('sentence');
+            $this->call('export:sentence',['--type'=>'translation']);
+            $this->call('export:sentence',['--type'=>'nissaya']);
+            //导出原文
+            $this->call('export:sentence',['--type'=>'original']);
+        }
 
 
         $this->info('zip');
         $this->info('zip');
         Log::debug('export offline: db写入完毕');
         Log::debug('export offline: db写入完毕');

+ 30 - 5
app/Console/Commands/ExportZip.php

@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
 use App\Tools\RedisClusters;
 use App\Tools\RedisClusters;
+use Illuminate\Support\Facades\App;
 
 
 class ExportZip extends Command
 class ExportZip extends Command
 {
 {
@@ -95,12 +96,36 @@ class ExportZip extends Command
                 ];
                 ];
         }
         }
 
 
-        //s3
-        Storage::put($zipFile, file_get_contents($zipFullFileName));
-        $s3Link = Storage::url($zipFile);
-        Log::info('export offline: link='.$s3Link);
+        $this->info('upload file='.$zipFile);
+        Log::debug('export offline: upload file {filename}',['filename'=>$zipFile]);
+
+        $bucket = 'attachments-'.config('app.env');
+        $tmpFile =  "{$bucket}\{$zipFile}";
+        Storage::put($tmpFile, file_get_contents($zipFullFileName));
+
+        $this->info('upload done file='.$tmpFile);
+        Log::debug('export offline: upload done {filename}',['filename'=>$tmpFile]);
+
+        if (App::environment('local')) {
+            $link = Storage::url($tmpFile);
+        }else{
+            try{
+                $link = Storage::temporaryUrl($tmpFile, now()->addDays(2));
+            }catch(\Exception $e){
+                $this->error('generate temporaryUrl fail');
+                Log::error('export offline: generate temporaryUrl fail {Exception}',
+                            [
+                                'exception'=>$e,
+                                'file'=>$tmpFile
+                            ]);
+                return 1;
+            }
+        }
+        $this->info('link = '.$link);
+        Log::info('export offline: link='.$link);
+
         $url[] = [
         $url[] = [
-            'link'=>$s3Link,
+            'link'=>$link,
             'hostname'=>'Amazon cloud storage(Hongkong)',
             'hostname'=>'Amazon cloud storage(Hongkong)',
         ];
         ];
         $info[] = ['filename'=>$zipFile,
         $info[] = ['filename'=>$zipFile,

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

@@ -14,7 +14,7 @@ class TestMdRender extends Command
      * run php artisan test:md.render term unity
      * run php artisan test:md.render term unity
      * @var string
      * @var string
      */
      */
-    protected $signature = 'test:md.render {item?} {--format}';
+    protected $signature = 'test:md.render {item?} {--format=}';
 
 
     /**
     /**
      * The console command description.
      * The console command description.
@@ -59,11 +59,8 @@ class TestMdRender extends Command
         md;
         md;
 
 
         $data['term'] = <<<md
         $data['term'] = <<<md
-        ## heading
+        ## term
         [[bhagavantu]]
         [[bhagavantu]]
-        ```
-        test
-        ```
         md;
         md;
         $data['noteMulti'] = <<<md
         $data['noteMulti'] = <<<md
         ## heading
         ## heading
@@ -130,11 +127,11 @@ class TestMdRender extends Command
         //$sent = MdRender::take_sentence($html);
         //$sent = MdRender::take_sentence($html);
         //print_r($sent);
         //print_r($sent);
 
 
-        $formats = $this->option('format');
+        $format = $this->option('format');
         if(empty($format)){
         if(empty($format)){
-            $formats = ['react','unity','text','tex'];
+            $formats = ['react','unity','text','tex','html'];
         }else{
         }else{
-            $formats = [$formats];
+            $formats = [$format];
         }
         }
         foreach ($formats as $format) {
         foreach ($formats as $format) {
             $this->info("format:{$format}");
             $this->info("format:{$format}");

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

@@ -289,6 +289,18 @@ class MdRender{
                     return '';
                     return '';
                 }
                 }
                 break;
                 break;
+            case 'html':
+                if(isset($tplProps)){
+                    if(is_array($tplProps)){
+                        return '';
+                    }else{
+                        return $tplProps;
+                    }
+                }else{
+                    Log::error('tplProps undefine');
+                    return '';
+                }
+                break;
             case 'text':
             case 'text':
                 if(isset($tplProps)){
                 if(isset($tplProps)){
                     if(is_array($tplProps)){
                     if(is_array($tplProps)){
@@ -300,7 +312,6 @@ class MdRender{
                     Log::error('tplProps undefine');
                     Log::error('tplProps undefine');
                     return '';
                     return '';
                 }
                 }
-
                 break;
                 break;
             case 'tex':
             case 'tex':
                 if(isset($tplProps)){
                 if(isset($tplProps)){
@@ -558,6 +569,10 @@ class MdRender{
                 $html = str_replace(['[%b%]','[%/b%]','[%i%]','[%/i%]'],['<b>','</b>','<i>','</i>'],$html);
                 $html = str_replace(['[%b%]','[%/b%]','[%i%]','[%/i%]'],['<b>','</b>','<i>','</i>'],$html);
                 $output = htmlspecialchars_decode($html,ENT_QUOTES);
                 $output = htmlspecialchars_decode($html,ENT_QUOTES);
                 break;
                 break;
+            case 'html':
+                $html = str_replace(['<p>','</p>'],['',''],$html);
+                $output = htmlspecialchars_decode($html,ENT_QUOTES);
+                break;
         }
         }
         return $output;
         return $output;
     }
     }

+ 52 - 43
app/Http/Api/Mq.php

@@ -1,52 +1,56 @@
 <?php
 <?php
+
 namespace App\Http\Api;
 namespace App\Http\Api;
+
 use PhpAmqpLib\Connection\AMQPStreamConnection;
 use PhpAmqpLib\Connection\AMQPStreamConnection;
 use PhpAmqpLib\Message\AMQPMessage;
 use PhpAmqpLib\Message\AMQPMessage;
 use PhpAmqpLib\Exchange\AMQPExchangeType;
 use PhpAmqpLib\Exchange\AMQPExchangeType;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
 
 
-class Mq{
+class Mq
+{
 
 
-    private static function connection(){
+    private static function connection()
+    {
         $host = config("queue.connections.rabbitmq.host");
         $host = config("queue.connections.rabbitmq.host");
-            $port = config("queue.connections.rabbitmq.port");
-            $user = config("queue.connections.rabbitmq.user");
-            $password = config("queue.connections.rabbitmq.password");
-            $vhost = config("queue.connections.rabbitmq.password");
-            if(empty($host) || empty($port) || empty($user) || empty($password) || empty($vhost)){
-                Log::error('rabbitmq set error');
-                return;
-            }
-            $connection = new AMQPStreamConnection($host,$port,$user,$password,$vhost);
-            return $connection;
+        $port = config("queue.connections.rabbitmq.port");
+        $user = config("queue.connections.rabbitmq.user");
+        $password = config("queue.connections.rabbitmq.password");
+        $vhost = config("queue.connections.rabbitmq.password");
+        if (empty($host) || empty($port) || empty($user) || empty($password) || empty($vhost)) {
+            Log::error('rabbitmq set error');
+            return;
+        }
+        $connection = new AMQPStreamConnection($host, $port, $user, $password, $vhost);
+        return $connection;
     }
     }
-    public static function publish(string $channelName, $message){
-       //一对一
-        try{
-            Log::debug('mq start {channel} {message}',['channel'=>$channelName,'message'=>$message]);
+    public static function publish(string $channelName, $message)
+    {
+        //一对一
+        try {
+            Log::debug('mq start {channel} {message}', ['channel' => $channelName, 'message' => $message]);
             $host = config("queue.connections.rabbitmq.host");
             $host = config("queue.connections.rabbitmq.host");
             $port = config("queue.connections.rabbitmq.port");
             $port = config("queue.connections.rabbitmq.port");
             $user = config("queue.connections.rabbitmq.user");
             $user = config("queue.connections.rabbitmq.user");
             $password = config("queue.connections.rabbitmq.password");
             $password = config("queue.connections.rabbitmq.password");
             $vhost = config("queue.connections.rabbitmq.virtual_host");
             $vhost = config("queue.connections.rabbitmq.virtual_host");
-            if(empty($host) || empty($port) || empty($user) || empty($password) || empty($vhost)){
+            if (empty($host) || empty($port) || empty($user) || empty($password) || empty($vhost)) {
                 Log::error('rabbitmq set error');
                 Log::error('rabbitmq set error');
                 return;
                 return;
             }
             }
-            $connection = new AMQPStreamConnection($host,$port,$user,$password,$vhost);
+            $connection = new AMQPStreamConnection($host, $port, $user, $password, $vhost);
             $channel = $connection->channel();
             $channel = $connection->channel();
             $channel->queue_declare($channelName, false, true, false, false);
             $channel->queue_declare($channelName, false, true, false, false);
 
 
-            $msg = new AMQPMessage(json_encode($message,JSON_UNESCAPED_UNICODE));
+            $msg = new AMQPMessage(json_encode($message, JSON_UNESCAPED_UNICODE));
             $channel->basic_publish($msg, '', $channelName);
             $channel->basic_publish($msg, '', $channelName);
 
 
             $channel->close();
             $channel->close();
             $connection->close();
             $connection->close();
-        }catch(\Exception $e){
+        } catch (\Exception $e) {
             Log::error($e);
             Log::error($e);
             return;
             return;
         }
         }
-
     }
     }
 
 
     /**
     /**
@@ -54,7 +58,8 @@ class Mq{
      * @param string $queue
      * @param string $queue
      * @param callable|null $callback
      * @param callable|null $callback
      */
      */
-    public static function worker($exchange,$queue,$callback=null){
+    public static function worker($exchange, $queue, $callback = null)
+    {
 
 
         $consumerTag = 'consumer';
         $consumerTag = 'consumer';
 
 
@@ -64,17 +69,17 @@ class Mq{
         $user = config("queue.connections.rabbitmq.user");
         $user = config("queue.connections.rabbitmq.user");
         $password = config("queue.connections.rabbitmq.password");
         $password = config("queue.connections.rabbitmq.password");
         $vhost = config("queue.connections.rabbitmq.virtual_host");
         $vhost = config("queue.connections.rabbitmq.virtual_host");
-        $connection = new AMQPStreamConnection($host,$port,$user,$password,$vhost);
+        $connection = new AMQPStreamConnection($host, $port, $user, $password, $vhost);
 
 
         $channel = $connection->channel();
         $channel = $connection->channel();
 
 
- /*
+        /*
      The following code is the same both in the consumer and the producer.
      The following code is the same both in the consumer and the producer.
      In this way we are sure we always have a queue to consume from and an
      In this way we are sure we always have a queue to consume from and an
          exchange where to publish messages.
          exchange where to publish messages.
  */
  */
 
 
- /*
+        /*
      name: $queue
      name: $queue
      passive: false
      passive: false
      durable: true // the queue will survive server restarts
      durable: true // the queue will survive server restarts
@@ -96,34 +101,39 @@ class Mq{
 
 
         /**
         /**
          * @param \PhpAmqpLib\Message\AMQPMessage $message
          * @param \PhpAmqpLib\Message\AMQPMessage $message
-        */
-        $process_message = function ($message) use($callback,$connection,$exchange,$queue)
-        {
-            if($callback !== null){
-                try{
+         */
+        $process_message = function ($message) use ($callback, $connection, $exchange, $queue) {
+            if ($callback !== null) {
+                try {
                     $result = $callback(json_decode($message->body));
                     $result = $callback(json_decode($message->body));
-                    if($result !== 0){
+                    if (\App\Tools\Tools::isStop()) {
+                        Log::debug('mq worker: .stop file exist. cancel the consumer.');
+                        $message->getChannel()->basic_cancel($message->getConsumerTag());
+                    }
+                    if ($result !== 0) {
                         throw new \Exception('task error');
                         throw new \Exception('task error');
                     }
                     }
-                }catch(\Exception $e){
+                } catch (\Exception $e) {
                     // push to issues
                     // push to issues
-                    Log::error('mq worker exception',$e);
+                    Log::error('mq worker exception', $e);
                     $channelName = 'issues';
                     $channelName = 'issues';
                     $channelIssues = $connection->channel();
                     $channelIssues = $connection->channel();
                     $channelIssues->queue_declare($channelName, false, true, false, false);
                     $channelIssues->queue_declare($channelName, false, true, false, false);
 
 
-                    $msg = new AMQPMessage(json_encode(['exchange'=>$exchange,
-                                                        'channel'=>$queue,
-                                                        'message'=>json_decode($message->body),
-                                                        'result'=>0,
-                                                        'error'=>$e,
-                                                        ],JSON_UNESCAPED_UNICODE));
+                    $msg = new AMQPMessage(json_encode([
+                        'exchange' => $exchange,
+                        'channel' => $queue,
+                        'message' => json_decode($message->body),
+                        'result' => 0,
+                        'error' => $e,
+                    ], JSON_UNESCAPED_UNICODE));
                     $channelIssues->basic_publish($msg, '', $channelName);
                     $channelIssues->basic_publish($msg, '', $channelName);
                     $channelIssues->close();
                     $channelIssues->close();
                 }
                 }
             }
             }
             $message->ack();
             $message->ack();
 
 
+
             // Send a message with the string "quit" to cancel the consumer.
             // Send a message with the string "quit" to cancel the consumer.
             /*
             /*
             if ($message->body === 'quit') {
             if ($message->body === 'quit') {
@@ -145,10 +155,9 @@ class Mq{
 
 
         /**
         /**
          * @param \PhpAmqpLib\Channel\AMQPChannel $channel
          * @param \PhpAmqpLib\Channel\AMQPChannel $channel
-        * @param \PhpAmqpLib\Connection\AbstractConnection $connection
-        */
-        $shutdown = function ($channel, $connection)
-        {
+         * @param \PhpAmqpLib\Connection\AbstractConnection $connection
+         */
+        $shutdown = function ($channel, $connection) {
             $channel->close();
             $channel->close();
             $connection->close();
             $connection->close();
         };
         };

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

@@ -210,6 +210,20 @@ class TemplateRender{
                     'tpl'=>'term',
                     'tpl'=>'term',
                     ];
                     ];
                 break;
                 break;
+            case 'html':
+                if(isset($props["meaning"])){
+                    $key = 'term-'.$props["word"];
+                    $termHead = "<a href='#'>".$props['meaning']."</a>";
+                    if(isset($GLOBALS[$key])){
+                        $output = $termHead;
+                    }else{
+                        $GLOBALS[$key] = 1;
+                        $output = $termHead.'(<em>'.$props["word"].'</em>)';
+                    }
+                }else{
+                    $output = $props["word"];
+                }
+                break;
             case 'text':
             case 'text':
                 if(isset($props["meaning"])){
                 if(isset($props["meaning"])){
                     $key = 'term-'.$props["word"];
                     $key = 'term-'.$props["word"];
@@ -275,6 +289,33 @@ class TemplateRender{
                     'tpl'=>'note',
                     'tpl'=>'note',
                     ];
                     ];
                 break;
                 break;
+            case 'html':
+                if(isset($GLOBALS['note_sn'])){
+                    $GLOBALS['note_sn']++;
+                }else{
+                    $GLOBALS['note_sn'] = 1;
+                    $GLOBALS['note'] = array();
+                }
+                $GLOBALS['note'][] = [
+                        'sn' => 1,
+                        'trigger' => $trigger,
+                        'content' => MdRender::render($props["note"],
+                                        $this->channel_id,
+                                        null,
+                                        'read',
+                                        'translation',
+                                        'markdown',
+                                        'html'
+                                    ),
+                        ];
+
+                $link="<a href='#footnote-".$GLOBALS['note_sn']."' name='note-".$GLOBALS['note_sn']."'>";
+                if(empty($trigger)){
+                    $output =  $link. "<sup>[" . $GLOBALS['note_sn'] . "]</sup></a>";
+                }else{
+                    $output = $link . $trigger . "</a>";
+                }
+                break;
             case 'text':
             case 'text':
                 $output = $trigger;
                 $output = $trigger;
                 break;
                 break;

+ 9 - 3
app/Http/Controllers/Controller.php

@@ -17,13 +17,19 @@ class Controller extends BaseController
 			'data'=>$result,
 			'data'=>$result,
 			'message'=> $message,
 			'message'=> $message,
 		];
 		];
-		return response()->json($response,200,['Content-Type' => 'application/json;charset=UTF-8',
-	'Charset' => 'utf-8'],JSON_UNESCAPED_UNICODE);
+		return response()->json($response,
+                            200,
+                            [
+                                'Content-Type' => 'application/json;charset=UTF-8',
+	                            'Charset' => 'utf-8'
+                            ],
+                            JSON_UNESCAPED_UNICODE
+                        );
 	}
 	}
     public function ok($result,$message=""){
     public function ok($result,$message=""){
         return $this->sendResponse($result,$message);
         return $this->sendResponse($result,$message);
     }
     }
-	public function sendError($error, $errorMessages = [], $code = 404){
+	public function sendError($error, $errorMessages = '', $code = 404){
 		$response = [
 		$response = [
 			'ok' => false,
 			'ok' => false,
 			'data'=>$errorMessages,
 			'data'=>$errorMessages,

+ 39 - 1
app/Http/Controllers/OfflineIndexController.php

@@ -7,6 +7,8 @@ use Illuminate\Support\Facades\Cache;
 use App\Tools\RedisClusters;
 use App\Tools\RedisClusters;
 use Illuminate\Support\Facades\Redis;
 use Illuminate\Support\Facades\Redis;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\App;
 
 
 class OfflineIndexController extends Controller
 class OfflineIndexController extends Controller
 {
 {
@@ -19,7 +21,43 @@ class OfflineIndexController extends Controller
     {
     {
         //
         //
         if(RedisClusters::has('/offline/index')){
         if(RedisClusters::has('/offline/index')){
-            return RedisClusters::get('/offline/index');
+            $fileInfo = RedisClusters::get('/offline/index');
+            foreach ($fileInfo as $key => $file) {
+                $zipFile = $file['filename'];
+                $bucket = 'attachments-'.config('app.env');
+                $tmpFile =  "{$bucket}\{$zipFile}";
+                $url = array();
+                foreach (config('mint.server.cdn_urls') as $key => $cdn) {
+                    $url[] = [
+                            'link' => $cdn . '/' . $zipFile,
+                            'hostname' =>'cdn-' . $key,
+                        ];
+                }
+                if (App::environment('local')) {
+                    $s3Link = Storage::url($tmpFile);
+                }else{
+                    try{
+                        $s3Link = Storage::temporaryUrl($tmpFile, now()->addDays(2));
+                    }catch(\Exception $e){
+                        Log::error('offline-index {Exception}',['exception'=>$e]);
+                        continue;
+                    }
+                }
+                Log::info('offline-index: link='.$s3Link);
+                $url[] = [
+                    'link'=>$s3Link,
+                    'hostname'=>'Amazon cloud storage(Hongkong)',
+                ];
+                $fileInfo[$key]['url'] = $url;
+            }
+            return response()->json($fileInfo,
+                                    200,
+                                    [
+                                        'Content-Type' => 'application/json;charset=UTF-8',
+                                        'Charset' => 'utf-8'
+                                    ],
+                                    JSON_UNESCAPED_UNICODE
+                                );
         }else{
         }else{
             return [];
             return [];
         }
         }

+ 1 - 1
composer.json

@@ -17,7 +17,7 @@
         "laravel/framework": "^8.75",
         "laravel/framework": "^8.75",
         "laravel/sanctum": "^2.11",
         "laravel/sanctum": "^2.11",
         "laravel/tinker": "^2.5",
         "laravel/tinker": "^2.5",
-	"league/flysystem-aws-s3-v3": "^1.0",
+        "league/flysystem-aws-s3-v3": "^1.0",
         "mustache/mustache": "^2.14",
         "mustache/mustache": "^2.14",
         "php-amqplib/php-amqplib": ">=3.0",
         "php-amqplib/php-amqplib": ">=3.0",
         "php-ffmpeg/php-ffmpeg": "^1.1",
         "php-ffmpeg/php-ffmpeg": "^1.1",

+ 6 - 0
resources/mustache/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>

+ 25 - 0
resources/mustache/html/main.html

@@ -0,0 +1,25 @@
+<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;
+        }
+    </style>
+</head>
+<body>
+<h1>[[book_title]]</h1>
+
+<h2>目录</h2>
+<ul>
+    [[#sections]]
+    <li><a href="#[[filename]]">[[filename]]</a></li>
+    [[/sections]]
+</ul>
+

+ 3 - 0
resources/mustache/html/section.html

@@ -0,0 +1,3 @@
+<h2 id="[[title]]">[[title]]</h2>
+[[content]]
+