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

Merge pull request #1668 from visuddhinanda/laravel

s3 add bucket
visuddhinanda 2 лет назад
Родитель
Сommit
640888dd7b

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

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

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

@@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
 use App\Models\WbwAnalysis;
 use Illuminate\Support\Facades\DB;
+use App\Tools\RedisClusters;
 
 class CacheWbwPreference extends Command
 {
@@ -53,24 +54,24 @@ class CacheWbwPreference extends Command
              * 最新优先
              */
             $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');
             if($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',
                     [$this->option('editor')]);
             }
             $wbw = $wbw->groupBy(['wbw_word','type','editor_id'])->cursor();
+            $bar = $this->output->createProgressBar($wbwCount[0]->count);
             $count = 0;
-            $bar = $this->output->createProgressBar($count[0]->count);
             foreach ($wbw as $key => $value) {
                 $data = WbwAnalysis::where('wbw_word',$value->wbw_word)
                                     ->where('type',$value->type)
                                     ->where('editor_id',$value->editor_id)
                                     ->orderBy('updated_at','desc')
                                     ->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();
                 $count++;
                 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.
-     * 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
      */
-    protected $signature = 'export:chapter {book} {para} {channel} {--debug}';
+    protected $signature = 'export:chapter {book} {para} {channel} {--debug} {--format=tex}';
 
     /**
      * The console command description.
@@ -50,6 +50,17 @@ class ExportChapter extends Command
         if(\App\Tools\Tools::isStop()){
             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');
         $para = $this->argument('para');
         $channelId = $this->argument('channel');
@@ -82,7 +93,7 @@ class ExportChapter extends Command
                                     ->where('channel_id',$channelId)
                                     ->first();
             if($chapter){
-                $filename = "{$sub->paragraph}.tex";
+                $filename = "{$sub->paragraph}.".$this->option('format');
                 $bookMeta['sections'][] = ['filename'=>$filename];
                 $paliTitle = PaliText::where('book',$book)->where('paragraph',$sub->paragraph)->value('toc');
                 $title = $chapter->title?$chapter->title:$paliTitle;
@@ -107,13 +118,24 @@ class ExportChapter extends Command
                                                         'read',
                                                         $channel['type'],
                                                         $sent->content_type,
-                                                        'tex'
+                                                        $renderFormat
                                                         );
                         $sentContent[] = trim($texText);
                     }
                     $paraContent = implode(' ',$sentContent);
                     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{
                         $currLevel = $body->level - $sub->level;
                         if($currLevel>0){
@@ -124,13 +146,26 @@ class ExportChapter extends Command
                             }else{
                                 $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{
                             $content[] = '\par '.$paraContent;
                         }
                     }
-                    $content[] = "\n";
+                    $content[] = "\n\n";
                 }
 
                 $sections[] = [
@@ -148,32 +183,55 @@ class ExportChapter extends Command
                                         'escape'=>function ($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);
-        $tex[] = ['name'=>'main.tex',
+        $tex[] = ['name'=>'main.'.$this->option('format'),
                   'content'=>$texContent
                  ];
         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']);
             $tex[] = ['name'=>$section['name'],
                   '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')){
-            $dir = "export/{$book}-{$para}-{$channelId}/";
+            $dir = "export/".$this->option('format')."/{$book}-{$para}-{$channelId}/";
             foreach ($tex as $key => $section) {
                 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');

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

@@ -16,7 +16,7 @@ class ExportOffline extends Command
      * php artisan export:offline lzma
      * @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.
@@ -69,22 +69,24 @@ class ExportOffline extends Command
         $this->info('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');
         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\Log;
 use App\Tools\RedisClusters;
+use Illuminate\Support\Facades\App;
 
 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[] = [
-            'link'=>$s3Link,
+            'link'=>$link,
             'hostname'=>'Amazon cloud storage(Hongkong)',
         ];
         $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
      * @var string
      */
-    protected $signature = 'test:md.render {item?} {--format}';
+    protected $signature = 'test:md.render {item?} {--format=}';
 
     /**
      * The console command description.
@@ -59,11 +59,8 @@ class TestMdRender extends Command
         md;
 
         $data['term'] = <<<md
-        ## heading
+        ## term
         [[bhagavantu]]
-        ```
-        test
-        ```
         md;
         $data['noteMulti'] = <<<md
         ## heading
@@ -130,11 +127,11 @@ class TestMdRender extends Command
         //$sent = MdRender::take_sentence($html);
         //print_r($sent);
 
-        $formats = $this->option('format');
+        $format = $this->option('format');
         if(empty($format)){
-            $formats = ['react','unity','text','tex'];
+            $formats = ['react','unity','text','tex','html'];
         }else{
-            $formats = [$formats];
+            $formats = [$format];
         }
         foreach ($formats as $format) {
             $this->info("format:{$format}");

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

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

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

@@ -1,52 +1,56 @@
 <?php
+
 namespace App\Http\Api;
+
 use PhpAmqpLib\Connection\AMQPStreamConnection;
 use PhpAmqpLib\Message\AMQPMessage;
 use PhpAmqpLib\Exchange\AMQPExchangeType;
 use Illuminate\Support\Facades\Log;
 
-class Mq{
+class Mq
+{
 
-    private static function connection(){
+    private static function connection()
+    {
         $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");
             $port = config("queue.connections.rabbitmq.port");
             $user = config("queue.connections.rabbitmq.user");
             $password = config("queue.connections.rabbitmq.password");
             $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');
                 return;
             }
-            $connection = new AMQPStreamConnection($host,$port,$user,$password,$vhost);
+            $connection = new AMQPStreamConnection($host, $port, $user, $password, $vhost);
             $channel = $connection->channel();
             $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->close();
             $connection->close();
-        }catch(\Exception $e){
+        } catch (\Exception $e) {
             Log::error($e);
             return;
         }
-
     }
 
     /**
@@ -54,7 +58,8 @@ class Mq{
      * @param string $queue
      * @param callable|null $callback
      */
-    public static function worker($exchange,$queue,$callback=null){
+    public static function worker($exchange, $queue, $callback = null)
+    {
 
         $consumerTag = 'consumer';
 
@@ -64,17 +69,17 @@ class Mq{
         $user = config("queue.connections.rabbitmq.user");
         $password = config("queue.connections.rabbitmq.password");
         $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();
 
- /*
+        /*
      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
          exchange where to publish messages.
  */
 
- /*
+        /*
      name: $queue
      passive: false
      durable: true // the queue will survive server restarts
@@ -96,34 +101,39 @@ class Mq{
 
         /**
          * @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));
-                    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');
                     }
-                }catch(\Exception $e){
+                } catch (\Exception $e) {
                     // push to issues
-                    Log::error('mq worker exception',$e);
+                    Log::error('mq worker exception', $e);
                     $channelName = 'issues';
                     $channelIssues = $connection->channel();
                     $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->close();
                 }
             }
             $message->ack();
 
+
             // Send a message with the string "quit" to cancel the consumer.
             /*
             if ($message->body === 'quit') {
@@ -145,10 +155,9 @@ class Mq{
 
         /**
          * @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();
             $connection->close();
         };

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

@@ -210,6 +210,20 @@ class TemplateRender{
                     'tpl'=>'term',
                     ];
                 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':
                 if(isset($props["meaning"])){
                     $key = 'term-'.$props["word"];
@@ -275,6 +289,33 @@ class TemplateRender{
                     'tpl'=>'note',
                     ];
                 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':
                 $output = $trigger;
                 break;

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

@@ -17,13 +17,19 @@ class Controller extends BaseController
 			'data'=>$result,
 			'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=""){
         return $this->sendResponse($result,$message);
     }
-	public function sendError($error, $errorMessages = [], $code = 404){
+	public function sendError($error, $errorMessages = '', $code = 404){
 		$response = [
 			'ok' => false,
 			'data'=>$errorMessages,

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

@@ -7,6 +7,8 @@ use Illuminate\Support\Facades\Cache;
 use App\Tools\RedisClusters;
 use Illuminate\Support\Facades\Redis;
 use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\App;
 
 class OfflineIndexController extends Controller
 {
@@ -19,7 +21,43 @@ class OfflineIndexController extends Controller
     {
         //
         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{
             return [];
         }

+ 1 - 1
composer.json

@@ -17,7 +17,7 @@
         "laravel/framework": "^8.75",
         "laravel/sanctum": "^2.11",
         "laravel/tinker": "^2.5",
-	"league/flysystem-aws-s3-v3": "^1.0",
+        "league/flysystem-aws-s3-v3": "^1.0",
         "mustache/mustache": "^2.14",
         "php-amqplib/php-amqplib": ">=3.0",
         "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]]
+