Răsfoiți Sursa

Merge pull request #2358 from visuddhinanda/development

Development
visuddhinanda 16 ore în urmă
părinte
comite
dc9ecc3e17
100 a modificat fișierele cu 4732 adăugiri și 2088 ștergeri
  1. 121 26
      api-v12/app/Console/Commands/ExportAiTrainingData.php
  2. 259 0
      api-v12/app/Console/Commands/ExportDiscussion.php
  3. 102 0
      api-v12/app/Console/Commands/ExportGlossary.php
  4. 277 0
      api-v12/app/Console/Commands/ExportZip2.php
  5. 58 0
      api-v12/app/Console/Commands/TestAIArticleTranslate.php
  6. 40 0
      api-v12/app/Console/Commands/TestAITerm.php
  7. 1 0
      api-v12/app/Console/Commands/UpgradeAITranslation.php
  8. 4 0
      api-v12/app/Console/Commands/UpgradeSystemCommentary.php
  9. 2 1
      api-v12/app/Console/Commands/UsersDesensitize.php
  10. 38 0
      api-v12/app/DTO/BaseDTO.php
  11. 21 0
      api-v12/app/DTO/LLMTranslation/TranslationItemDTO.php
  12. 23 0
      api-v12/app/DTO/LLMTranslation/TranslationMetaDTO.php
  13. 19 0
      api-v12/app/DTO/LLMTranslation/TranslationPromptTokenDetailsDTO.php
  14. 41 0
      api-v12/app/DTO/LLMTranslation/TranslationResponseDTO.php
  15. 27 0
      api-v12/app/DTO/LLMTranslation/TranslationUsageDTO.php
  16. 26 0
      api-v12/app/DTO/Search/AggregationDTO.php
  17. 23 0
      api-v12/app/DTO/Search/AggregationsDTO.php
  18. 19 0
      api-v12/app/DTO/Search/BucketDTO.php
  19. 74 0
      api-v12/app/DTO/Search/HitItemDTO.php
  20. 25 0
      api-v12/app/DTO/Search/HitsDTO.php
  21. 21 0
      api-v12/app/DTO/Search/QueryInfoDTO.php
  22. 26 0
      api-v12/app/DTO/Search/SearchDataDTO.php
  23. 21 0
      api-v12/app/DTO/Search/SearchResultDTO.php
  24. 23 0
      api-v12/app/DTO/Search/ShardsDTO.php
  25. 117 0
      api-v12/app/Helpers/MarkdownHelper.php
  26. 67 0
      api-v12/app/Helpers/WikiContentParser.php
  27. 8 0
      api-v12/app/Http/Api/ChannelApi.php
  28. 31 4
      api-v12/app/Http/Api/MdRender.php
  29. 90 70
      api-v12/app/Http/Api/TemplateRender.php
  30. 1 1
      api-v12/app/Http/Controllers/AccessTokenController.php
  31. 5 5
      api-v12/app/Http/Controllers/AiAssistantController.php
  32. 17 17
      api-v12/app/Http/Controllers/AiModelController.php
  33. 2 2
      api-v12/app/Http/Controllers/AiTranslateController.php
  34. 6 6
      api-v12/app/Http/Controllers/ApiController.php
  35. 319 299
      api-v12/app/Http/Controllers/ArticleController.php
  36. 42 37
      api-v12/app/Http/Controllers/ArticleFtsController.php
  37. 76 74
      api-v12/app/Http/Controllers/ArticleMapController.php
  38. 3 3
      api-v12/app/Http/Controllers/ArticleNavController.php
  39. 26 26
      api-v12/app/Http/Controllers/ArticleProgressController.php
  40. 66 65
      api-v12/app/Http/Controllers/AttachmentController.php
  41. 4 4
      api-v12/app/Http/Controllers/AuthController.php
  42. 1 1
      api-v12/app/Http/Controllers/BlogController.php
  43. 8 6
      api-v12/app/Http/Controllers/CategoryController.php
  44. 61 55
      api-v12/app/Http/Controllers/ChannelController.php
  45. 18 10
      api-v12/app/Http/Controllers/ChannelIOController.php
  46. 286 0
      api-v12/app/Http/Controllers/ChapterContentController.php
  47. 20 13
      api-v12/app/Http/Controllers/ChapterController.php
  48. 21 12
      api-v12/app/Http/Controllers/ChapterIOController.php
  49. 15 13
      api-v12/app/Http/Controllers/ChapterIndexController.php
  50. 1 1
      api-v12/app/Http/Controllers/ChatController.php
  51. 2 2
      api-v12/app/Http/Controllers/ChatMessageController.php
  52. 106 192
      api-v12/app/Http/Controllers/CollectionController.php
  53. 5 5
      api-v12/app/Http/Controllers/CommandController.php
  54. 4 4
      api-v12/app/Http/Controllers/CompoundController.php
  55. 256 31
      api-v12/app/Http/Controllers/CorpusController.php
  56. 119 96
      api-v12/app/Http/Controllers/CourseController.php
  57. 120 112
      api-v12/app/Http/Controllers/CourseMemberController.php
  58. 47 51
      api-v12/app/Http/Controllers/DhammaTermController.php
  59. 3 4
      api-v12/app/Http/Controllers/DictController.php
  60. 11 9
      api-v12/app/Http/Controllers/DictInfoController.php
  61. 2 2
      api-v12/app/Http/Controllers/DictMeaningController.php
  62. 6 6
      api-v12/app/Http/Controllers/DictPreferenceController.php
  63. 23 24
      api-v12/app/Http/Controllers/DictVocabularyController.php
  64. 54 54
      api-v12/app/Http/Controllers/DiscussionController.php
  65. 66 67
      api-v12/app/Http/Controllers/DiscussionCountController.php
  66. 17 0
      api-v12/app/Http/Controllers/DownloadController.php
  67. 5 5
      api-v12/app/Http/Controllers/EmailCertificationController.php
  68. 15 15
      api-v12/app/Http/Controllers/ExportController.php
  69. 40 41
      api-v12/app/Http/Controllers/ExportWbwController.php
  70. 9 9
      api-v12/app/Http/Controllers/ForgotPasswordController.php
  71. 18 18
      api-v12/app/Http/Controllers/GroupController.php
  72. 6 6
      api-v12/app/Http/Controllers/GroupMemberController.php
  73. 17 17
      api-v12/app/Http/Controllers/InteractiveController.php
  74. 17 17
      api-v12/app/Http/Controllers/InviteController.php
  75. 213 0
      api-v12/app/Http/Controllers/Library/AnthologyController.php
  76. 212 0
      api-v12/app/Http/Controllers/Library/AnthologyReadController.php
  77. 19 8
      api-v12/app/Http/Controllers/Library/BookController.php
  78. 82 0
      api-v12/app/Http/Controllers/Library/SearchController.php
  79. 62 58
      api-v12/app/Http/Controllers/LikeController.php
  80. 15 15
      api-v12/app/Http/Controllers/ModelLogController.php
  81. 4 4
      api-v12/app/Http/Controllers/NissayaCardController.php
  82. 25 25
      api-v12/app/Http/Controllers/NissayaEndingController.php
  83. 12 12
      api-v12/app/Http/Controllers/NotificationController.php
  84. 5 47
      api-v12/app/Http/Controllers/OfflineIndexController.php
  85. 1 1
      api-v12/app/Http/Controllers/PaliBookCategoryController.php
  86. 17 17
      api-v12/app/Http/Controllers/PaliTextController.php
  87. 121 0
      api-v12/app/Http/Controllers/ParagraphContentController.php
  88. 5 5
      api-v12/app/Http/Controllers/PgPaliDictDownloadController.php
  89. 33 33
      api-v12/app/Http/Controllers/ProgressChapterController.php
  90. 28 28
      api-v12/app/Http/Controllers/ProjectController.php
  91. 6 6
      api-v12/app/Http/Controllers/ProjectTreeController.php
  92. 16 15
      api-v12/app/Http/Controllers/RecentController.php
  93. 14 13
      api-v12/app/Http/Controllers/RelatedParagraphController.php
  94. 23 23
      api-v12/app/Http/Controllers/RelationController.php
  95. 15 16
      api-v12/app/Http/Controllers/ResetPasswordController.php
  96. 117 114
      api-v12/app/Http/Controllers/SearchController.php
  97. 53 49
      api-v12/app/Http/Controllers/SearchPaliDataController.php
  98. 36 35
      api-v12/app/Http/Controllers/SearchPaliWbwController.php
  99. 9 9
      api-v12/app/Http/Controllers/SearchTitleController.php
  100. 19 17
      api-v12/app/Http/Controllers/SentHistoryController.php

+ 121 - 26
api-v12/app/Console/Commands/ExportAiTrainingData.php

@@ -6,17 +6,20 @@ use Illuminate\Console\Command;
 use Illuminate\Support\Facades\Log;
 use App\Models\Sentence;
 use App\Models\PaliSentence;
-use Illuminate\Support\Str;
 use App\Http\Api\MdRender;
+use Illuminate\Support\Facades\File;
+use App\Http\Api\ChannelApi;
+use App\Services\PaliTextService;
 
 class ExportAiTrainingData extends Command
 {
+    private $ShortTrans = 0.17;
     /**
      * The name and signature of the console command.
-     *
+     * php artisan export:ai.training.data
      * @var string
      */
-    protected $signature = 'export:ai.training.data {--format=gz  : zip file format 7z,lzma,gz }';
+    protected $signature = 'export:ai.training.data {--format=gz  : zip file format 7z,lzma,gz } {--test}';
 
     /**
      * The console command description.
@@ -42,9 +45,10 @@ class ExportAiTrainingData extends Command
      */
     public function handle()
     {
-        Log::debug('task export offline sentence-table start');
+        Log::info('task export offline sentence-table start');
         //创建文件夹
-        $exportDir = storage_path('app/tmp/export/offline');
+        $base = 'app/tmp/export/offline';
+        $exportDir = storage_path($base);
         if (!is_dir($exportDir)) {
             $res = mkdir($exportDir, 0755, true);
             if (!$res) {
@@ -54,20 +58,56 @@ class ExportAiTrainingData extends Command
                 $this->info('make dir successful ' . $exportDir);
             }
         }
-        $filename = 'wikipali-offline-ai-training-' . date("Y-m-d") . '.tsv';
-        $exportFile = storage_path('app/tmp/export/offline/' . $filename);
-        $fp = fopen($exportFile, 'w');
-        if ($fp === false) {
-            die('无法创建文件');
+
+        //创建临时文件夹\
+        $dirname = $exportDir . '/' . 'wikipali-offline-ai-training-' . date("YmdHis");
+
+        $tmp = mkdir($dirname, 0755, true);
+        if (!$tmp) {
+            $this->error('mkdir fail path=' . $dirname);
+            return 1;
+        } else {
+            $this->info('make dir successful ' . $dirname);
+        }
+
+        $fpIndex = fopen($dirname . '/index.md', 'w');
+        if ($fpIndex === false) {
+            die('无法创建索引文件');
         }
 
         $channels = [
-            '19f53a65-81db-4b7d-8144-ac33f1217d34',
+            '7ac4d13b-a43d-4409-91b5-5f2a82b916b3',
+            'e5bc5c97-a6fb-4ccb-b7df-be6dcfee9c43',
+            '74ebf4c5-c243-4948-955d-6c277e29276a',
+            '3b0cb0aa-ea88-4ce5-b67d-00a3e76220cc',
+            '5310999c-0b0c-4bb0-9bb9-9cdd176e9ef0',
+            '331447b6-39bb-4b49-ac10-6206db93a050',
         ];
+
         $start = time();
         foreach ($channels as $key => $channel) {
+            if ($this->option('test') && $key > 0) {
+                // test mode 只跑一个
+                break;
+            }
+            fwrite($fpIndex, "# {$channel}\n");
+            $channelInfo = ChannelApi::getById($channel);
+            if ($channelInfo) {
+                fwrite($fpIndex, "- 版本名称:{$channelInfo['name']}\n");
+                fwrite($fpIndex, "- 语言:{$channelInfo['lang']}\n");
+            }
+            // 创建文件
+            $this->info('export start' . $channel);
+            $filename = $channel . '.jsonl';
+            $exportFile = $dirname . '/' . $filename;
+            $fp = fopen($exportFile, 'w');
+            if ($fp === false) {
+                die('无法创建文件');
+            }
+
             $db = Sentence::where('channel_uid', $channel);
             $bar = $this->output->createProgressBar($db->count());
+
             $srcDb = $db->select([
                 'book_id',
                 'paragraph',
@@ -75,9 +115,30 @@ class ExportAiTrainingData extends Command
                 'word_end',
                 'content',
                 'content_type'
-            ])->cursor();
+            ])
+                ->whereNotNull('content')
+                ->orderBy('book_id')
+                ->orderBy('paragraph')
+                ->orderBy('word_start')->cursor();
+            $done = [];
             foreach ($srcDb as $sent) {
-                $content = MdRender::render(
+                $id = "{$sent->book_id}-{$sent->paragraph}-{$sent->word_start}-{$sent->word_end}";
+                if (isset($done[$id])) {
+                    continue;
+                }
+                //获取原文
+                $origin = PaliSentence::where('book', $sent->book_id)
+                    ->where('paragraph', $sent->paragraph)
+                    ->where('word_begin', $sent->word_start)
+                    ->where('word_end', $sent->word_end)
+                    ->value('text');
+                //忽略空的原文
+                if (self::isEmpty($origin)) {
+                    Log::warning('origin is empty id=' . $id);
+                    continue;
+                }
+                // 渲染译文
+                $translation = MdRender::render(
                     $sent->content,
                     [$channel],
                     null,
@@ -86,29 +147,63 @@ class ExportAiTrainingData extends Command
                     $sent->content_type,
                     'text',
                 );
-                $origin = PaliSentence::where('book', $sent->book_id)
-                    ->where('paragraph', $sent->paragraph)
-                    ->where('word_begin', $sent->word_start)
-                    ->where('word_end', $sent->word_end)
-                    ->value('text');
-                $currData = array(
-                    str_replace("\n", "", $origin),
-                    str_replace("\n", "", $content),
-                );
+                $translation = trim($translation);
+                // 忽略空的译文
+                if (self::isEmpty($translation)) {
+                    Log::warning('translation is empty id=' . $id);
+                    continue;
+                }
 
-                fwrite($fp, implode("\t", $currData) . "\n");
+                //忽略过短的译文
+                if (mb_strlen($translation) / mb_strlen($origin) < $this->ShortTrans) {
+                    Log::warning('translation is short id=' . $id);
+                    continue;
+                }
+                //原文与翻译完全相同
+                if ($translation === $origin) {
+                    Log::warning('translation is same id=' . $id);
+                    continue;
+                }
+                // 获取分类标签
+                $paliTextService = app(PaliTextService::class);
+                $tags = $paliTextService->getParaCategoryTags($sent->book_id, $sent->paragraph);
+                $path = $paliTextService->getParaPathTitle($sent->book_id, $sent->paragraph);
+                $currData = [
+                    'id' => $id,
+                    'original' => $origin,
+                    'translation' => $translation,
+                    'category' => $tags,
+                    'path' => $path,
+                ];
 
+                fwrite($fp, json_encode($currData, JSON_UNESCAPED_UNICODE) . "\n");
                 $bar->advance();
+                $done[$id] = 1;
             }
+            fclose($fp);
         }
-        fclose($fp);
+        fclose($fpIndex);
+
         $this->info((time() - $start) . ' seconds');
-        $this->call('export:zip', [
+        $this->call('export:zip2', [
             'id' => 'ai-translating-training-data',
-            'filename' => $exportFile,
+            'filename' => $dirname,
             'title' => 'wikipali ai translating training data',
             'format' => $this->option('format'),
         ]);
+
+        sleep(5);
+        File::deleteDirectory($dirname);
+
         return 0;
     }
+
+    private function isEmpty(?string $input): bool
+    {
+        if (empty($input)) {
+            return true;
+        }
+        $result = preg_replace('/[\s\d\p{P}]/u', '', $input);
+        return empty($result);
+    }
 }

+ 259 - 0
api-v12/app/Console/Commands/ExportDiscussion.php

@@ -0,0 +1,259 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use App\Http\Api\ChannelApi;
+use Carbon\Carbon;
+
+class ExportDiscussion extends Command
+{
+    /**
+     * The name and signature of the console command.
+     * php artisan export:discussion
+     */
+    protected $signature = 'export:discussion {editor : The editor UID to export discussions for}';
+
+    /**
+     * The console command description.
+     */
+    protected $description = 'Export discussions made by a specific editor to a Markdown file';
+
+    /** @var string 巴利原文 channel_uid */
+    private string $orgChannelId;
+
+    /** @var resource 输出文件句柄(流式写入,避免大字符串堆积在内存) */
+    private $fileHandle;
+
+    /** @var string 输出文件路径 */
+    private string $outputPath;
+
+    /**
+     * Execute the console command.
+     */
+    public function handle(): int
+    {
+        $editorUid = $this->argument('editor');
+
+        $this->info("Fetching discussions for editor: {$editorUid}");
+
+        // 1. 获取巴利原文 channel_uid
+        $this->orgChannelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
+        if (!$this->orgChannelId) {
+            $this->error('Failed to retrieve Pali source channel ID.');
+            return self::FAILURE;
+        }
+        $this->info("Pali channel ID: {$this->orgChannelId}");
+
+        // 2. 统计总数(用于进度条)
+        $total = DB::table('discussions')
+            ->where('editor_uid', $editorUid)
+            ->where('status', 'active')
+            ->count();
+
+        if ($total === 0) {
+            $this->warn("No discussions found for editor: {$editorUid}");
+            return self::SUCCESS;
+        }
+
+        $this->info("Found {$total} discussion(s). Processing...");
+
+        // 3. 打开文件句柄(流式写入,不在内存中拼接整个 Markdown)
+        $filename = "discussion_export_{$editorUid}_" . now()->format('YmdHis') . '.md';
+        $this->outputPath = storage_path("app/tmp/{$filename}");
+        $this->fileHandle = fopen($this->outputPath, 'w');
+        if (!$this->fileHandle) {
+            $this->error("Cannot open file for writing: {$this->outputPath}");
+            return self::FAILURE;
+        }
+
+        // 写文件头
+        $this->writeLine("# 讨论导出报告\n");
+        $this->writeLine("- **Editor UID**: {$editorUid}");
+        $this->writeLine("- **导出时间**: " . now()->toDateTimeString());
+        $this->writeLine("\n---\n");
+
+        // 4. 分批处理(每批 50 条),避免内存溢出
+        $progressBar = $this->output->createProgressBar($total);
+        $progressBar->start();
+
+        DB::table('discussions')
+            ->where('editor_uid', $editorUid)
+            ->where('status', 'active')
+            ->orderBy('created_at', 'asc')
+            ->select(['id', 'res_id', 'res_type', 'content', 'created_at'])
+            ->chunk(50, function ($discussions) use ($progressBar) {
+                $this->processChunk($discussions);
+                $progressBar->advance($discussions->count());
+
+                // 每批处理完后主动释放内存
+                gc_collect_cycles();
+            });
+
+        $progressBar->finish();
+        $this->newLine();
+
+        fclose($this->fileHandle);
+
+        $this->info("\n✅ 导出完成!文件已保存到: {$this->outputPath}");
+
+        return self::SUCCESS;
+    }
+
+    /**
+     * 处理一批 discussions。
+     */
+    private function processChunk(\Illuminate\Support\Collection $discussions): void
+    {
+        // --- 批量查译文 sentences ---
+        $resIds = $discussions->pluck('res_id')->unique()->values()->all();
+
+        $translationMap = DB::table('sentences')
+            ->whereIn('uid', $resIds)
+            ->select([
+                'uid',
+                'book_id',
+                'paragraph',
+                'word_start',
+                'word_end',
+                'content',
+                'channel_uid',
+            ])
+            ->get()
+            ->keyBy('uid');
+
+        // --- 批量查 sent_histories(分小批,避免超大 IN) ---
+        $historiesMap = [];
+        foreach (array_chunk($resIds, 100) as $batch) {
+            DB::table('sent_histories')
+                ->whereIn('sent_uid', $batch)
+                ->orderBy('create_time', 'asc')
+                ->select(['sent_uid', 'content', 'create_time'])
+                ->each(function ($row) use (&$historiesMap) {
+                    $historiesMap[$row->sent_uid][] = $row;
+                });
+        }
+
+        // --- 收集本批所有唯一坐标,批量查巴利原文 ---
+        $coordKeys = [];
+        foreach ($translationMap as $t) {
+            $key = "{$t->book_id}_{$t->paragraph}_{$t->word_start}_{$t->word_end}";
+            $coordKeys[$key] = $t;
+        }
+        $paliMap = $this->fetchPaliSentences($coordKeys);
+
+        // --- 写 Markdown ---
+        foreach ($discussions as $discussion) {
+            $sentUid     = $discussion->res_id;
+            $translation = $translationMap->get($sentUid);
+            if (!$translation) {
+                continue;
+            }
+
+            $coordKey    = "{$translation->book_id}_{$translation->paragraph}_{$translation->word_start}_{$translation->word_end}";
+            $pali        = $paliMap[$coordKey] ?? null;
+            $paliContent = $pali ? trim($pali->content ?? '(无原文)') : '(未找到巴利原文)';
+
+            $discussionCreatedAt = $discussion->created_at
+                ? Carbon::parse($discussion->created_at)
+                : null;
+
+            $histories      = $historiesMap[$sentUid] ?? [];
+            $matchedHistory = $this->findClosestHistory($histories, $discussionCreatedAt);
+            $translationAtTime = $matchedHistory
+                ? trim($matchedHistory->content)
+                : trim($translation->content ?? '(无译文内容)');
+
+            $this->writeLine("# {$paliContent}\n");
+            $this->writeLine("  - **历史译文**: {$translationAtTime}");
+            $this->writeLine("  - **评论**: " . trim($discussion->title ?? '') . trim($discussion->content ?? ''));
+            $this->writeLine("  - **当前译文**: {$translation->content}");
+            $this->writeLine('');
+        }
+
+        // 显式释放本批数据
+        unset($translationMap, $historiesMap, $coordKeys, $paliMap);
+    }
+
+    /**
+     * 批量查询巴利原文,每组最多 30 个坐标,避免超大 SQL。
+     *
+     * @param array<string, object> $coordKeys  key="{book_id}_{paragraph}_{word_start}_{word_end}"
+     * @return array<string, object>
+     */
+    private function fetchPaliSentences(array $coordKeys): array
+    {
+        $paliMap = [];
+
+        foreach (array_chunk(array_values($coordKeys), 30) as $group) {
+            $results = DB::table('sentences')
+                ->where('channel_uid', $this->orgChannelId)
+                ->where(function ($q) use ($group) {
+                    foreach ($group as $t) {
+                        $q->orWhere(function ($sub) use ($t) {
+                            $sub->where('book_id',    $t->book_id)
+                                ->where('paragraph',  $t->paragraph)
+                                ->where('word_start', $t->word_start)
+                                ->where('word_end',   $t->word_end);
+                        });
+                    }
+                })
+                ->select(['book_id', 'paragraph', 'word_start', 'word_end', 'content'])
+                ->get();
+
+            foreach ($results as $ps) {
+                $key = "{$ps->book_id}_{$ps->paragraph}_{$ps->word_start}_{$ps->word_end}";
+                $paliMap[$key] = $ps;
+            }
+
+            unset($results);
+        }
+
+        return $paliMap;
+    }
+
+    /**
+     * 流式写入一行到文件。
+     */
+    private function writeLine(string $line): void
+    {
+        fwrite($this->fileHandle, $line . "\n");
+    }
+
+    /**
+     * 在历史记录中找评论发布时间之前最近的那条。
+     * 若全部在评论之后,则退而取最早一条。
+     *
+     * @param array       $histories           sent_histories(已按 create_time ASC 排序)
+     * @param Carbon|null $discussionCreatedAt 评论发布时间
+     */
+    private function findClosestHistory(array $histories, ?Carbon $discussionCreatedAt): ?object
+    {
+        if (empty($histories)) {
+            return null;
+        }
+
+        if (!$discussionCreatedAt) {
+            return end($histories) ?: null;
+        }
+
+        $discussionTimestamp = $discussionCreatedAt->timestamp;
+        $best     = null;
+        $bestDiff = PHP_INT_MAX;
+
+        foreach ($histories as $h) {
+            $historyTime = (int) $h->create_time;
+            if ($historyTime <= $discussionTimestamp) {
+                $diff = $discussionTimestamp - $historyTime;
+                if ($diff < $bestDiff) {
+                    $bestDiff = $diff;
+                    $best     = $h;
+                }
+            }
+        }
+
+        // 所有历史都在评论之后 → 取最早一条
+        return $best ?? $histories[0];
+    }
+}

+ 102 - 0
api-v12/app/Console/Commands/ExportGlossary.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\File;
+use App\Services\TermService;
+
+
+class ExportGlossary extends Command
+{
+    /**
+     * The name and signature of the console command.
+     * php artisan export:export-glossary zh-Hans
+     * @var string
+     */
+    protected $signature = 'export:export-glossary {lang}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '导出术语表';
+
+    protected TermService $termService;
+
+    public function __construct(TermService $termService)
+    {
+        $this->termService = $termService;
+        parent::__construct();
+    }
+    /**
+     * Execute the console command.
+     */
+    public function handle()
+    {
+        Log::info('task export offline sentence-table start');
+        $lang = $this->argument('lang');
+        //创建文件夹
+        $base = 'app/tmp/export/offline';
+        $exportDir = storage_path($base);
+        if (!is_dir($exportDir)) {
+            $res = mkdir($exportDir, 0755, true);
+            if (!$res) {
+                $this->error('mkdir fail path=' . $exportDir);
+                return 1;
+            } else {
+                $this->info('make dir successful ' . $exportDir);
+            }
+        }
+
+        //创建临时文件夹\
+        $dirname = $exportDir . '/' . 'wikipali_glossary_' . date("YmdHis");
+
+        $tmp = mkdir($dirname, 0755, true);
+        if (!$tmp) {
+            $this->error('mkdir fail path=' . $dirname);
+            return 1;
+        } else {
+            $this->info('make dir successful ' . $dirname);
+        }
+
+        $fpIndex = fopen($dirname . '/index.md', 'w');
+        if ($fpIndex === false) {
+            die('无法创建索引文件');
+        }
+
+        // 创建json文件
+        $this->info('export start' . $lang);
+        $filename = 'glossary_' . $lang . '.jsonl';
+        $exportFile = $dirname . '/' . $filename;
+        $fp = fopen($exportFile, 'w');
+        if ($fp === false) {
+            die('无法创建文件');
+        }
+        $start = time();
+
+        //**业务逻辑 */
+
+        $data = $this->termService->getCommunityGlossary($lang);
+        foreach ($data['items'] as $key => $value) {
+            fwrite($fp, json_encode($value, JSON_UNESCAPED_UNICODE) . "\n");
+        }
+
+        fclose($fpIndex);
+
+        $this->info((time() - $start) . ' seconds');
+        $this->call('export:zip2', [
+            'id' => 'wikipali_glossary',
+            'filename' => $dirname,
+            'title' => 'wikipali glossary of community',
+            'format' => 'jsonl',
+        ]);
+
+        sleep(5);
+        File::deleteDirectory($dirname);
+
+        return 0;
+    }
+}

+ 277 - 0
api-v12/app/Console/Commands/ExportZip2.php

@@ -0,0 +1,277 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\App;
+
+use Symfony\Component\Process\Process;
+
+class ExportZip2 extends Command
+{
+    protected $signature = 'export:zip2
+        {filename : filename}
+        {title : title}
+        {id : 标识符}
+        {format? : zip file format 7z,lzma,gz }';
+
+    protected $description = '压缩导出的文件';
+
+    public function handle()
+    {
+        Log::debug('export offline: 开始压缩');
+
+        $defaultExportPath = storage_path('app/public/export/offline');
+        $exportFile = $this->argument('filename');
+
+        $filename = basename($exportFile);
+
+        if ($filename === $exportFile) {
+            $exportFullFileName = $defaultExportPath . '/' . $filename;
+            $exportPath = $defaultExportPath;
+        } else {
+            $exportFullFileName = $exportFile;
+            $exportPath = dirname($exportFile);
+        }
+
+        $format = $this->argument('format') ?? 'gz';
+
+        if (!file_exists($exportFullFileName)) {
+
+            Log::error('export offline: file not exists', [
+                'file' => $exportFullFileName
+            ]);
+
+            $this->error('file not exists: ' . $exportFullFileName);
+
+            return 1;
+        }
+
+        $zipFile = $this->getZipFileName($filename, $format);
+        $zipFullFileName = $exportPath . '/' . $zipFile;
+
+        if (file_exists($zipFullFileName)) {
+            unlink($zipFullFileName);
+        }
+
+        $this->info("start compress: {$exportFullFileName}");
+        Log::debug('export offline zip start', [
+            'file' => $exportFullFileName,
+            'format' => $format
+        ]);
+
+        $this->compress($exportFullFileName, $zipFullFileName, $format);
+
+        $this->info('压缩完成');
+
+        Log::debug('zip done', [
+            'zip' => $zipFullFileName
+        ]);
+
+        /*
+        |--------------------------------------------------------------------------
+        | 上传 S3
+        |--------------------------------------------------------------------------
+        */
+
+        $bucket = config('mint.attachments.bucket_name.temporary');
+        $tmpFile = $bucket . '/' . $zipFile;
+
+        $this->info('upload file=' . $tmpFile);
+
+        Log::debug('export offline upload', [
+            'file' => $tmpFile
+        ]);
+
+        Storage::put($tmpFile, fopen($zipFullFileName, 'r'));
+
+        $this->info('upload done');
+
+        Log::debug('upload done');
+
+        /*
+        |--------------------------------------------------------------------------
+        | 生成下载链接
+        |--------------------------------------------------------------------------
+        */
+
+        if (App::environment('local')) {
+            $link = Storage::url($tmpFile);
+        } else {
+            try {
+                $link = Storage::temporaryUrl(
+                    $tmpFile,
+                    now()->addDays(2)
+                );
+            } catch (\Exception $e) {
+                Log::error('temporaryUrl fail', [
+                    'exception' => $e
+                ]);
+                $this->error('generate temporaryUrl fail');
+                return 1;
+            }
+        }
+
+        $this->info('link=' . $link);
+
+        /*
+        |--------------------------------------------------------------------------
+        | CDN 列表
+        |--------------------------------------------------------------------------
+        */
+
+        $url = [];
+        foreach (config('mint.server.cdn_urls') as $key => $cdn) {
+            $url[] = [
+                'link' => $cdn . '/' . $zipFile,
+                'hostname' => 'china cdn-' . $key
+            ];
+        }
+
+        $url[] = [
+            'link' => $link,
+            'hostname' => 'Amazon cloud storage(Hongkong)'
+        ];
+
+        /*
+        |--------------------------------------------------------------------------
+        | Cache 写入
+        |--------------------------------------------------------------------------
+        */
+
+        $info = Cache::get('/offline/index', []);
+        if (!is_array($info)) {
+            $info = [];
+        }
+        $id = $this->argument('id');
+        // 先移除已有相同 id 的记录
+        $info = array_values(array_filter($info, function ($item) use ($id) {
+            return !isset($item['id']) || $item['id'] != $id;
+        }));
+        // 再追加新数据
+        $info[] = [
+            'id' => $id,
+            'title' => $this->argument('title'),
+            'filename' => $zipFile,
+            'url' => $url,
+            'create_at' => now()->toDateTimeString(),
+            'chapter' => Cache::get("/export/chapter/count"),
+            'filesize' => filesize($zipFullFileName),
+            'min_app_ver' => '1.3',
+        ];
+
+        Cache::put('/offline/index', $info);
+
+        /*
+        |--------------------------------------------------------------------------
+        | 删除原始文件
+        |--------------------------------------------------------------------------
+        */
+
+        sleep(5);
+        try {
+            if (is_file($exportFullFileName)) {
+                unlink($exportFullFileName);
+            }
+            if (file_exists($zipFullFileName)) {
+                unlink($zipFullFileName);
+            }
+        } catch (\Throwable $e) {
+            Log::error('delete source fail', [
+                'exception' => $e
+            ]);
+        }
+
+        return 0;
+    }
+
+    /*
+    |--------------------------------------------------------------------------
+    | 生成压缩文件名
+    |--------------------------------------------------------------------------
+    */
+
+    protected function getZipFileName(string $filename, string $format): string
+    {
+        return match ($format) {
+            '7z' => $filename . '.7z',
+            'lzma' => $filename . '.lzma',
+            default => $filename . '.tar.gz'
+        };
+    }
+
+    /*
+    |--------------------------------------------------------------------------
+    | 压缩函数
+    |--------------------------------------------------------------------------
+    */
+
+    protected function compress($source, $target, $format)
+    {
+        $isDir = is_dir($source);
+        switch ($format) {
+            case '7z':
+                $command = [
+                    '7z',
+                    'a',
+                    '-t7z',
+                    '-mx=9',
+                    $target,
+                    $source
+                ];
+                break;
+
+            case 'lzma':
+                if ($isDir) {
+                    $tmpTar = $source . '.tar';
+                    $tar = new Process([
+                        'tar',
+                        '-cf',
+                        $tmpTar,
+                        '-C',
+                        dirname($source),
+                        basename($source)
+                    ]);
+                    $tar->run();
+                    $source = $tmpTar;
+                }
+                $command = [
+                    'xz',
+                    '-k',
+                    '-9',
+                    '--format=lzma',
+                    $source
+                ];
+                break;
+
+            default:
+                $command = [
+                    'tar',
+                    '-czf',
+                    $target,
+                    '-C',
+                    dirname($source),
+                    basename($source)
+                ];
+        }
+
+        $this->info(implode(' ', $command));
+        $process = new Process($command);
+        $process->setTimeout(60 * 60 * 6);
+        $process->run();
+
+        $this->info($process->getOutput());
+
+        if (!$process->isSuccessful()) {
+
+            Log::error('compress fail', [
+                'error' => $process->getErrorOutput()
+            ]);
+
+            throw new \RuntimeException($process->getErrorOutput());
+        }
+    }
+}

+ 58 - 0
api-v12/app/Console/Commands/TestAIArticleTranslate.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Services\AIAssistant\ArticleTranslateService;
+
+class TestAIArticleTranslate extends Command
+{
+    /**
+     * The name and signature of the console command.
+     * php artisan test:ai.article.translate
+     * @var string
+     */
+    protected $signature = 'test:ai.article.translate {--article=} {--anthology=} {--model=}  {--channel=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Execute the console command.
+     */
+    public function handle()
+    {
+        if (
+            !$this->option('model') ||
+            !$this->option('channel')
+        ) {
+            $this->error('model,article,channel is requested');
+            return;
+        }
+        //
+        // ===== 创建 Service =====
+        $service = app(ArticleTranslateService::class);
+        // ===== 执行 =====
+        if ($this->option('article')) {
+            $this->info('article translate start');
+            $total = $service->setModel($this->option('model'))
+                ->setChannel($this->option('channel'))
+                ->translateArticle($this->option('article'))
+                ->save();
+            $this->info("{$total} sentences saved");
+        }
+        if ($this->option('anthology')) {
+            $this->info('anthology translate start');
+            $total = $service->setModel($this->option('model'))
+                ->setChannel($this->option('channel'))
+                ->translateAnthology($this->option('anthology'), function ($article, $sentences) {
+                    $this->info("translate article {$article} sentences {$sentences}");
+                });
+            $this->info("{$total} article saved");
+        }
+    }
+}

+ 40 - 0
api-v12/app/Console/Commands/TestAITerm.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Services\AITermService;
+
+class TestAITerm extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'test:ai.term';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Execute the console command.
+     */
+    public function handle()
+    {
+        //
+        // ===== 创建 Service =====
+        $service = app(AITermService::class);
+        $service->setModel('dd81ce6c-e9ff-46b2-b1af-947728ba996e');
+
+        // ===== 执行 =====
+        $result = $service->create('f3ba16e5-862d-49c4-b5b0-39ab8b8ca4f4');
+
+        // ===== 调试输出(建议保留)=====
+        dump($result);
+    }
+}

+ 1 - 0
api-v12/app/Console/Commands/UpgradeAITranslation.php

@@ -10,6 +10,7 @@ use App\Services\AIModelService;
 use App\Services\SentenceService;
 use App\Services\SearchPaliDataService;
 use App\Services\AIAssistant\NissayaTranslateService;
+
 use App\Http\Resources\AiModelResource;
 use App\Http\Controllers\AuthController;
 

+ 4 - 0
api-v12/app/Console/Commands/UpgradeSystemCommentary.php

@@ -297,6 +297,10 @@ md;
     {
         $output  = [];
         foreach ($input as $key => $value) {
+            if (!isset($original[$key])) {
+                Log::warning('no id');
+                continue;
+            }
             $value['id'] = $original[$key]['id'];
             if (isset($value['commentary'])) {
                 $newCommentary = array_map(function ($n) use ($commentary) {

+ 2 - 1
api-v12/app/Console/Commands/UsersDesensitize.php

@@ -61,7 +61,8 @@ class UsersDesensitize extends Command
 
             if (
                 mb_substr($user->username, 0, 4) === 'test' ||
-                $user->username === 'admin'
+                $user->username === 'admin' ||
+                $user->username === 'visuddhinanda'
             ) {
                 $this->info('test user jump' . $user->username);
                 $jumped++;

+ 38 - 0
api-v12/app/DTO/BaseDTO.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\DTO;
+
+abstract readonly class BaseDTO implements \JsonSerializable
+{
+    public function toArray(): array
+    {
+        $result = [];
+
+        foreach (get_object_vars($this) as $key => $value) {
+            $result[$key] = $this->normalizeValue($value);
+        }
+
+        return $result;
+    }
+
+    protected function normalizeValue(mixed $value): mixed
+    {
+        if ($value instanceof self) {
+            return $value->toArray();
+        }
+
+        if (is_array($value)) {
+            return array_map(
+                fn($item) => $this->normalizeValue($item),
+                $value
+            );
+        }
+
+        return $value;
+    }
+
+    public function jsonSerialize(): array
+    {
+        return $this->toArray();
+    }
+}

+ 21 - 0
api-v12/app/DTO/LLMTranslation/TranslationItemDTO.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\DTO\LLMTranslation;
+
+use App\DTO\BaseDTO;
+
+readonly class TranslationItemDTO extends BaseDTO
+{
+    public function __construct(
+        public string $id,
+        public string $content,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            id: $data['id'],
+            content: $data['content'],
+        );
+    }
+}

+ 23 - 0
api-v12/app/DTO/LLMTranslation/TranslationMetaDTO.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\DTO\LLMTranslation;
+
+use App\DTO\BaseDTO;
+
+readonly class TranslationMetaDTO  extends BaseDTO
+{
+    public function __construct(
+        public int $duration,
+        public int $itemsCount,
+        public TranslationUsageDTO $usage,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            duration: $data['duration'],
+            itemsCount: $data['items_count'],
+            usage: TranslationUsageDTO::fromArray($data['usage']),
+        );
+    }
+}

+ 19 - 0
api-v12/app/DTO/LLMTranslation/TranslationPromptTokenDetailsDTO.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\DTO\LLMTranslation;
+
+use App\DTO\BaseDTO;
+
+readonly class TranslationPromptTokenDetailsDTO  extends BaseDTO
+{
+    public function __construct(
+        public int $cachedTokens,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            cachedTokens: $data['cached_tokens'],
+        );
+    }
+}

+ 41 - 0
api-v12/app/DTO/LLMTranslation/TranslationResponseDTO.php

@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * 使用方式
+ * use App\DTO\LLMTranslation\TranslationResponseDTO;
+
+$dto = TranslationResponseDTO::fromArray($response);
+
+dd($dto->data[0]->content);
+ */
+
+namespace App\DTO\LLMTranslation;
+
+use App\DTO\BaseDTO;
+
+readonly class TranslationResponseDTO extends BaseDTO
+{
+    /**
+     * @param TranslationItemDTO[] $data
+     */
+    public function __construct(
+        public bool $success,
+        public string $error,
+        public array $data,
+        public TranslationMetaDTO $meta,
+    ) {}
+
+    public static function fromArray(array $payload): self
+    {
+        return new self(
+            success: $payload['success'],
+            error: '',
+            data: array_map(
+                fn(array $item) => TranslationItemDTO::fromArray($item),
+                $payload['data']
+            ),
+
+            meta: TranslationMetaDTO::fromArray($payload['meta']),
+        );
+    }
+}

+ 27 - 0
api-v12/app/DTO/LLMTranslation/TranslationUsageDTO.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\DTO\LLMTranslation;
+
+use App\DTO\BaseDTO;
+
+readonly class TranslationUsageDTO extends BaseDTO
+{
+    public function __construct(
+        public int $promptTokens,
+        public int $completionTokens,
+        public int $totalTokens,
+        public TranslationPromptTokenDetailsDTO $promptTokensDetails,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            promptTokens: $data['prompt_tokens'],
+            completionTokens: $data['completion_tokens'],
+            totalTokens: $data['total_tokens'],
+            promptTokensDetails: TranslationPromptTokenDetailsDTO::fromArray(
+                $data['prompt_tokens_details']
+            ),
+        );
+    }
+}

+ 26 - 0
api-v12/app/DTO/Search/AggregationDTO.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\DTO\Search;
+
+/**
+ * 通用聚合结果DTO
+ * 用于处理所有结构相同的聚合桶数据
+ */
+class AggregationDTO
+{
+    public function __construct(
+        public int $doc_count_error_upper_bound,
+        public int $sum_other_doc_count,
+        /** @var BucketDTO[] */
+        public array $buckets,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            doc_count_error_upper_bound: $data['doc_count_error_upper_bound'],
+            sum_other_doc_count: $data['sum_other_doc_count'],
+            buckets: array_map(fn($bucket) => BucketDTO::fromArray($bucket), $data['buckets']),
+        );
+    }
+}

+ 23 - 0
api-v12/app/DTO/Search/AggregationsDTO.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\DTO\Search;
+
+class AggregationsDTO
+{
+    public function __construct(
+        public AggregationDTO $granularity,
+        public AggregationDTO $resource_type,
+        public AggregationDTO $language,
+        public AggregationDTO $category,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            granularity: AggregationDTO::fromArray($data['granularity']),
+            resource_type: AggregationDTO::fromArray($data['resource_type']),
+            language: AggregationDTO::fromArray($data['language']),
+            category: AggregationDTO::fromArray($data['category']),
+        );
+    }
+}

+ 19 - 0
api-v12/app/DTO/Search/BucketDTO.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\DTO\Search;
+
+class BucketDTO
+{
+    public function __construct(
+        public string $key,
+        public int $doc_count,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            key: $data['key'],
+            doc_count: $data['doc_count'],
+        );
+    }
+}

+ 74 - 0
api-v12/app/DTO/Search/HitItemDTO.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\DTO\Search;
+
+class HitItemDTO
+{
+    public function __construct(
+        public string $id,
+        public float $score,
+        public string $content,
+        public string $title,
+        public string $path,
+        public array $category,
+        public string $highlight,
+
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        $source = $data['_source'];
+        $highlight = $data['highlight'];
+        $highlightArray = [];
+        if (is_array($highlight)) {
+            foreach ($highlight as $key => $value) {
+                $highlightArray = array_merge($highlightArray, $value);
+            }
+        }
+
+        $content = $source['content'];
+        $contentArray = [];
+        if (is_array($content)) {
+            foreach ($content as $key => $value) {
+                $contentArray[] = $value;
+            }
+        }
+        $category = [];
+        if (is_array($source['category'])) {
+            $category = $source['category'];
+        } else {
+            $category = [$source['category']];
+        }
+
+        return new self(
+            id: $source['id'],
+            score: $data['_score'],
+            title: $source['title']['pali'] ?? '',
+            content: implode('', $contentArray),
+            path: $source['path'] ?? '',
+            category: $category,
+            highlight: implode('', $highlightArray),
+        );
+    }
+
+
+
+    /**
+     * 提取 para 引用ID(核心逻辑🔥)
+     */
+    public function getParaId(): ?string
+    {
+        if (preg_match('/pali_para_(\d+)_(\d+)/', $this->id, $matches)) {
+            return "{$matches[1]}-{$matches[2]}";
+        }
+        return null;
+    }
+
+    public function getParaLink(): ?string
+    {
+        $id = $this->getParaId();
+        if (!$id) return null;
+
+        return "{{para|id={$id}|title={$id}|style=reference}}";
+    }
+}

+ 25 - 0
api-v12/app/DTO/Search/HitsDTO.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\DTO\Search;
+
+class HitsDTO
+{
+    /**
+     * @param HitItemDTO[] $items
+     */
+    public function __construct(
+        public int $total,
+        public array $items,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            total: $data['total']['value'],
+            items: array_map(
+                fn($item) => HitItemDTO::fromArray($item),
+                $data['hits']
+            )
+        );
+    }
+}

+ 21 - 0
api-v12/app/DTO/Search/QueryInfoDTO.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\DTO\Search;
+
+class QueryInfoDTO
+{
+    public function __construct(
+        public string $original_query,
+        public string $search_mode,
+        public string $request_method,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            original_query: $data['original_query'],
+            search_mode: $data['search_mode'],
+            request_method: $data['request_method'],
+        );
+    }
+}

+ 26 - 0
api-v12/app/DTO/Search/SearchDataDTO.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\DTO\Search;
+
+class SearchDataDTO
+{
+    public function __construct(
+        public int $took,
+        public bool $timed_out,
+        public ShardsDTO $shards,
+        public HitsDTO $hits,
+        public AggregationsDTO $aggregations,
+
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            took: $data['took'],
+            timed_out: $data['timed_out'],
+            shards: ShardsDTO::fromArray($data['_shards']),
+            hits: HitsDTO::fromArray($data['hits']),
+            aggregations: AggregationsDTO::fromArray($data['aggregations']),
+        );
+    }
+}

+ 21 - 0
api-v12/app/DTO/Search/SearchResultDTO.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\DTO\Search;
+
+class SearchResultDTO
+{
+    public function __construct(
+        public bool $success,
+        public SearchDataDTO $data,
+        public QueryInfoDTO $query_info,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            success: $data['success'],
+            data: SearchDataDTO::fromArray($data['data']),
+            query_info: QueryInfoDTO::fromArray($data['query_info']),
+        );
+    }
+}

+ 23 - 0
api-v12/app/DTO/Search/ShardsDTO.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\DTO\Search;
+
+class ShardsDTO
+{
+    public function __construct(
+        public int $total,
+        public int $successful,
+        public int $skipped,
+        public int $failed,
+    ) {}
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            total: $data['total'],
+            successful: $data['successful'],
+            skipped: $data['skipped'],
+            failed: $data['failed'],
+        );
+    }
+}

+ 117 - 0
api-v12/app/Helpers/MarkdownHelper.php

@@ -0,0 +1,117 @@
+<?php
+
+namespace App\Helpers;
+
+use Illuminate\Support\Str;
+
+class MarkdownHelper
+{
+    /**
+     * 将 Markdown 字符串按段落、标题、表格、列表、代码块等规则拆分成数组
+     *
+     * @param string $markdown
+     * @return array
+     */
+    public static function splitByParagraphs(string $markdown): array
+    {
+        // 保护代码块内容,防止内部换行被拆分
+        $codeBlocks = [];
+        $markdown = preg_replace_callback('/```(.*?)```/s', function ($matches) use (&$codeBlocks) {
+            $placeholder = '%%CODE_BLOCK_' . count($codeBlocks) . '%%';
+            $codeBlocks[$placeholder] = $matches[0];
+            return $placeholder;
+        }, $markdown);
+
+        // 按两个及以上换行符拆分
+        $parts = preg_split('/\n\s*\n/', $markdown);
+
+        $result = [];
+
+        foreach ($parts as $part) {
+            $part = trim($part);
+            if ($part === '') {
+                continue;
+            }
+
+            // 恢复代码块
+            foreach ($codeBlocks as $placeholder => $codeBlock) {
+                if (strpos($part, $placeholder) !== false) {
+                    $part = str_replace($placeholder, $codeBlock, $part);
+                }
+            }
+
+            // 进一步按标题、表格、列表拆分(这些通常不会被两个换行分隔)
+            $subParts = self::splitBySpecialBlocks($part);
+            foreach ($subParts as $subPart) {
+                $subPart = trim($subPart);
+                if ($subPart !== '') {
+                    $result[] = $subPart;
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * 按标题、表格、列表等特殊块进一步拆分
+     *
+     * @param string $text
+     * @return array
+     */
+    protected static function splitBySpecialBlocks(string $text): array
+    {
+        // 保护代码块(防止被误拆)
+        $codeBlocks = [];
+        $text = preg_replace_callback('/```(.*?)```/s', function ($matches) use (&$codeBlocks) {
+            $placeholder = '%%CODE_BLOCK_' . count($codeBlocks) . '%%';
+            $codeBlocks[$placeholder] = $matches[0];
+            return $placeholder;
+        }, $text);
+
+        // 按标题 (#, ##, 等)
+        $lines = preg_split('/(^#{1,6}\s+.*$)/m', $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+        $result = [];
+
+        foreach ($lines as $line) {
+            $line = trim($line);
+            if ($line === '') {
+                continue;
+            }
+
+            // 如果包含表格(简单判断:包含 | 且多个行)
+            if (strpos($line, '|') !== false && substr_count($line, "\n") >= 1) {
+                $rows = explode("\n", $line);
+                $table = [];
+                foreach ($rows as $row) {
+                    if (trim($row) !== '') {
+                        $table[] = $row;
+                    }
+                }
+                if (!empty($table)) {
+                    $result[] = implode("\n", $table);
+                }
+                continue;
+            }
+
+            // 如果包含列表(无序列表 - 或 *,有序列表 1. 2.)
+            if (preg_match('/^(\s*[-*+]\s+|\s*\d+\.\s+)/m', $line)) {
+                // 保留整个列表块(连续列表行)
+                $result[] = $line;
+                continue;
+            }
+
+            // 恢复代码块
+            foreach ($codeBlocks as $placeholder => $codeBlock) {
+                if (strpos($line, $placeholder) !== false) {
+                    $line = str_replace($placeholder, $codeBlock, $line);
+                }
+            }
+
+            $result[] = $line;
+        }
+
+        return $result;
+    }
+}

+ 67 - 0
api-v12/app/Helpers/WikiContentParser.php

@@ -0,0 +1,67 @@
+<?php
+// app/Support/WikiContentParser.php
+
+namespace App\Helpers;
+
+class WikiContentParser
+{
+    /**
+     * 给 HTML 中的 h1~h3 注入 id,并提取目录结构
+     * 返回 ['content' => string, 'toc' => array]
+     */
+    public static function parse(string $html): array
+    {
+        $toc = [];
+        $slugCount = [];
+
+        $content = preg_replace_callback(
+            '/<(h[123])([^>]*)>(.*?)<\/\1>/si',
+            function ($matches) use (&$toc, &$slugCount) {
+                [$full, $tag, $attrs, $inner] = $matches;
+
+                if (preg_match('/\bid=["\']([^"\']+)["\']/', $attrs, $m)) {
+                    $id = $m[1];
+                } else {
+                    $text = strip_tags($inner);
+                    $id   = self::slugify($text);
+
+                    if (isset($slugCount[$id])) {
+                        $slugCount[$id]++;
+                        $id .= '-' . $slugCount[$id];
+                    } else {
+                        $slugCount[$id] = 0;
+                    }
+
+                    $attrs .= ' id="' . $id . '"';
+                }
+
+                $toc[] = [
+                    'id'    => $id,
+                    'text'  => strip_tags($inner),
+                    'level' => (int) substr($tag, 1),
+                ];
+
+                return "<{$tag}{$attrs}>{$inner}</{$tag}>";
+            },
+            $html
+        );
+
+        // 归一化层级:找最小 level,所有条目 level = level - minLevel + 1
+        if (!empty($toc)) {
+            $minLevel = min(array_column($toc, 'level'));
+            foreach ($toc as &$item) {
+                $item['level'] = $item['level'] - $minLevel + 1;
+            }
+            unset($item);
+        }
+
+        return ['content' => $content, 'toc' => $toc];
+    }
+
+    private static function slugify(string $text): string
+    {
+        // 保留中文、字母、数字,其余转连字符
+        $slug = preg_replace('/[^\p{L}\p{N}]+/u', '-', trim($text));
+        return strtolower(trim($slug, '-')) ?: 'section';
+    }
+}

+ 8 - 0
api-v12/app/Http/Api/ChannelApi.php

@@ -27,6 +27,14 @@ class ChannelApi
             return false;
         }
     }
+    public static function getByIds(array $ids)
+    {
+        $channels = [];
+        foreach ($ids as  $id) {
+            $channels[] = self::getById($id);
+        }
+        return $channels;
+    }
     public static function getCanReadByUser($userUuid = null)
     {
         #获取 user 在某章节 所有有权限的 channel 列表

+ 31 - 4
api-v12/app/Http/Api/MdRender.php

@@ -293,10 +293,11 @@ class MdRender
              * 生成模版参数
              *
              */
-            //TODO 判断$channelId里面的是否都是uuid
             $channelInfo = [];
             foreach ($channelId as $key => $id) {
-                $channelInfo[] = Channel::where('uid', $id)->first();
+                if (Str::isUuid($id)) {
+                    $channelInfo[] = Channel::where('uid', $id)->first();
+                }
             }
 
             $tplRender = new TemplateRender(
@@ -346,7 +347,11 @@ class MdRender
                 /**html tex text simple markdown */
                 if (isset($tplProps)) {
                     if (is_array($tplProps)) {
-                        return $tplProps[0];
+                        if (isset($tplProps[0])) {
+                            return $tplProps[0];
+                        } else {
+                            return '';
+                        }
                     } else {
                         return $tplProps;
                     }
@@ -640,8 +645,10 @@ class MdRender
                     $output .= '</div>';
                     unset($GLOBALS['note']);
                 }
+
                 //处理图片链接
                 $output = str_replace('<img src="', '<img src="' . config('app.url'), $output);
+                $output = $this->replaceSinglePWithSpan($output);
                 break;
             case 'markdown':
                 //处理脚注
@@ -677,7 +684,27 @@ class MdRender
         );
 
         $output  = $mdRender->convert($markdown, $channelId, $queryId);
-
         return $output;
     }
+
+    /**
+     * 如果字符串中只有一对 p 标签,则替换为 span
+     *
+     * @param string $html
+     * @return string
+     */
+    public static function replaceSinglePWithSpan(string $html): string
+    {
+        preg_match_all('/<p\b[^>]*>.*?<\/p>/is', $html, $matches);
+
+        if (count($matches[0]) === 1) {
+            return preg_replace(
+                ['/^\s*<p\b([^>]*)>/i', '/<\/p>\s*$/i'],
+                ['<span$1>', '</span>'],
+                $html
+            );
+        }
+
+        return $html;
+    }
 }

+ 90 - 70
api-v12/app/Http/Api/TemplateRender.php

@@ -22,6 +22,8 @@ use App\Http\Api\PaliTextApi;
 
 use App\Tools\Tools;
 
+use App\Services\ArticleService;
+
 class TemplateRender
 {
     protected $param = [];
@@ -158,17 +160,78 @@ class TemplateRender
             case 'ai':
                 $result = $this->render_ai();
                 break;
+            case 'para':
+                $result = $this->render_para();
+                break;
             default:
-                # code...
-                $result = [
-                    'props' => base64_encode(\json_encode([])),
-                    'html' => '',
+                if (mb_substr($tpl_name, 0, 4, "UTF-8") === 'Tpl:') {
+                    $result = $this->render_tpl($tpl_name);
+                } else {
+                    $result = [
+                        'props' => base64_encode(\json_encode([])),
+                        'html' => '',
+                        'tag' => 'span',
+                        'tpl' => 'unknown',
+                    ];
+                }
+
+                break;
+        }
+        return $result;
+    }
+
+    public function render_tpl($name)
+    {
+        $article = app(ArticleService::class)->getRawByTitle($name);
+        $content = $article->content;
+        if (count($this->param) > 0) {
+            $m = new \Mustache_Engine(array(
+                'entity_flags' => ENT_QUOTES,
+                'escape' => function ($value) {
+                    return $value;
+                }
+            ));
+            $content = $m->render($content, $this->param);
+        }
+        $output = [];
+        switch ($this->format) {
+            case 'react':
+                $output = [
+                    'props' => base64_encode(\json_encode(['content' => $content])),
+                    'html' => $content,
                     'tag' => 'span',
-                    'tpl' => 'unknown',
+                    'tpl' => 'tpl',
                 ];
                 break;
+            default:
+                $output = $content;
+                break;
         }
-        return $result;
+        return $output;
+    }
+
+    public function render_para()
+    {
+        $props = [];
+        $props['id'] = $this->get_param($this->param, "id", 1);
+        $props['title'] = $this->get_param($this->param, "title", 2);
+        $props['style'] = $this->get_param($this->param, "style", 3);
+
+        $output = [];
+        switch ($this->format) {
+            case 'react':
+                $output = [
+                    'props' => base64_encode(\json_encode($props)),
+                    'html' => $props['title'],
+                    'tag' => 'span',
+                    'tpl' => 'para',
+                ];
+                break;
+            default:
+                $output = $props['title'];
+                break;
+        }
+        return $output;
     }
 
     public function getTermProps($word, $tag = null, $channel = null)
@@ -333,54 +396,15 @@ class TemplateRender
                 ];
                 break;
             case 'html':
-                if (isset($props["meaning"])) {
-                    $GLOBALS[$this->glossaryKey][$props["word"]] = $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"];
-                    if (isset($GLOBALS[$key])) {
-                        $output = $props["meaning"];
-                    } else {
-                        $GLOBALS[$key] = 1;
-                        $output = $props["meaning"] . '(' . $props["word"] . ')';
-                    }
-                } else {
-                    $output = $props["word"];
-                }
-                break;
-            case 'tex':
-                if (isset($props["meaning"])) {
-                    $key = 'term-' . $props["word"];
-                    if (isset($GLOBALS[$key])) {
-                        $output = $props["meaning"];
-                    } else {
-                        $GLOBALS[$key] = 1;
-                        $output = $props["meaning"] . '(' . $props["word"] . ')';
-                    }
-                } else {
-                    $output = $props["word"];
-                }
-                break;
-            case 'simple':
-                if (isset($props["meaning"])) {
-                    $output = $props["meaning"];
-                } else {
-                    $output = $props["word"];
-                }
+                $no = isset($props['id']) ? '' : 'term_invalid';
+                $id = isset($props['id']) ? $props['id'] : '';
+                $output = "<span ";
+                $output .= "class='term-ref {$no}' ";
+                $output .= "data-id='{$id}' ";
+                $output .= "data-term='{$props['word']}' ";
+                $output .= ">";
+                $output .= $props['meaning'] ?? $props['word'];
+                $output .= "</span>";
                 break;
             case 'markdown':
                 if (isset($props["meaning"])) {
@@ -413,11 +437,7 @@ class TemplateRender
                 }
                 break;
             default:
-                if (isset($props["meaning"])) {
-                    $output = $props["meaning"];
-                } else {
-                    $output = $props["word"];
-                }
+                $output = $props['meaning'] ?? $props['word'];
                 break;
         }
         return $output;
@@ -933,7 +953,7 @@ class TemplateRender
 
         $sid = $this->get_param($this->param, "id", 1);
         $channel = $this->get_param($this->param, "channel", 2);
-        $text = $this->get_param($this->param, "text", 2, 'both');
+        $show = $this->get_param($this->param, "text", 2, 'both');
 
         if (!empty($channel)) {
             $channels = explode(',', $channel);
@@ -961,7 +981,7 @@ class TemplateRender
         } else {
             $tpl = "sentedit";
         }
-
+        $props['show'] = $show;
         //输出引用
         $arrSid = explode('-', $sid);
         $bookPara = array_slice($arrSid, 0, 2);
@@ -1000,14 +1020,14 @@ class TemplateRender
                 break;
             case 'prompt':
                 $output = '';
-                if ($text === 'both' || $text === 'origin') {
+                if ($show === 'both' || $show === 'origin') {
                     if (isset($props['origin']) && is_array($props['origin'])) {
                         foreach ($props['origin'] as $key => $value) {
                             $output .= $value['html'];
                         }
                     }
                 }
-                if ($text === 'both' || $text === 'translation') {
+                if ($show === 'both' || $show === 'translation') {
                     if (isset($props['translation']) && is_array($props['translation'])) {
                         foreach ($props['translation'] as $key => $value) {
                             $output .= $value['html'];
@@ -1018,14 +1038,14 @@ class TemplateRender
             case 'html':
                 $output = '';
                 $output .= '<span class="sentence">';
-                if ($text === 'both' || $text === 'origin') {
+                if ($show === 'both' || $show === 'origin') {
                     if (isset($props['origin']) && is_array($props['origin'])) {
                         foreach ($props['origin'] as $key => $value) {
                             $output .= '<span class="origin">' . $value['html'] . '</span>';
                         }
                     }
                 }
-                if ($text === 'both' || $text === 'translation') {
+                if ($show === 'both' || $show === 'translation') {
                     if (isset($props['translation']) && is_array($props['translation'])) {
                         foreach ($props['translation'] as $key => $value) {
                             $output .= '<span class="translation">' . $value['html'] . '</span>';
@@ -1045,7 +1065,7 @@ class TemplateRender
                 break;
             case 'simple':
                 $output = '';
-                if ($text === 'both' || $text === 'origin') {
+                if ($show === 'both' || $show === 'origin') {
                     if (empty($output)) {
                         if (
                             isset($props['origin']) &&
@@ -1058,7 +1078,7 @@ class TemplateRender
                         }
                     }
                 }
-                if ($text === 'both' || $text === 'translation') {
+                if ($show === 'both' || $show === 'translation') {
                     if (
                         isset($props['translation']) &&
                         is_array($props['translation']) &&
@@ -1072,7 +1092,7 @@ class TemplateRender
                 break;
             case 'markdown':
                 $output = '';
-                if ($text === 'both' || $text === 'origin') {
+                if ($show === 'both' || $show === 'origin') {
                     if (
                         $this->options['origin'] === true ||
                         $this->options['origin'] === 'true'
@@ -1084,7 +1104,7 @@ class TemplateRender
                         }
                     }
                 }
-                if ($text === 'both' || $text === 'translation') {
+                if ($show === 'both' || $show === 'translation') {
                     if (
                         $this->options['translation']  === true ||
                         $this->options['translation']  === 'true'
@@ -1098,7 +1118,7 @@ class TemplateRender
                                 $output .= trim($value['html']);
                             }
                         } else {
-                            if ($text === 'translation') {
+                            if ($show === 'translation') {
                                 //无译文用原文代替
                                 if (isset($props['origin']) && is_array($props['origin'])) {
                                     foreach ($props['origin'] as $key => $value) {

+ 1 - 1
api-v12/app/Http/Controllers/AccessTokenController.php

@@ -38,7 +38,7 @@ class AccessTokenController extends Controller
             Log::error('未登录');
             return $this->error(__('auth.failed'), [], 401);
         }
-        $payload = $request->get('payload');
+        $payload = $request->input('payload');
         $result = array();
         foreach ($payload as $key => $value) {
             //鉴权

+ 5 - 5
api-v12/app/Http/Controllers/AiAssistantController.php

@@ -34,17 +34,17 @@ class AiAssistantController extends Controller
             ->orWhere('privacy', 'public')
             ->orWhereIn('uid', $resId);
         if ($request->has('keyword')) {
-            $table = $table->where('name', 'like', '%' . $request->get('keyword') . '%');
+            $table = $table->where('name', 'like', '%' . $request->input('keyword') . '%');
         }
         $count = $table->count();
 
         $table = $table->orderBy(
-            $request->get('order', 'created_at'),
-            $request->get('dir', 'asc')
+            $request->input('order', 'created_at'),
+            $request->input('dir', 'asc')
         );
 
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 1000));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
 

+ 17 - 17
api-v12/app/Http/Controllers/AiModelController.php

@@ -28,16 +28,16 @@ class AiModelController extends Controller
             Log::error('notification auth failed {request}', ['request' => $request]);
             return $this->error(__('auth.failed'), 401, 401);
         }
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'all':
                 $table = AiModel::whereNotNull('owner_id');
                 break;
             case 'studio':
-                $studioId = StudioApi::getIdByName($request->get('name'));
+                $studioId = StudioApi::getIdByName($request->input('name'));
                 $table = AiModel::where('owner_id', $studioId);
                 break;
             case 'usable':
-                $table = AiModel::where('owner_id', $request->get('user_id'))
+                $table = AiModel::where('owner_id', $request->input('user_id'))
                     ->orWhere('privacy', 'public');
                 break;
             case 'chat':
@@ -45,17 +45,17 @@ class AiModelController extends Controller
                 break;
         }
         if ($request->has('keyword')) {
-            $table = $table->where('name', 'like', '%' . $request->get('keyword') . '%');
+            $table = $table->where('name', 'like', '%' . $request->input('keyword') . '%');
         }
         $count = $table->count();
 
         $table = $table->orderBy(
-            $request->get('order', 'created_at'),
-            $request->get('dir', 'asc')
+            $request->input('order', 'created_at'),
+            $request->input('dir', 'asc')
         );
 
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 1000));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
 
@@ -80,13 +80,13 @@ class AiModelController extends Controller
         if (!$user) {
             return $this->error(__('auth.failed'), 401, 401);
         }
-        $studioId = StudioApi::getIdByName($request->get('studio_name'));
+        $studioId = StudioApi::getIdByName($request->input('studio_name'));
         Log::debug('store', ['studioId' => $studioId, 'user' => $user]);
         if (!self::canEdit($user['user_uid'], $studioId)) {
             return $this->error(__('auth.failed'), 403, 403);
         }
         $new = new AiModel();
-        $new->name = $request->get('name');
+        $new->name = $request->input('name');
         $new->uid = Str::uuid();
         $new->real_name = Str::uuid();
         $new->owner_id = $studioId;
@@ -124,13 +124,13 @@ class AiModelController extends Controller
         if (!self::canEdit($user['user_uid'], $aiModel->owner_id)) {
             return $this->error(__('auth.failed'), 403, 403);
         }
-        $aiModel->name = $request->get('name');
-        $aiModel->description = $request->get('description');
-        $aiModel->system_prompt = $request->get('system_prompt');
-        $aiModel->url = $request->get('url');
-        $aiModel->model = $request->get('model');
-        $aiModel->key = $request->get('key');
-        $aiModel->privacy = $request->get('privacy');
+        $aiModel->name = $request->input('name');
+        $aiModel->description = $request->input('description');
+        $aiModel->system_prompt = $request->input('system_prompt');
+        $aiModel->url = $request->input('url');
+        $aiModel->model = $request->input('model');
+        $aiModel->key = $request->input('key');
+        $aiModel->privacy = $request->input('privacy');
         $aiModel->editor_id = $user['user_uid'];
         $aiModel->save();
         return $this->ok(new AiModelResource($aiModel));

+ 2 - 2
api-v12/app/Http/Controllers/AiTranslateController.php

@@ -25,7 +25,7 @@ class AiTranslateController extends Controller
     public function store(Request $request)
     {
         //
-        return $this->fetch(strip_tags($request->get('origin')));
+        return $this->fetch(strip_tags($request->input('origin')));
     }
 
     private function fetch($origin, $engin = 'kimi', $prompt_pre = '', $prompt_suf = '请翻译上述巴利文。')
@@ -73,7 +73,7 @@ class AiTranslateController extends Controller
                 ->where('paragraph', $para[1])
                 ->value('text');
             if (!empty($content)) {
-                return $this->fetch($content, $request->get('engin', config('mint.ai.default')));
+                return $this->fetch($content, $request->input('engin', config('mint.ai.default')));
             } else {
                 return $this->error('no content', 200, 200);
             }

+ 6 - 6
api-v12/app/Http/Controllers/ApiController.php

@@ -45,12 +45,12 @@ class ApiController extends Controller
         $begin = $currTime - $times - 1;
         $value = 0;
         for ($i = $begin; $i <= $currTime; $i++) {
-            $keyApi = $key . $request->get('api', 'all') . "/" . $i;
+            $keyApi = $key . $request->input('api', 'all') . "/" . $i;
             if (!empty(Redis::get($keyApi . '/delay'))) {
-                if ($request->get('item') === 'average') {
+                if ($request->input('item') === 'average') {
                     $value += intval(Redis::get($keyApi . '/delay') / Redis::get($keyApi . '/count'));
                 } else {
-                    $value += (int)Redis::get($keyApi . '/' . $request->get('item'));
+                    $value += (int)Redis::get($keyApi . '/' . $request->input('item'));
                 }
             }
         }
@@ -74,12 +74,12 @@ class ApiController extends Controller
         $output = [];
         for ($i = $begin; $i <= $currMinute; $i++) {
             $value = 0;
-            $keyApi = $key . $request->get('api', 'all') . "/" . $i;
+            $keyApi = $key . $request->input('api', 'all') . "/" . $i;
             if (!empty(Redis::get($keyApi . '/delay'))) {
-                if ($request->get('item') === 'average') {
+                if ($request->input('item') === 'average') {
                     $value += intval(Redis::get($keyApi . '/delay') / Redis::get($keyApi . '/count'));
                 } else {
-                    $value += (int)Redis::get($keyApi . '/' . $request->get('item'));
+                    $value += (int)Redis::get($keyApi . '/' . $request->input('item'));
                 }
             } else {
                 $value = 0;

+ 319 - 299
api-v12/app/Http/Controllers/ArticleController.php

@@ -24,108 +24,112 @@ use App\Tools\OpsLog;
 
 class ArticleController extends Controller
 {
-    public static function userCanRead($user_uid,Article $article){
-        if($article->status === 30 ){
+    public static function userCanRead($user_uid, Article $article)
+    {
+        if ($article->status === 30) {
             return true;
         }
-        if(empty($user_uid)){
+        if (empty($user_uid)) {
             return false;
         }
-            //私有文章,判断是否为所有者
-        if($user_uid === $article->owner){
+        //私有文章,判断是否为所有者
+        if ($user_uid === $article->owner) {
             return true;
         }
         //非所有者
         //判断是否为文章协作者
-        $power = ShareApi::getResPower($user_uid,$article->uid);
-        if($power >= 10 ){
+        $power = ShareApi::getResPower($user_uid, $article->uid);
+        if ($power >= 10) {
             return true;
         }
         //无读取权限
         //判断文集是否有读取权限
-        $inCollection = ArticleCollection::where('article_id',$article->uid)
-                                        ->select('collect_id')
-                                        ->groupBy('collect_id')->get();
-        if(!$inCollection){
+        $inCollection = ArticleCollection::where('article_id', $article->uid)
+            ->select('collect_id')
+            ->groupBy('collect_id')->get();
+        if (!$inCollection) {
             return false;
         }
         //查找与文章同主人的文集
-        $collections = Collection::whereIn('uid',$inCollection)
-                                    ->where('owner',$article->owner)
-                                    ->select('uid')
-                                    ->get();
-        if(!$collections){
+        $collections = Collection::whereIn('uid', $inCollection)
+            ->where('owner', $article->owner)
+            ->select('uid')
+            ->get();
+        if (!$collections) {
             return false;
         }
         //查找与文章同主人的文集是否是共享的
         $power = 0;
         foreach ($collections as $collection) {
             # code...
-            $currPower = ShareApi::getResPower($user_uid,$collection->uid);
-            if($currPower >= 10){
+            $currPower = ShareApi::getResPower($user_uid, $collection->uid);
+            if ($currPower >= 10) {
                 return true;
             }
         }
         return false;
     }
-    public static function userCanEditId($user_uid,$articleId){
+    public static function userCanEditId($user_uid, $articleId)
+    {
         $article = Article::find($articleId);
-        if($article){
-            return ArticleController::userCanEdit($user_uid,$article);
-        }else{
+        if ($article) {
+            return ArticleController::userCanEdit($user_uid, $article);
+        } else {
             return false;
         }
     }
-    public static function userCanEdit($user_uid,$article){
-        if(empty($user_uid)){
+    public static function userCanEdit($user_uid, $article)
+    {
+        if (empty($user_uid)) {
             return false;
         }
         //私有文章,判断是否为所有者
-        if($user_uid === $article->owner){
+        if ($user_uid === $article->owner) {
             return true;
         }
         //非所有者
         //判断是否为文章协作者
-        $power = ShareApi::getResPower($user_uid,$article->uid);
-        if($power >= 20 ){
+        $power = ShareApi::getResPower($user_uid, $article->uid);
+        if ($power >= 20) {
             return true;
         }
         //无读取权限
         //判断文集是否有读取权限
-        $inCollection = ArticleCollection::where('article_id',$article->uid)
-                                        ->select('collect_id')
-                                        ->groupBy('collect_id')->get();
-        if(!$inCollection){
+        $inCollection = ArticleCollection::where('article_id', $article->uid)
+            ->select('collect_id')
+            ->groupBy('collect_id')->get();
+        if (!$inCollection) {
             return false;
         }
         //查找与文章同主人的文集
-        $collections = Collection::whereIn('uid',$inCollection)
-                                    ->where('owner',$article->owner)
-                                    ->select('uid')
-                                    ->get();
-        if(!$collections){
+        $collections = Collection::whereIn('uid', $inCollection)
+            ->where('owner', $article->owner)
+            ->select('uid')
+            ->get();
+        if (!$collections) {
             return false;
         }
         //查找与文章同主人的文集是否是共享的
         $power = 0;
         foreach ($collections as $collection) {
             # code...
-            $currPower = ShareApi::getResPower($user_uid,$collection->uid);
-            if($currPower >= 20){
+            $currPower = ShareApi::getResPower($user_uid, $collection->uid);
+            if ($currPower >= 20) {
                 return true;
             }
         }
         return false;
     }
 
-    public static function userCanManage($user_uid,$studioName){
-        if(empty($user_uid)){
+    public static function userCanManage($user_uid, $studioName)
+    {
+        if (empty($user_uid)) {
             return false;
         }
         //判断是否为所有者
-        if($user_uid === StudioApi::getIdByName($studioName)){
+        if ($user_uid === StudioApi::getIdByName($studioName)) {
             return true;
-        }else{
+        } else {
             return false;
         }
     }
@@ -137,118 +141,130 @@ class ArticleController extends Controller
     public function index(Request $request)
     {
         //
-        $field = ['uid','title','subtitle',
-                                'summary','owner','lang',
-                                'status','editor_id','updated_at','created_at'];
-        if($request->get('content')==="true"){
+        $field = [
+            'uid',
+            'title',
+            'subtitle',
+            'summary',
+            'owner',
+            'lang',
+            'status',
+            'editor_id',
+            'updated_at',
+            'created_at'
+        ];
+        if ($request->input('content') === "true") {
             $field[] = 'content';
             $field[] = 'content_type';
         }
         $table = Article::select($field);
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'template':
-                $studioId = StudioApi::getIdByName($request->get('studio_name'));
+                $studioId = StudioApi::getIdByName($request->input('studio_name'));
                 $table = $table->where('owner', $studioId);
                 break;
             case 'studio':
-				# 获取studio内所有 article
+                # 获取studio内所有 article
                 $user = AuthApi::current($request);
-                if(!$user){
-                    return $this->error(__('auth.failed'),[],401);
+                if (!$user) {
+                    return $this->error(__('auth.failed'), [], 401);
                 }
                 //判断当前用户是否有指定的studio的权限
-                $studioId = StudioApi::getIdByName($request->get('name'));
-                if($user['user_uid'] !== $studioId){
-                    return $this->error(__('auth.failed'),[],403);
+                $studioId = StudioApi::getIdByName($request->input('name'));
+                if ($user['user_uid'] !== $studioId) {
+                    return $this->error(__('auth.failed'), [], 403);
                 }
 
-                if($request->get('view2','my')==='my'){
+                if ($request->input('view2', 'my') === 'my') {
                     $table = $table->where('owner', $studioId);
-                }else{
+                } else {
                     //协作
-                    $resList = ShareApi::getResList($studioId,3);
-                    $resId=[];
+                    $resList = ShareApi::getResList($studioId, 3);
+                    $resId = [];
                     foreach ($resList as $res) {
                         $resId[] = $res['res_id'];
                     }
-                    $table = $table->whereIn('uid', $resId)->where('owner','<>', $studioId);
+                    $table = $table->whereIn('uid', $resId)->where('owner', '<>', $studioId);
                 }
 
                 //根据anthology过滤
-                if($request->has('anthology')){
-                    switch ($request->get('anthology')) {
+                if ($request->has('anthology')) {
+                    switch ($request->input('anthology')) {
                         case 'all':
                             break;
                         case 'none':
                             # 我的文集
-                            $myCollection = Collection::where('owner',$studioId)->select('uid')->get();
+                            $myCollection = Collection::where('owner', $studioId)->select('uid')->get();
                             //收录在我的文集里面的文章
-                            $articles = ArticleCollection::whereIn('collect_id',$myCollection)
-                                                         ->select('article_id')->groupBy('article_id')->get();
+                            $articles = ArticleCollection::whereIn('collect_id', $myCollection)
+                                ->select('article_id')->groupBy('article_id')->get();
                             //不在这些范围之内的文章
-                            $table =  $table->whereNotIn('uid',$articles);
+                            $table =  $table->whereNotIn('uid', $articles);
                             break;
                         default:
-                            $articles = ArticleCollection::where('collect_id',$request->get('anthology'))
-                                                         ->select('article_id')->get();
-                            $table =  $table->whereIn('uid',$articles);
+                            $articles = ArticleCollection::where('collect_id', $request->input('anthology'))
+                                ->select('article_id')->get();
+                            $table =  $table->whereIn('uid', $articles);
                             break;
                     }
                 }
-				break;
+                break;
             case 'public':
-                $table = $table->where('status',30);
+                $table = $table->where('status', 30);
                 break;
             default:
                 $this->error("view error");
                 break;
         }
         //处理搜索
-        if($request->has("search") && !empty($request->get("search"))){
-            $table = $table->where('title', 'like', "%".$request->get("search")."%");
+        if ($request->has("search") && !empty($request->input("search"))) {
+            $table = $table->where('title', 'like', "%" . $request->input("search") . "%");
         }
-        if($request->has("subtitle") && !empty($request->get("subtitle"))){
-            $table = $table->where('subtitle', 'like', $request->get("subtitle"));
+        if ($request->has("subtitle") && !empty($request->input("subtitle"))) {
+            $table = $table->where('subtitle', 'like', $request->input("subtitle"));
         }
         //获取记录总条数
         $count = $table->count();
         //处理排序
-        $table = $table->orderBy($request->get("order",'updated_at'),
-                                 $request->get("dir",'desc'));
+        $table = $table->orderBy(
+            $request->input("order", 'updated_at'),
+            $request->input("dir", 'desc')
+        );
         //处理分页
-        $table = $table->skip($request->get("offset",0))
-                       ->take($request->get("limit",1000));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input("limit", 1000));
         //获取数据
         $result = $table->get();
-		return $this->ok(["rows"=>ArticleResource::collection($result),"count"=>$count]);
+        return $this->ok(["rows" => ArticleResource::collection($result), "count" => $count]);
     }
 
-        /**
+    /**
      * Display a listing of the resource.
      *
      * @return \Illuminate\Http\Response
      */
-    public function showMyNumber(Request $request){
+    public function showMyNumber(Request $request)
+    {
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        $studioId = StudioApi::getIdByName($request->get('studio'));
-        if($user['user_uid'] !== $studioId){
+        $studioId = StudioApi::getIdByName($request->input('studio'));
+        if ($user['user_uid'] !== $studioId) {
             return $this->error(__('auth.failed'));
         }
         //我的
         $my = Article::where('owner', $studioId)->count();
         //协作
-        $resList = ShareApi::getResList($studioId,3);
-        $resId=[];
+        $resList = ShareApi::getResList($studioId, 3);
+        $resId = [];
         foreach ($resList as $res) {
             $resId[] = $res['res_id'];
         }
-        $collaboration = Article::whereIn('uid', $resId)->where('owner','<>', $studioId)->count();
+        $collaboration = Article::whereIn('uid', $resId)->where('owner', '<>', $studioId)->count();
 
-        return $this->ok(['my'=>$my,'collaboration'=>$collaboration]);
+        return $this->ok(['my' => $my, 'collaboration' => $collaboration]);
     }
 
     /**
@@ -261,114 +277,114 @@ class ArticleController extends Controller
     {
         //判断权限
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             Log::error('未登录');
-            return $this->error(__('auth.failed'),[],401);
-        }else{
-            $user_uid=$user['user_uid'];
+            return $this->error(__('auth.failed'), [], 401);
+        } else {
+            $user_uid = $user['user_uid'];
         }
 
-        $canManage = ArticleController::userCanManage($user_uid,$request->get('studio'));
-        if(!$canManage){
+        $canManage = ArticleController::userCanManage($user_uid, $request->input('studio'));
+        if (!$canManage) {
             Log::error('userCanManage 失败');
             //判断是否有文集权限
-            if($request->has('anthologyId')){
-                $currPower = ShareApi::getResPower($user_uid,$request->get('anthologyId'));
-                if($currPower <= 10){
+            if ($request->has('anthologyId')) {
+                $currPower = ShareApi::getResPower($user_uid, $request->input('anthologyId'));
+                if ($currPower <= 10) {
                     Log::error('没有文集编辑权限');
-                    return $this->error(__('auth.failed'),[],403);
+                    return $this->error(__('auth.failed'), [], 403);
                 }
-            }else{
+            } else {
                 Log::error('没有文集id');
-                return $this->error(__('auth.failed'),[],403);
+                return $this->error(__('auth.failed'), [], 403);
             }
         }
         //权限判断结束
 
         //查询标题是否重复
         /*
-        if(Article::where('title',$request->get('title'))->where('owner',$studioUuid)->exists()){
+        if(Article::where('title',$request->input('title'))->where('owner',$studioUuid)->exists()){
             return $this->error(__('validation.exists'));
         }*/
-        Log::debug('开始新建'.$request->get('title'));
+        Log::debug('开始新建' . $request->input('title'));
 
         $newArticle = new Article;
-        DB::transaction(function() use($user,$request,$newArticle){
-            $studioUuid = StudioApi::getIdByName($request->get('studio'));
+        DB::transaction(function () use ($user, $request, $newArticle) {
+            $studioUuid = StudioApi::getIdByName($request->input('studio'));
             //新建文章,加入文集必须都成功。否则回滚
             $newArticle->id = app('snowflake')->id();
             $newArticle->uid = Str::uuid();
-            $newArticle->title = mb_substr($request->get('title'),0,128,'UTF-8');
-            $newArticle->lang = $request->get('lang');
-            if(!empty($request->get('status'))){
-                $newArticle->status = $request->get('status');
+            $newArticle->title = mb_substr($request->input('title'), 0, 128, 'UTF-8');
+            $newArticle->lang = $request->input('lang');
+            if (!empty($request->input('status'))) {
+                $newArticle->status = $request->input('status');
             }
             $newArticle->owner = $studioUuid;
             $newArticle->owner_id = $user['user_id'];
             $newArticle->editor_id = $user['user_id'];
-            $newArticle->parent = $request->get('parentId');
-            $newArticle->create_time = time()*1000;
-            $newArticle->modify_time = time()*1000;
+            $newArticle->parent = $request->input('parentId');
+            $newArticle->create_time = time() * 1000;
+            $newArticle->modify_time = time() * 1000;
             $newArticle->save();
-            OpsLog::debug($user['user_uid'],$newArticle);
-
-            Log::debug('开始挂接 id='.$newArticle->uid);
-            $anthologyId = $request->get('anthologyId');
-            if(Str::isUuid($anthologyId)){
-                $parentNode = $request->get('parentNode');
-                if(Str::isUuid($parentNode)){
-                    Log::debug('有挂接点'.$parentNode);
-                    $map = ArticleCollection::where('collect_id',$anthologyId)
-                                        ->orderBy('id')->get();
-                    Log::debug('查询到原map数据'.count($map));
+            OpsLog::debug($user['user_uid'], $newArticle);
+
+            Log::debug('开始挂接 id=' . $newArticle->uid);
+            $anthologyId = $request->input('anthologyId');
+            if (Str::isUuid($anthologyId)) {
+                $parentNode = $request->input('parentNode');
+                if (Str::isUuid($parentNode)) {
+                    Log::debug('有挂接点' . $parentNode);
+                    $map = ArticleCollection::where('collect_id', $anthologyId)
+                        ->orderBy('id')->get();
+                    Log::debug('查询到原map数据' . count($map));
                     $newMap = array();
                     $parentNodeLevel = -1;
                     $appended = false;
                     foreach ($map as $key => $row) {
                         $orgNode = $row;
-                        if(!$appended){
-                            if($parentNodeLevel>0){
-                                if($row->level <= $parentNodeLevel ){
+                        if (!$appended) {
+                            if ($parentNodeLevel > 0) {
+                                if ($row->level <= $parentNodeLevel) {
                                     //parent node 末尾
                                     $newNode = array();
                                     $newNode['collect_id'] = $anthologyId;
                                     $newNode['article_id'] = $newArticle->uid;
-                                    $newNode['level'] = $parentNodeLevel+1;
+                                    $newNode['level'] = $parentNodeLevel + 1;
                                     $newNode['title'] = $newArticle->title;
                                     $newNode['children'] = 0;
                                     $newMap[] = $newNode;
-                                    Log::debug('新增节点',['node'=>$newNode]);
+                                    Log::debug('新增节点', ['node' => $newNode]);
                                     $appended = true;
                                 }
-                            }else{
-                                if($row->article_id === $parentNode){
+                            } else {
+                                if ($row->article_id === $parentNode) {
                                     $parentNodeLevel = $row->level;
-                                    $orgNode['children'] = $orgNode['children']+1;
+                                    $orgNode['children'] = $orgNode['children'] + 1;
                                 }
                             }
                         }
                         $newMap[] = $orgNode;
                     }
-                    if($parentNodeLevel>0){
-                        if($appended===false){
-                        //
+                    if ($parentNodeLevel > 0) {
+                        if ($appended === false) {
+                            //
                             Log::debug('没挂上 挂到结尾');
                             $newNode = array();
                             $newNode['collect_id'] = $anthologyId;
                             $newNode['article_id'] = $newArticle->uid;
-                            $newNode['level'] = $parentNodeLevel+1;
+                            $newNode['level'] = $parentNodeLevel + 1;
                             $newNode['title'] = $newArticle->title;
                             $newNode['children'] = 0;
                             $newMap[] = $newNode;
                         }
-                    }else{
-                        Log::error('没找到挂接点'.$parentNode);
+                    } else {
+                        Log::error('没找到挂接点' . $parentNode);
                     }
-                    Log::debug('新map数据'.count($newMap));
+                    Log::debug('新map数据' . count($newMap));
 
-                    $delete = ArticleCollection::where('collect_id',$anthologyId)->delete();
-                    Log::debug('删除旧map数据'.$delete);
-                    $count=0;
+                    $delete = ArticleCollection::where('collect_id', $anthologyId)->delete();
+                    Log::debug('删除旧map数据' . $delete);
+                    $count = 0;
                     foreach ($newMap as $key => $row) {
                         $new = new ArticleCollection;
                         $new->id = app('snowflake')->id();
@@ -378,32 +394,30 @@ class ArticleController extends Controller
                         $new->level = $row["level"];
                         $new->children = $row["children"];
                         $new->editor_id = $user["user_id"];
-                        if(isset($row["deleted_at"])){
+                        if (isset($row["deleted_at"])) {
                             $new->deleted_at = $row["deleted_at"];
                         }
                         $new->save();
                         $count++;
                     }
-                    Log::debug('新map数据'.$count);
+                    Log::debug('新map数据' . $count);
                     ArticleMapController::updateCollection($anthologyId);
-                }else{
+                } else {
                     $articleMap = new ArticleCollection();
                     $articleMap->id = app('snowflake')->id();
                     $articleMap->article_id = $newArticle->uid;
-                    $articleMap->collect_id = $request->get('anthologyId');
+                    $articleMap->collect_id = $request->input('anthologyId');
                     $articleMap->title = Article::find($newArticle->uid)->title;
                     $articleMap->level = 1;
                     $articleMap->save();
                 }
-
             }
         });
-        if(Str::isUuid($newArticle->uid)){
+        if (Str::isUuid($newArticle->uid)) {
             return $this->ok(new ArticleResource($newArticle));
-        }else{
+        } else {
             return $this->error('fail');
         }
-
     }
 
     /**
@@ -412,23 +426,23 @@ class ArticleController extends Controller
      * @param  \App\Models\Article  $article
      * @return \Illuminate\Http\Response
      */
-    public function show(Request  $request,Article $article)
+    public function show(Request  $request, Article $article)
     {
         //
-        if(!$article){
+        if (!$article) {
             return $this->error("no recorder");
         }
         //判断权限
         $user = AuthApi::current($request);
-        if(!$user){
-            $user_uid="";
-        }else{
-            $user_uid=$user['user_uid'];
+        if (!$user) {
+            $user_uid = "";
+        } else {
+            $user_uid = $user['user_uid'];
         }
 
-        $canRead = ArticleController::userCanRead($user_uid,$article);
-        if(!$canRead){
-            return $this->error(__('auth.failed'),403,403);
+        $canRead = ArticleController::userCanRead($user_uid, $article);
+        if (!$canRead) {
+            return $this->error(__('auth.failed'), 403, 403);
         }
         return $this->ok(new ArticleResource($article));
     }
@@ -438,32 +452,31 @@ class ArticleController extends Controller
      * @param  string  $article
      * @return \Illuminate\Http\Response
      */
-    public function preview(Request  $request,string $articleId)
+    public function preview(Request  $request, string $articleId)
     {
         //
         $article = Article::find($articleId);
-        if(!$article){
+        if (!$article) {
             return $this->error("no recorder");
         }
         //判断权限
         $user = AuthApi::current($request);
-        if(!$user){
-            $user_uid="";
-        }else{
-            $user_uid=$user['user_uid'];
+        if (!$user) {
+            $user_uid = "";
+        } else {
+            $user_uid = $user['user_uid'];
         }
 
-        $canRead = ArticleController::userCanRead($user_uid,$article);
-        if(!$canRead){
-            return $this->error(__('auth.failed'),[],401);
+        $canRead = ArticleController::userCanRead($user_uid, $article);
+        if (!$canRead) {
+            return $this->error(__('auth.failed'), [], 401);
         }
-        if($request->has('content')){
-            $article->content = $request->get('content');
+        if ($request->has('content')) {
+            $article->content = $request->input('content');
             return $this->ok(new ArticleResource($article));
-        }else{
-            return $this->error('no content',[],200);
+        } else {
+            return $this->error('no content', [], 200);
         }
-
     }
 
     /**
@@ -476,53 +489,55 @@ class ArticleController extends Controller
     public function update(Request $request, Article $article)
     {
         //
-        if(!$article){
+        if (!$article) {
             return $this->error("no recorder");
         }
         //鉴权
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error(__('auth.failed'),401,401);
-        }else{
-            $user_uid=$user['user_uid'];
+        if (!$user) {
+            return $this->error(__('auth.failed'), 401, 401);
+        } else {
+            $user_uid = $user['user_uid'];
         }
 
-        $canEdit = ArticleController::userCanEdit($user_uid,$article);
-        if(!$canEdit){
-            return $this->error(__('auth.failed'),401,401);
+        $canEdit = ArticleController::userCanEdit($user_uid, $article);
+        if (!$canEdit) {
+            return $this->error(__('auth.failed'), 401, 401);
         }
 
         /*
         //查询标题是否重复
-        if(Article::where('title',$request->get('title'))
+        if(Article::where('title',$request->input('title'))
                   ->where('owner',$article->owner)
                   ->where('uid',"<>",$article->uid)
                   ->exists()){
             return $this->error(__('validation.exists'));
         }*/
 
-        $content = $request->get('content');
-        if($request->get('to_tpl')===true){
+        $content = $request->input('content');
+        if ($request->input('to_tpl') === true) {
             /**
              * 转化为模版
              */
-            $tplContent = $this->toTpl($content,
-                                       $request->get('anthology_id'),
-                                       $user);
+            $tplContent = $this->toTpl(
+                $content,
+                $request->input('anthology_id'),
+                $user
+            );
             $content = $tplContent;
         }
 
-        $article->title = mb_substr($request->get('title'),0,128,'UTF-8') ;
-        $article->subtitle = mb_substr($request->get('subtitle'),0,128,'UTF-8') ;
-        $article->summary = mb_substr($request->get('summary'),0,1024,'UTF-8') ;
+        $article->title = mb_substr($request->input('title'), 0, 128, 'UTF-8');
+        $article->subtitle = mb_substr($request->input('subtitle'), 0, 128, 'UTF-8');
+        $article->summary = mb_substr($request->input('summary'), 0, 1024, 'UTF-8');
         $article->content = $content;
-        $article->lang = $request->get('lang');
-        $article->status = $request->get('status',10);
+        $article->lang = $request->input('lang');
+        $article->status = $request->input('status', 10);
         $article->editor_id = $user['user_id'];
-        $article->modify_time = time()*1000;
+        $article->modify_time = time() * 1000;
         $article->save();
 
-        OpsLog::debug($user_uid,$article);
+        OpsLog::debug($user_uid, $article);
         return $this->ok(new ArticleResource($article));
     }
 
@@ -532,19 +547,19 @@ class ArticleController extends Controller
      * @param  \App\Models\Article  $article
      * @return \Illuminate\Http\Response
      */
-    public function destroy(Request $request,Article $article)
+    public function destroy(Request $request, Article $article)
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        if($user['user_uid'] !== $article->owner){
+        if ($user['user_uid'] !== $article->owner) {
             return $this->error(__('auth.failed'));
         }
         $delete = 0;
-        DB::transaction(function() use($article,$delete){
+        DB::transaction(function () use ($article, $delete) {
             //TODO 删除文集中的文章
             $delete = $article->delete();
             ArticleMapController::deleteArticle($article->uid);
@@ -553,40 +568,43 @@ class ArticleController extends Controller
         return $this->ok($delete);
     }
 
-    public function toTpl($content,$anthologyId,$user){
+    public function toTpl($content, $anthologyId, $user)
+    {
         //查询书号
-        if(!Str::isUuid($anthologyId)){
+        if (!Str::isUuid($anthologyId)) {
             throw new \Exception('anthology Id not uuid');
         }
 
-        $bookId = $this->getBookId($anthologyId,$user);
+        $bookId = $this->getBookId($anthologyId, $user);
 
-        $tpl = $this->convertToTpl($content,$bookId['book'],$bookId['paragraph']);
+        $tpl = $this->convertToTpl($content, $bookId['book'], $bookId['paragraph']);
 
         //保存原文到句子表
         $customBook = $this->getCustomBookByBookId($bookId['book']);
         $sentenceSave = new SentenceApi;
-        $auth = $sentenceSave->auth($customBook->channel_id,$user['user_uid']);
-        if(!$auth){
+        $auth = $sentenceSave->auth($customBook->channel_id, $user['user_uid']);
+        if (!$auth) {
             throw new \Exception('auth fail');
         }
         foreach ($tpl['sentences'] as $key => $sentence) {
-            $sentenceSave->store($sentence,$user);
+            $sentenceSave->store($sentence, $user);
         }
         return $tpl['content'];
     }
 
-    private function getCustomBookByBookId($bookId){
-        return CustomBook::where('book_id',$bookId)->first();
+    private function getCustomBookByBookId($bookId)
+    {
+        return CustomBook::where('book_id', $bookId)->first();
     }
 
-    private function getBookId($anthologyId,$user){
-        $anthology = Collection::where('uid',$anthologyId)->first();
-        if(!$anthology){
-            throw new \Exception('anthology not exists id='.$anthologyId);
+    private function getBookId($anthologyId, $user)
+    {
+        $anthology = Collection::where('uid', $anthologyId)->first();
+        if (!$anthology) {
+            throw new \Exception('anthology not exists id=' . $anthologyId);
         }
         $bookId = $anthology->book_id;
-        if(empty($bookId)){
+        if (empty($bookId)) {
             //生成 book id
             $newBookId = CustomBook::max('book_id') + 1;
 
@@ -599,138 +617,140 @@ class ArticleController extends Controller
             $newBook->lang = $anthology->lang;
             $newBook->status = $anthology->status;
             //查询anthology所在的studio有没有符合要求的channel 没有的话,建立
-            $channelId = ChannelApi::userBookGetOrCreate($anthology->owner,$anthology->lang,$anthology->status);
-            if($channelId === false){
-                throw new \Exception('user book get fail studio='.$anthology->owner.' language='.$anthology->lang);
+            $channelId = ChannelApi::userBookGetOrCreate($anthology->owner, $anthology->lang, $anthology->status);
+            if ($channelId === false) {
+                throw new \Exception('user book get fail studio=' . $anthology->owner . ' language=' . $anthology->lang);
             }
             $newBook->channel_id = $channelId;
             $ok = $newBook->save();
-            if(!$ok){
-                throw new \Exception('user book create fail studio='.$anthology->owner.' language='.$anthology->lang);
+            if (!$ok) {
+                throw new \Exception('user book create fail studio=' . $anthology->owner . ' language=' . $anthology->lang);
             }
-            CustomBookId::where('key','max_book_number')->update(['value'=>$newBookId]);
+            CustomBookId::where('key', 'max_book_number')->update(['value' => $newBookId]);
             $bookId = $newBookId;
             $anthology->book_id = $newBookId;
             $anthology->save();
-        }else{
-            $channelId = CustomBook::where('book_id',$bookId)->value('channel_id');
+        } else {
+            $channelId = CustomBook::where('book_id', $bookId)->value('channel_id');
         }
-        $maxPara = Sentence::where('channel_uid',$channelId)
-                           ->where('book_id',$bookId)->max('paragraph');
-        if(!$maxPara){
+        $maxPara = Sentence::where('channel_uid', $channelId)
+            ->where('book_id', $bookId)->max('paragraph');
+        if (!$maxPara) {
             $maxPara = 0;
         }
-        return ['book'=>$bookId,'paragraph'=>$maxPara+1];
+        return ['book' => $bookId, 'paragraph' => $maxPara + 1];
     }
 
-    public function convertToTpl($content,$bookId,$paraStart){
+    public function convertToTpl($content, $bookId, $paraStart)
+    {
         $newSentence = array();
         $para = $paraStart;
-		$sentNum = 1;
-		$newText =  "";
-		$isTable=false;
-		$isList=false;
-		$newSent="";
-        $sentences = explode("\n",$content);
-		foreach ($sentences as $row) {
-			//$data 为一行文本
-            $listHead= "";
+        $sentNum = 1;
+        $newText =  "";
+        $isTable = false;
+        $isList = false;
+        $newSent = "";
+        $sentences = explode("\n", $content);
+        foreach ($sentences as $row) {
+            //$data 为一行文本
+            $listHead = "";
             $isList = false;
 
             $heading = false;
             $title = false;
 
-			$trimData = trim($row);
+            $trimData = trim($row);
 
             # 判断是否为list
-			$listLeft =strstr($row,"- ",true);
-			if($listLeft !== FALSE){
-                if(ctype_space($listLeft) || empty($listLeft)){
+            $listLeft = strstr($row, "- ", true);
+            if ($listLeft !== FALSE) {
+                if (ctype_space($listLeft) || empty($listLeft)) {
                     # - 左侧是空,判定为list
-                    $isList=true;
-                    $iListPos = mb_strpos($row,'- ',0,"UTF-8");
-                    $listHead = mb_substr($row,0,$iListPos+2,"UTF-8");
-                    $listBody = mb_substr($row,$iListPos+2,mb_strlen($row,"UTF-8")-$iListPos+2,"UTF-8");
+                    $isList = true;
+                    $iListPos = mb_strpos($row, '- ', 0, "UTF-8");
+                    $listHead = mb_substr($row, 0, $iListPos + 2, "UTF-8");
+                    $listBody = mb_substr($row, $iListPos + 2, mb_strlen($row, "UTF-8") - $iListPos + 2, "UTF-8");
                 }
-			}
+            }
 
             # TODO 判断是否为标题
-			$headingStart =mb_strpos($row,"# ",0,'UTF-8');
-			if($headingStart !== false){
-                $headingLeft = mb_substr($row,0,$headingStart+2,'UTF-8');
-                $title = mb_substr($row,$headingStart+2,null,'UTF-8');
-                if(str_replace('#','', trim($headingLeft)) === ''){
+            $headingStart = mb_strpos($row, "# ", 0, 'UTF-8');
+            if ($headingStart !== false) {
+                $headingLeft = mb_substr($row, 0, $headingStart + 2, 'UTF-8');
+                $title = mb_substr($row, $headingStart + 2, null, 'UTF-8');
+                if (str_replace('#', '', trim($headingLeft)) === '') {
                     # 除了#没有其他东西,那么是标题
                     $heading = $headingLeft;
                     $newText .= $headingLeft;
-                    $newText .='{{'."{$bookId}-{$para}-{$sentNum}-{$sentNum}"."}}\n";
-                    $newSentence[] = $this->newSent($bookId,$para,$sentNum,$sentNum,$title);
-                    $newSent="";
+                    $newText .= '{{' . "{$bookId}-{$para}-{$sentNum}-{$sentNum}" . "}}\n";
+                    $newSentence[] = $this->newSent($bookId, $para, $sentNum, $sentNum, $title);
+                    $newSent = "";
                     $para++;
                     $sentNum = 1;
                     continue;
                 }
-			}
-
-			//判断是否为表格开始
-			if(mb_substr($trimData,0,1,"UTF-8") == "|"){
-				$isTable=true;
-			}
-			if($trimData!="" && $isTable == true){
-				//如果是表格 不新增句子
-				$newSent .= "{$row}\n";
-				continue;
-			}
-            if($isList == true){
+            }
+
+            //判断是否为表格开始
+            if (mb_substr($trimData, 0, 1, "UTF-8") == "|") {
+                $isTable = true;
+            }
+            if ($trimData != "" && $isTable == true) {
+                //如果是表格 不新增句子
+                $newSent .= "{$row}\n";
+                continue;
+            }
+            if ($isList == true) {
                 $newSent .= $listBody;
-            }else{
+            } else {
                 $newSent .= $trimData;
             }
 
-			#生成句子编号
-			if($trimData==""){
-				#空行
-				if(strlen($newSent)>0){
-					//之前有内容
-					$newText .='{{'."{$bookId}-{$para}-{$sentNum}-{$sentNum}"."}}\n";
-                    $newSentence[] = $this->newSent($bookId,$para,$sentNum,$sentNum,$newSent);
-					$newSent="";
-				}
-				#新的段落 不插入数据库
-				$para++;
-				$sentNum = 1;
-				$newText .="\n";
-				$isTable = false; //表格开始标记
-				$isList = false;
-				continue;
-			}else{
-				$sentNum=$sentNum+10;
-			}
-
-			if(mb_substr($trimData,0,2,"UTF-8")=="{{"){
-				#已经有的句子链接不处理
-				$newText .= $trimData."\n";
-			}else{
+            #生成句子编号
+            if ($trimData == "") {
+                #空行
+                if (strlen($newSent) > 0) {
+                    //之前有内容
+                    $newText .= '{{' . "{$bookId}-{$para}-{$sentNum}-{$sentNum}" . "}}\n";
+                    $newSentence[] = $this->newSent($bookId, $para, $sentNum, $sentNum, $newSent);
+                    $newSent = "";
+                }
+                #新的段落 不插入数据库
+                $para++;
+                $sentNum = 1;
+                $newText .= "\n";
+                $isTable = false; //表格开始标记
+                $isList = false;
+                continue;
+            } else {
+                $sentNum = $sentNum + 10;
+            }
+
+            if (mb_substr($trimData, 0, 2, "UTF-8") == "{{") {
+                #已经有的句子链接不处理
+                $newText .= $trimData . "\n";
+            } else {
                 $newText .= $listHead;
-				$newText .='{{'."{$bookId}-{$para}-{$sentNum}-{$sentNum}"."}}\n";
-                $newSentence[] = $this->newSent($bookId,$para,$sentNum,$sentNum,$newSent);
-				$newSent="";
-			}
-		}
+                $newText .= '{{' . "{$bookId}-{$para}-{$sentNum}-{$sentNum}" . "}}\n";
+                $newSentence[] = $this->newSent($bookId, $para, $sentNum, $sentNum, $newSent);
+                $newSent = "";
+            }
+        }
 
         return [
-            'content' =>$newText,
-            'sentences' =>$newSentence,
+            'content' => $newText,
+            'sentences' => $newSentence,
         ];
     }
 
-    private function newSent($book,$para,$start,$end,$content){
+    private function newSent($book, $para, $start, $end, $content)
+    {
         return array(
-            'book_id'=>$book,
-            'paragraph'=>$para,
-            'word_start'=>$start,
-            'word_end'=>$end,
-            'content'=>$content,
+            'book_id' => $book,
+            'paragraph' => $para,
+            'word_start' => $start,
+            'word_end' => $end,
+            'content' => $content,
         );
     }
 }

+ 42 - 37
api-v12/app/Http/Controllers/ArticleFtsController.php

@@ -1,7 +1,9 @@
 <?php
+
 /**
  * 文章全文搜索
  */
+
 namespace App\Http\Controllers;
 
 use Illuminate\Http\Request;
@@ -23,23 +25,23 @@ class ArticleFtsController extends Controller
     {
         //
         $pageSize = 10;
-        $pageCurrent = $request->get('from',0);
+        $pageCurrent = $request->input('from', 0);
 
         $articlesId = [];
-        if(!empty($request->get('anthology'))){
+        if (!empty($request->input('anthology'))) {
             //子节点
-            $node = ArticleCollection::where('article_id',$request->get('id'))
-                        ->where('collect_id',$request->get('anthology'))->first();
-            if($node){
-                $nodeList = ArticleCollection::where('collect_id',$request->get('anthology'))
-                                ->where('id','>=',(int)$node->id)
-                                ->orderBy('id')
-                                ->skip($request->get('from',0))
-                                ->get();
+            $node = ArticleCollection::where('article_id', $request->input('id'))
+                ->where('collect_id', $request->input('anthology'))->first();
+            if ($node) {
+                $nodeList = ArticleCollection::where('collect_id', $request->input('anthology'))
+                    ->where('id', '>=', (int)$node->id)
+                    ->orderBy('id')
+                    ->skip($request->input('from', 0))
+                    ->get();
                 $result = [];
                 $count = 0;
                 foreach ($nodeList as $curr) {
-                    if($count>0 && $curr->level <= $node->level){
+                    if ($count > 0 && $curr->level <= $node->level) {
                         break;
                     }
                     $result[] = $curr;
@@ -48,27 +50,27 @@ class ArticleFtsController extends Controller
                     $articlesId[] = $value->article_id;
                 }
             }
-        }else{
-            $articlesId[] = $request->get('id');
+        } else {
+            $articlesId[] = $request->input('id');
         }
         $total = count($articlesId);
-        $channels = explode(',',$request->get('channels'));
+        $channels = explode(',', $request->input('channels'));
         $output = [];
-        for ($i=$pageCurrent; $i <$pageCurrent+$pageSize ; $i++) {
-            if($i>=$total){
+        for ($i = $pageCurrent; $i < $pageCurrent + $pageSize; $i++) {
+            if ($i >= $total) {
                 break;
             }
             $curr = $articlesId[$i];
             foreach ($channels as $channel) {
                 # code...
-                $article = $this->fetch($curr,$channel);
+                $article = $this->fetch($curr, $channel);
                 if ($article === false) {
                     Log::error('fetch fail');
-                }else{
+                } else {
                     # code...
                     $content = $article['html'];
-                    if(!empty($request->get('key'))){
-                        if(strpos($content,$request->get('key')) !== false){
+                    if (!empty($request->input('key'))) {
+                        if (strpos($content, $request->input('key')) !== false) {
                             $output[] = $article;
                         }
                     }
@@ -76,44 +78,47 @@ class ArticleFtsController extends Controller
             }
         }
 
-        return $this->ok(['rows'=>$output,
-            'page'=>[
+        return $this->ok([
+            'rows' => $output,
+            'page' => [
                 'size' => $pageSize,
                 'current' => $pageCurrent,
                 'total' => $total
-            ],]);
+            ],
+        ]);
     }
 
-    private function fetch($articleId,$channel,$token=null){
+    private function fetch($articleId, $channel, $token = null)
+    {
         try {
             $api = config('mint.server.api.bamboo');
             $basicUrl = $api . '/v2/article/';
             $url =  $basicUrl . $articleId;;
 
             $urlParam = [
-                    'mode' => 'read',
-                    'format' => 'text',
-                    'channel' => $channel,
+                'mode' => 'read',
+                'format' => 'text',
+                'channel' => $channel,
             ];
-            Log::debug('http request',['url'=>$url,'param'=>$urlParam]);
-            if($token){
-                $response = Http::withToken($this->option('token'))->get($url,$urlParam);
-            }else{
-                $response = Http::get($url,$urlParam);
+            Log::debug('http request', ['url' => $url, 'param' => $urlParam]);
+            if ($token) {
+                $response = Http::withToken($this->option('token'))->get($url, $urlParam);
+            } else {
+                $response = Http::get($url, $urlParam);
             }
 
-            if($response->failed()){
-                Log::error('http request error'.$response->json('message'));
+            if ($response->failed()) {
+                Log::error('http request error' . $response->json('message'));
                 return false;
             }
-            if(!$response->json('ok')){
+            if (!$response->json('ok')) {
                 return false;
             }
             $article = $response->json('data');
             return $article;
-        }catch (\Throwable $th) {
+        } catch (\Throwable $th) {
             // 处理请求过程中抛出的异常
-            Log::error('fetch',['error'=>$th]);
+            Log::error('fetch', ['error' => $th]);
             return false;
         }
     }

+ 76 - 74
api-v12/app/Http/Controllers/ArticleMapController.php

@@ -21,54 +21,55 @@ class ArticleMapController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'anthology':
-                $table = ArticleCollection::where('collect_id',$request->get('id'))
-                            ->leftJoin('articles','articles.uid','=','article_collections.article_id');
+                $table = ArticleCollection::where('collect_id', $request->input('id'))
+                    ->leftJoin('articles', 'articles.uid', '=', 'article_collections.article_id');
                 break;
             case 'article':
-                $table = ArticleCollection::where('article_id',$request->get('id'))
-                            ->leftJoin('articles','articles.uid','=','article_collections.article_id');
+                $table = ArticleCollection::where('article_id', $request->input('id'))
+                    ->leftJoin('articles', 'articles.uid', '=', 'article_collections.article_id');
                 break;
         }
         $count = $table->count();
         $result = [];
-        if(!empty($request->get('parent'))){
+        if (!empty($request->input('parent'))) {
             //输出某节点的子节点
-            $node = $table->where('article_id',$request->get('parent'))->first();
-            if($node){
-                $nodeList = ArticleCollection::where('collect_id',$request->get('id'))
-                                            ->where('id','>',(int)$node->id)->orderBy('id')->get();
+            $node = $table->where('article_id', $request->input('parent'))->first();
+            if ($node) {
+                $nodeList = ArticleCollection::where('collect_id', $request->input('id'))
+                    ->where('id', '>', (int)$node->id)->orderBy('id')->get();
                 foreach ($nodeList as $key => $curr) {
-                    if($curr->level <= $node->level){
+                    if ($curr->level <= $node->level) {
                         break;
                     }
-                    if($request->has('lazy')){
-                        if($curr->level === $node->level+1){
+                    if ($request->has('lazy')) {
+                        if ($curr->level === $node->level + 1) {
                             $result[] = $curr;
                         }
-                    }else{
+                    } else {
                         $result[] = $curr;
                     }
                 }
             }
-        }else{
-            if($request->has('lazy') && $count > 300){
-                $table = $table->where('level',1);
+        } else {
+            if ($request->has('lazy') && $count > 300) {
+                $table = $table->where('level', 1);
             }
             $result = $table->select([
                 'article_collections.id',
-                'collect_id','article_id',
+                'collect_id',
+                'article_id',
                 'level',
                 'article_collections.title',
                 'children',
                 'article_collections.editor_id',
                 'article_collections.deleted_at',
                 'articles.status'
-                ])->orderBy('id')->get();
+            ])->orderBy('id')->get();
         }
 
-        return $this->ok(["rows"=>ArticleMapResource::collection($result),"count"=>$count]);
+        return $this->ok(["rows" => ArticleMapResource::collection($result), "count" => $count]);
     }
 
     /**
@@ -82,37 +83,36 @@ class ArticleMapController extends Controller
         //
 
         $validated = $request->validate([
-                'anthology_id' => 'required',
-                'operation' => 'required'
-            ]);
-        $collection  = Collection::find($request->get('anthology_id'));
-        if(!$collection){
+            'anthology_id' => 'required',
+            'operation' => 'required'
+        ]);
+        $collection  = Collection::find($request->input('anthology_id'));
+        if (!$collection) {
             return $this->error("no recorder");
         }
         //鉴权
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
-        if(!CollectionController::UserCanEdit($user["user_uid"],$collection)){
-            Log::error($user["user_uid"].'无文集编辑权限'.$collection->uid);
+        if (!CollectionController::UserCanEdit($user["user_uid"], $collection)) {
+            Log::error($user["user_uid"] . '无文集编辑权限' . $collection->uid);
             return $this->error(__('auth.failed'));
         }
         switch ($validated['operation']) {
             case 'add':
                 # 添加多个文章到文集
-                $count=0;
-                foreach ($request->get('article_id') as $key => $article) {
+                $count = 0;
+                foreach ($request->input('article_id') as $key => $article) {
                     # code...
 
-                    if(!ArticleCollection::where('article_id',$article)
-                                        ->where('collect_id',$request->get('anthology_id'))
-                                        ->exists())
-                    {
+                    if (!ArticleCollection::where('article_id', $article)
+                        ->where('collect_id', $request->input('anthology_id'))
+                        ->exists()) {
                         $new = new ArticleCollection;
                         $new->id = app('snowflake')->id();
                         $new->article_id = $article;
-                        $new->collect_id = $request->get('anthology_id');
+                        $new->collect_id = $request->input('anthology_id');
                         $new->title = Article::find($article)->title;
                         $new->level = 1;
                         $new->editor_id = $user["user_id"];
@@ -137,16 +137,15 @@ class ArticleMapController extends Controller
     public function show(string $articleCollection)
     {
         //
-        $id = explode('_',$articleCollection);
-        $result = ArticleCollection::where('article_id',$id[0])
-                    ->where('collect_id',$id[1])
-                    ->first();
-        if($result){
+        $id = explode('_', $articleCollection);
+        $result = ArticleCollection::where('article_id', $id[0])
+            ->where('collect_id', $id[1])
+            ->first();
+        if ($result) {
             return $this->ok(new ArticleMapResource($result));
-        }else{
+        } else {
             return $this->error('no');
         }
-
     }
 
     /**
@@ -164,23 +163,23 @@ class ArticleMapController extends Controller
         ]);
 
         $collection  = Collection::find($id);
-        if(!$collection){
+        if (!$collection) {
             return $this->error("no recorder");
         }
         //鉴权
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
-        if(!CollectionController::UserCanEdit($user["user_uid"],$collection)){
+        if (!CollectionController::UserCanEdit($user["user_uid"], $collection)) {
             return $this->error(__('auth.failed'));
         }
 
         switch ($validated['operation']) {
             case 'anthology':
-                $delete = ArticleCollection::where('collect_id',$id)->delete();
-                $count=0;
-                foreach ($request->get('data') as $key => $row) {
+                $delete = ArticleCollection::where('collect_id', $id)->delete();
+                $count = 0;
+                foreach ($request->input('data') as $key => $row) {
                     # code...
                     $new = new ArticleCollection;
                     $new->id = app('snowflake')->id();
@@ -190,7 +189,7 @@ class ArticleMapController extends Controller
                     $new->level = $row["level"];
                     $new->children = $row["children"];
                     $new->editor_id = $user["user_id"];
-                    if(isset($row["deleted_at"])){
+                    if (isset($row["deleted_at"])) {
                         $new->deleted_at = $row["deleted_at"];
                     }
                     $new->save();
@@ -213,48 +212,51 @@ class ArticleMapController extends Controller
         //
     }
 
-    public static function deleteArticle(string $articleId){
+    public static function deleteArticle(string $articleId)
+    {
         //查找有这个文章的文集
-        $collections = ArticleCollection::where('article_id',$articleId)
-                                        ->select('collect_id')
-                                        ->groupBy('collect_id')
-                                        ->get();
+        $collections = ArticleCollection::where('article_id', $articleId)
+            ->select('collect_id')
+            ->groupBy('collect_id')
+            ->get();
         //设置为删除
-        ArticleCollection::where('article_id',$articleId)
-                         ->update(['deleted_at'=>now()]);
+        ArticleCollection::where('article_id', $articleId)
+            ->update(['deleted_at' => now()]);
         //查找没有下级文章的文集
-        $updateCollections = ArticleCollection::where('article_id',$articleId)
-                                            ->where('children',0)
-                                            ->select('collect_id')
-                                            ->groupBy('collect_id')
-                                            ->get();
+        $updateCollections = ArticleCollection::where('article_id', $articleId)
+            ->where('children', 0)
+            ->select('collect_id')
+            ->groupBy('collect_id')
+            ->get();
         //真的删除没有下级文章的文集中的文章
-        $count = ArticleCollection::where('article_id',$articleId)
-                                  ->where('children',0)
-                                  ->delete();
+        $count = ArticleCollection::where('article_id', $articleId)
+            ->where('children', 0)
+            ->delete();
         //更新改动的文集
         foreach ($updateCollections as  $collection) {
             # code...
             ArticleMapController::updateCollection($collection->collect_id);
         }
-        return [count($collections),$count];
+        return [count($collections), $count];
     }
 
-    public static function deleteCollection(string $collectionId){
-        $count = ArticleCollection::where('collect_id',$collectionId)
-                                  ->delete();
+    public static function deleteCollection(string $collectionId)
+    {
+        $count = ArticleCollection::where('collect_id', $collectionId)
+            ->delete();
         return $count;
     }
 
     /**
      * 用表中的数据生成json,更新collection 表中的字段
      */
-    public static function updateCollection(string $collectionId){
-        $result = ArticleCollection::where('collect_id',$collectionId)
-                        ->select(['article_id','level','title'])
-                        ->orderBy('id')->get();
-        Collection::where('uid',$collectionId)
-                  ->update(['article_list'=>json_encode($result,JSON_UNESCAPED_UNICODE)]);
+    public static function updateCollection(string $collectionId)
+    {
+        $result = ArticleCollection::where('collect_id', $collectionId)
+            ->select(['article_id', 'level', 'title'])
+            ->orderBy('id')->get();
+        Collection::where('uid', $collectionId)
+            ->update(['article_list' => json_encode($result, JSON_UNESCAPED_UNICODE)]);
         return count($result);
     }
 }

+ 3 - 3
api-v12/app/Http/Controllers/ArticleNavController.php

@@ -16,9 +16,9 @@ class ArticleNavController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get('type')) {
+        switch ($request->input('type')) {
             case 'chapter':
-                $para = explode('-', $request->get('id'));
+                $para = explode('-', $request->input('id'));
                 $prev = PaliText::where('book', $para[0])
                     ->where('paragraph', '<', $para[1])
                     ->where('level', '<', 8)
@@ -41,7 +41,7 @@ class ArticleNavController extends Controller
                 }
                 break;
             case 'para':
-                $para = explode('-', $request->get('id'));
+                $para = explode('-', $request->input('id'));
                 $prev = PaliText::where('book', $para[0])
                     ->where('paragraph', '<', $para[1])
                     ->orderBy('paragraph', 'desc')

+ 26 - 26
api-v12/app/Http/Controllers/ArticleProgressController.php

@@ -20,40 +20,40 @@ class ArticleProgressController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'chapter':
-                $chapter = PaliTextApi::getChapterStartEnd($request->get('book'),$request->get('para'));
-                $channels = Sentence::where('book_id',$request->get('book'))
-                                    ->whereBetween('paragraph',$chapter)
-                                    ->where('strlen','>',0)
-                                    ->groupBy('channel_uid')
-                                    ->select('channel_uid')
-                                    ->get();
+                $chapter = PaliTextApi::getChapterStartEnd($request->input('book'), $request->input('para'));
+                $channels = Sentence::where('book_id', $request->input('book'))
+                    ->whereBetween('paragraph', $chapter)
+                    ->where('strlen', '>', 0)
+                    ->groupBy('channel_uid')
+                    ->select('channel_uid')
+                    ->get();
                 //获取单句长度
-                $sentLen = PaliSentence::where('book',$request->get('book'))
-                            ->whereBetween('paragraph',$chapter)
-                            ->orderBy('word_begin')
-                            ->select(['book','paragraph','word_begin','word_end','length'])
-                            ->get();
+                $sentLen = PaliSentence::where('book', $request->input('book'))
+                    ->whereBetween('paragraph', $chapter)
+                    ->orderBy('word_begin')
+                    ->select(['book', 'paragraph', 'word_begin', 'word_end', 'length'])
+                    ->get();
                 //获取每个channel的完成度
                 foreach ($channels as $key => $value) {
                     # code...
-                    $finished = Sentence::where('book_id',$request->get('book'))
-                    ->whereBetween('paragraph',$chapter)
-                    ->where('channel_uid',$value->channel_uid)
-                    ->where('strlen','>',0)
-                    ->select(['strlen','book_id','paragraph','word_start','word_end'])
-                    ->get();
-                    $final=[];
+                    $finished = Sentence::where('book_id', $request->input('book'))
+                        ->whereBetween('paragraph', $chapter)
+                        ->where('channel_uid', $value->channel_uid)
+                        ->where('strlen', '>', 0)
+                        ->select(['strlen', 'book_id', 'paragraph', 'word_start', 'word_end'])
+                        ->get();
+                    $final = [];
                     foreach ($sentLen as  $sent) {
                         # code...
-                        $first = Arr::first($finished, function ($value, $key) use($sent) {
-                            return ($value->book_id==$sent->book &&
-                                    $value->paragraph==$sent->paragraph &&
-                                    $value->word_start==$sent->word_begin &&
-                                    $value->word_end==$sent->word_end);
+                        $first = Arr::first($finished, function ($value, $key) use ($sent) {
+                            return ($value->book_id == $sent->book &&
+                                $value->paragraph == $sent->paragraph &&
+                                $value->word_start == $sent->word_begin &&
+                                $value->word_end == $sent->word_end);
                         });
-                        $final[] = [$sent->length,$first?true:false];
+                        $final[] = [$sent->length, $first ? true : false];
                     }
                     $value['final'] = $final;
                 }

+ 66 - 65
api-v12/app/Http/Controllers/AttachmentController.php

@@ -28,38 +28,40 @@ class AttachmentController extends Controller
     public function index(Request $request)
     {
         //
-		switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'studio':
                 $user = AuthApi::current($request);
-                if(!$user){
+                if (!$user) {
                     return $this->error(__('auth.failed'));
                 }
                 //判断当前用户是否有指定的studio的权限
-                if($user['user_uid'] !== StudioApi::getIdByName($request->get('studio'))){
+                if ($user['user_uid'] !== StudioApi::getIdByName($request->input('studio'))) {
                     return $this->error(__('auth.failed'));
                 }
                 $table = Attachment::where('owner_uid', $user["user_uid"]);
                 break;
             default:
-                return $this->error("error view",[],200);
-            break;
+                return $this->error("error view", [], 200);
+                break;
         }
-        if($request->has('search')){
-            $table = $table->where('title', 'like', $request->get('search')."%");
+        if ($request->has('search')) {
+            $table = $table->where('title', 'like', $request->input('search') . "%");
         }
-        if($request->has('content_type')){
-            $table = $table->where('content_type', 'like', $request->get('content_type')."%");
+        if ($request->has('content_type')) {
+            $table = $table->where('content_type', 'like', $request->input('content_type') . "%");
         }
         $count = $table->count();
-        $table = $table->orderBy($request->get('order','updated_at'),
-                                 $request->get('dir','desc'));
+        $table = $table->orderBy(
+            $request->input('order', 'updated_at'),
+            $request->input('dir', 'desc')
+        );
 
-        $table = $table->skip($request->get('offset',0))
-                       ->take($request->get('limit',1000));
+        $table = $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
 
-        return $this->ok(["rows"=>AttachmentResource::collection($result),"count"=>$count]);
+        return $this->ok(["rows" => AttachmentResource::collection($result), "count" => $count]);
     }
 
     /**
@@ -71,8 +73,8 @@ class AttachmentController extends Controller
     public function store(Request $request)
     {
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error(__('auth.failed'),401,401);
+        if (!$user) {
+            return $this->error(__('auth.failed'), 401, 401);
         }
 
         $request->validate([
@@ -80,14 +82,14 @@ class AttachmentController extends Controller
         ]);
 
         $isCreate = true;
-        if(Str::isUuid($request->get('id'))){
-            $attachment = Attachment::find($request->get('id'));
-            if(!$attachment){
+        if (Str::isUuid($request->input('id'))) {
+            $attachment = Attachment::find($request->input('id'));
+            if (!$attachment) {
                 return $this->error('no res');
             }
             $fileId = $attachment->id;
             $isCreate = false;
-        }else{
+        } else {
             $fileId = Str::uuid();
         }
 
@@ -96,34 +98,34 @@ class AttachmentController extends Controller
 
         $ext = $file->getClientOriginalExtension();
 
-        if($request->get('type') === 'avatar'){
+        if ($request->input('type') === 'avatar') {
             $resize = Image::make($file)->fit(512);
-            Storage::put($bucket.'/'.$fileId.'.jpg',$resize->stream());
+            Storage::put($bucket . '/' . $fileId . '.jpg', $resize->stream());
             $resize = Image::make($file)->fit(256);
-            Storage::put($bucket.'/'.$fileId.'_m.jpg',$resize->stream());
+            Storage::put($bucket . '/' . $fileId . '_m.jpg', $resize->stream());
             $resize = Image::make($file)->fit(128);
-            Storage::put($bucket.'/'.$fileId.'_s.jpg',$resize->stream());
-            $name = $fileId.'.jpg';
-        }else{
+            Storage::put($bucket . '/' . $fileId . '_s.jpg', $resize->stream());
+            $name = $fileId . '.jpg';
+        } else {
             //Move Uploaded File
-            $name = $fileId.'.'.$ext;
-            if(!$isCreate){
+            $name = $fileId . '.' . $ext;
+            if (!$isCreate) {
                 //替换模式,先删除旧文件
-                Storage::delete($bucket.'/'.$name);
+                Storage::delete($bucket . '/' . $name);
             }
-            $filename = $file->storeAs($bucket,$name);
+            $filename = $file->storeAs($bucket, $name);
         }
 
-        if($isCreate){
+        if ($isCreate) {
             $attachment = new Attachment;
             $attachment->id = $fileId;
             $attachment->bucket = $bucket;
-            if($request->has('studio')){
-                $owner_uid = StudioApi::getIdByName($request->get('studio'));
-            }else{
+            if ($request->has('studio')) {
+                $owner_uid = StudioApi::getIdByName($request->input('studio'));
+            } else {
                 $owner_uid = $user['user_uid'];
             }
-            if($owner_uid){
+            if ($owner_uid) {
                 $attachment->owner_uid = $owner_uid;
             }
             $attachment->status = 'public';
@@ -138,17 +140,17 @@ class AttachmentController extends Controller
         $attachment->content_type = $file->getMimeType();
         $attachment->save();
 
-        $type = explode('/',$file->getMimeType());
+        $type = explode('/', $file->getMimeType());
         switch ($type[0]) {
             case 'image':
                 $thumbnail = Image::make($file);
                 break;
             case 'video':
-                $tmpFile = $file->storeAs($bucket,$name,'local');
-                $path = storage_path('app/'.$tmpFile);
+                $tmpFile = $file->storeAs($bucket, $name, 'local');
+                $path = storage_path('app/' . $tmpFile);
                 if (App::environment('local')) {
                     $ffmpeg = FFMpeg::create();
-                }else{
+                } else {
                     $ffmpeg = FFMpeg::create(array(
                         'ffmpeg.binaries' => '/usr/bin/ffmpeg',
                         'ffprobe.binaries' => '/usr/bin/ffprobe',
@@ -167,23 +169,22 @@ class AttachmentController extends Controller
                 # code...
                 break;
         }
-        if(isset($thumbnail)){
+        if (isset($thumbnail)) {
             //生成缩略图
             $thumbnail->resize(256, 256, function ($constraint) {
                 $constraint->aspectRatio();
             });
-            Storage::put($bucket.'/'.$fileId.'_m.jpg',$thumbnail->stream());
+            Storage::put($bucket . '/' . $fileId . '_m.jpg', $thumbnail->stream());
             $thumbnail->resize(128, 128, function ($constraint) {
                 $constraint->aspectRatio();
             });
-            Storage::put($bucket.'/'.$fileId.'_s.jpg',$thumbnail->stream());
+            Storage::put($bucket . '/' . $fileId . '_s.jpg', $thumbnail->stream());
             //销毁图片资源
             $thumbnail->destroy();
         }
 
 
         return $this->ok(new AttachmentResource($attachment));
-
     }
 
     /**
@@ -209,11 +210,11 @@ class AttachmentController extends Controller
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error(__('auth.failed'),401,401);
+        if (!$user) {
+            return $this->error(__('auth.failed'), 401, 401);
         }
 
-        $attachment->title = $request->get('title');
+        $attachment->title = $request->input('title');
         $attachment->save();
         return $this->ok(new AttachmentResource($attachment));
     }
@@ -224,42 +225,42 @@ class AttachmentController extends Controller
      * @param  string  $id
      * @return \Illuminate\Http\Response
      */
-    public function destroy(Request $request,string $id)
+    public function destroy(Request $request, string $id)
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error(__('auth.failed'),401,401);
+        if (!$user) {
+            return $this->error(__('auth.failed'), 401, 401);
         }
-        if(Str::isUuid($id)){
-            $res = Attachment::where('id',$id)->first();
-        }else{
+        if (Str::isUuid($id)) {
+            $res = Attachment::where('id', $id)->first();
+        } else {
             /**
              * 从文件名获取bucket和name
              */
-            $pos = mb_strrpos($request->get('name'),'/',0,"UTF-8");
-            if($pos === false){
-                return $this->error('无效的文件名',500,500);
+            $pos = mb_strrpos($request->input('name'), '/', 0, "UTF-8");
+            if ($pos === false) {
+                return $this->error('无效的文件名', 500, 500);
             }
-            $bucket = mb_substr($request->get('name'),0,$pos,'UTF-8');
-            $name = mb_substr($request->get('name'),$pos+1,NULL,'UTF-8');
-            $res = Attachment::where('bucket',$bucket)
-                            ->where('name',$name)
-                            ->first();
+            $bucket = mb_substr($request->input('name'), 0, $pos, 'UTF-8');
+            $name = mb_substr($request->input('name'), $pos + 1, NULL, 'UTF-8');
+            $res = Attachment::where('bucket', $bucket)
+                ->where('name', $name)
+                ->first();
         }
-        if(!$res){
+        if (!$res) {
             return $this->error('no res');
         }
-        if($user['user_uid'] !== $res->user_uid){
-            return $this->error(__('auth.failed'),403,403);
+        if ($user['user_uid'] !== $res->user_uid) {
+            return $this->error(__('auth.failed'), 403, 403);
         }
 
         //删除文件
         $filename = $res->bucket . '/' . $res->name;
         $path_parts = pathinfo($res->name);
         Storage::delete($filename);
-        Storage::delete($res->bucket.'/'.$path_parts['filename'].'_m.jpg');
-        Storage::delete($res->bucket.'/'.$path_parts['filename'].'_s.jpg');
+        Storage::delete($res->bucket . '/' . $path_parts['filename'] . '_m.jpg');
+        Storage::delete($res->bucket . '/' . $path_parts['filename'] . '_s.jpg');
 
         $del = $res->delete();
         return $this->ok($del);

+ 4 - 4
api-v12/app/Http/Controllers/AuthController.php

@@ -73,12 +73,12 @@ class AuthController extends Controller
     {
 
         $query = UserInfo::where(function ($query) use ($request) {
-            $query->where('username', $request->get('username'))
-                ->where('password', md5($request->get('password')));
+            $query->where('username', $request->input('username'))
+                ->where('password', md5($request->input('password')));
         })
             ->orWhere(function ($query) use ($request) {
-                $query->where('email', $request->get('username'))
-                    ->where('password', md5($request->get('password')));
+                $query->where('email', $request->input('username'))
+                    ->where('password', md5($request->input('password')));
             });
         //Log::info($query->toSql());
         $user = $query->first();

+ 1 - 1
api-v12/app/Http/Controllers/BlogController.php

@@ -187,7 +187,7 @@ class BlogController extends Controller
     // 搜索
     public function search(Request $request)
     {
-        $query = $request->get('q');
+        $query = $request->input('q');
 
         if (empty($query)) {
             return redirect()->route('blog.index');

+ 8 - 6
api-v12/app/Http/Controllers/CategoryController.php

@@ -8,6 +8,7 @@ use App\Models\ProgressChapter;
 use App\Models\Tag;
 use App\Models\TagMap;
 
+
 class CategoryController extends Controller
 {
     protected static int $nextId = 1;
@@ -116,10 +117,6 @@ class CategoryController extends Controller
         }
         // 获取该分类下的章节
         $books = ProgressChapter::with('channel.owner')
-            ->leftJoin('pali_texts', function ($join) {
-                $join->on('progress_chapters.book', '=', 'pali_texts.book')
-                    ->on('progress_chapters.para', '=', 'pali_texts.paragraph');
-            })
             ->whereIns(['progress_chapters.book', 'progress_chapters.para'], $chaptersParam)
             ->whereHas('channel', function ($query) {
                 $query->where('status', 30);
@@ -140,13 +137,18 @@ class CategoryController extends Controller
             if (empty($title)) {
                 $title = $pali->firstWhere('book', $book->book)->toc;
             }
+            //Log::debug('getBooksInfo', ['book' => $book->book, 'paragraph' => $book->para]);
+            $pcd_book_id = $pali->first(function ($item) use ($book) {
+                return $item->book == $book->book
+                    && $item->paragraph == $book->para;
+            })?->pcd_book_id;
             $categoryBooks[] = [
                 "id" => $book->uid,
                 "title" => $title,
                 "author" => $book->channel->name,
                 "publisher" => $book->channel->owner,
                 "type" => __('labels.' . $book->channel->type),
-                "cover" => "/assets/images/cover/zh-hans/1/{$book->pcd_book_id}.png",
+                "cover" => "/assets/images/cover/zh-hans/1/{$pcd_book_id}.png",
                 "description" => $book->summary ?? "比库戒律的详细说明",
                 "language" => __('language.' . $book->channel->lang),
             ];
@@ -155,7 +157,7 @@ class CategoryController extends Controller
     }
     private function loadCategories()
     {
-        $json = file_get_contents(public_path("date/category/default.json"));
+        $json = file_get_contents(public_path("data/category/default.json"));
         $tree = json_decode($json, true);
         $flat = self::flattenWithIds($tree);
         return $flat;

+ 61 - 55
api-v12/app/Http/Controllers/ChannelController.php

@@ -14,7 +14,6 @@ use App\Models\WbwBlock;
 use App\Models\PaliSentence;
 use App\Models\CustomBook;
 
-use App\Http\Controllers\AuthController;
 use App\Http\Resources\ChannelResource;
 
 use App\Http\Api\AuthApi;
@@ -50,15 +49,15 @@ class ChannelController extends Controller
         if ($request->has("book")) {
             $indexCol[] = 'progress_chapters.progress';
         }
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'public':
                 $table = Channel::select($indexCol)
                     ->where('status', 30);
                 /*
                 if ($request->has("book")) {
                     $table = $table->leftJoin('progress_chapters', 'channels.uid', '=', 'progress_chapters.channel_id',)
-                        ->where('progress_chapters.book', $request->get("book"))
-                        ->where('progress_chapters.para', $request->get("paragraph"));
+                        ->where('progress_chapters.book', $request->input("book"))
+                        ->where('progress_chapters.para', $request->input("paragraph"));
                 }*/
                 break;
             case 'studio':
@@ -68,13 +67,13 @@ class ChannelController extends Controller
                     return $this->error(__('auth.failed'));
                 }
                 //判断当前用户是否有指定的studio的权限
-                $studioId = StudioApi::getIdByName($request->get('name'));
+                $studioId = StudioApi::getIdByName($request->input('name'));
                 if (!StudioApi::userCanList($user['user_uid'], $studioId)) {
                     return $this->error(__('auth.failed'), 403, 403);
                 }
 
                 $table = Channel::select($indexCol);
-                if ($request->get('view2', 'my') === 'my') {
+                if ($request->input('view2', 'my') === 'my') {
                     $table = $table->where('owner_uid', $studioId);
                 } else {
                     //协作
@@ -84,8 +83,8 @@ class ChannelController extends Controller
                         $resId[] = $res['res_id'];
                     }
                     $table = $table->whereIn('channels.uid', $resId);
-                    if ($request->get('collaborator', 'all') !== 'all') {
-                        $table = $table->where('owner_uid', $request->get('collaborator'));
+                    if ($request->input('collaborator', 'all') !== 'all') {
+                        $table = $table->where('owner_uid', $request->input('collaborator'));
                     } else {
                         $table = $table->where('owner_uid', '<>', $studioId);
                     }
@@ -101,7 +100,7 @@ class ChannelController extends Controller
                     return $this->error(__('auth.failed'));
                 }
                 //判断当前用户是否有指定的studio的权限
-                if ($user['user_uid'] !== StudioApi::getIdByName($request->get('name'))) {
+                if ($user['user_uid'] !== StudioApi::getIdByName($request->input('name'))) {
                     return $this->error(__('auth.failed'));
                 }
                 $channelById = [];
@@ -157,8 +156,8 @@ class ChannelController extends Controller
                     $channelById[$value['res_id']] = $value;
                 }
                 //获取全网公开channel
-                $chapter = PaliTextApi::getChapterStartEnd($request->get('book'), $request->get('para'));
-                $publicChannelsWithContent = Sentence::where('book_id', $request->get('book'))
+                $chapter = PaliTextApi::getChapterStartEnd($request->input('book'), $request->input('para'));
+                $publicChannelsWithContent = Sentence::where('book_id', $request->input('book'))
                     ->whereBetween('paragraph', $chapter)
                     ->where('strlen', '>', 0)
                     ->where('status', 30)
@@ -185,49 +184,49 @@ class ChannelController extends Controller
                 break;
             case 'id':
                 $table = Channel::select($indexCol)
-                    ->whereIn('uid', explode(',', $request->get("id")));
+                    ->whereIn('uid', explode(',', $request->input("id")));
         }
 
         if ($request->has("book")) {
-            if ($request->get("view") === "public") {
+            if ($request->input("view") === "public") {
                 $table = $table->leftJoin('progress_chapters', 'channels.uid', '=', 'progress_chapters.channel_id',)
-                    ->where('progress_chapters.book', $request->get("book"))
-                    ->where('progress_chapters.para', $request->get("paragraph"));
+                    ->where('progress_chapters.book', $request->input("book"))
+                    ->where('progress_chapters.para', $request->input("paragraph"));
             } else {
                 $table = $table->leftJoin('progress_chapters', function ($join) use ($request) {
                     $join->on('channels.uid', '=', 'progress_chapters.channel_id')
-                        ->where('progress_chapters.book', $request->get("book"))
-                        ->where('progress_chapters.para', $request->get("paragraph")); // 条件写在这里!
+                        ->where('progress_chapters.book', $request->input("book"))
+                        ->where('progress_chapters.para', $request->input("paragraph")); // 条件写在这里!
                 });
             }
 
             /* leftJoin('progress_chapters', 'channels.uid', '=', 'progress_chapters.channel_id',)
-                ->where('progress_chapters.book', $request->get("book"))
-                ->where('progress_chapters.para', $request->get("paragraph"));*/
+                ->where('progress_chapters.book', $request->input("book"))
+                ->where('progress_chapters.para', $request->input("paragraph"));*/
         }
         //处理搜索
-        if (!empty($request->get("search"))) {
-            $table = $table->where('name', 'like', "%" . $request->get("search") . "%");
+        if (!empty($request->input("search"))) {
+            $table = $table->where('name', 'like', "%" . $request->input("search") . "%");
         }
         if ($request->has("type")) {
-            $table = $table->where('type', $request->get("type"));
+            $table = $table->where('type', $request->input("type"));
         }
         if ($request->has("updated_at")) {
-            $table = $table->where('updated_at', '>', $request->get("updated_at"));
+            $table = $table->where('updated_at', '>', $request->input("updated_at"));
         }
         if ($request->has("created_at")) {
-            $table = $table->where('created_at', '>', $request->get("created_at"));
+            $table = $table->where('created_at', '>', $request->input("created_at"));
         }
         //获取记录总条数
         $count = $table->count();
         //处理排序
         $table = $table->orderBy(
-            $request->get("order", 'created_at'),
-            $request->get("dir", 'desc')
+            $request->input("order", 'created_at'),
+            $request->input("dir", 'desc')
         );
         //处理分页
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get("limit", 200));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input("limit", 200));
         Log::debug('channel sql ' . $table->toSql());
         //获取数据
         $result = $table->get();
@@ -236,7 +235,7 @@ class ChannelController extends Controller
             if ($request->has('progress')) {
                 //获取进度
                 //获取单句长度
-                $sentLen = PaliSentence::where('book', $request->get('book'))
+                $sentLen = PaliSentence::where('book', $request->input('book'))
                     ->whereBetween('paragraph', $chapter)
                     ->orderBy('word_begin')
                     ->select(['book', 'paragraph', 'word_begin', 'word_end', 'length'])
@@ -245,7 +244,7 @@ class ChannelController extends Controller
             foreach ($result as $key => $value) {
                 if ($request->has('progress')) {
                     //获取进度
-                    $finalTable = Sentence::where('book_id', $request->get('book'))
+                    $finalTable = Sentence::where('book_id', $request->input('book'))
                         ->whereBetween('paragraph', $chapter)
                         ->where('channel_uid', $value->uid)
                         ->where('strlen', '>', 0)
@@ -312,7 +311,7 @@ class ChannelController extends Controller
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        $studioId = StudioApi::getIdByName($request->get('studio'));
+        $studioId = StudioApi::getIdByName($request->input('studio'));
         if ($user['user_uid'] !== $studioId) {
             return $this->error(__('auth.failed'));
         }
@@ -338,7 +337,7 @@ class ChannelController extends Controller
     {
         $indexCol = ['uid', 'name', 'summary', 'type', 'owner_uid', 'lang', 'status', 'updated_at', 'created_at'];
 
-        $sent = $request->get('sentence');
+        $sent = $request->input('sentence');
         $query = [];
         $queryWithChannel = [];
         $sentContainer = [];
@@ -371,7 +370,13 @@ class ChannelController extends Controller
         }
         //获取单句长度
         if (count($query) > 0) {
-            $table = Sentence::whereIns(['book_id', 'paragraph', 'word_start', 'word_end', 'channel_uid'], $queryWithChannel)
+            $table = Sentence::whereIns([
+                'book_id',
+                'paragraph',
+                'word_start',
+                'word_end',
+                'channel_uid'
+            ], $queryWithChannel)
                 ->select(['book_id', 'paragraph', 'word_start', 'word_end', 'strlen']);
             $sentLen = $table->get();
 
@@ -389,9 +394,10 @@ class ChannelController extends Controller
         $channelId = [];
 
         //获取全网公开的有译文的channel
-        if ($request->get('owner') === 'all' || $request->get('owner') === 'public') {
+        if ($request->input('owner') === 'all' || $request->input('owner') === 'public') {
             if (count($query) > 0) {
-                $publicChannelsWithContent = Sentence::whereIns(['book_id', 'paragraph', 'word_start', 'word_end'], $query)
+                $fields = ['book_id', 'paragraph', 'word_start', 'word_end'];
+                $publicChannelsWithContent = Sentence::whereIns($fields, $query)
                     ->where('strlen', '>', 0)
                     ->where('status', 30)
                     ->groupBy('channel_uid')
@@ -414,7 +420,7 @@ class ChannelController extends Controller
         $user = AuthApi::current($request);
         if ($user !== false) {
             //我自己的
-            if ($request->get('owner') === 'all' || $request->get('owner') === 'my') {
+            if ($request->input('owner') === 'all' || $request->input('owner') === 'my') {
                 $my = Channel::select($indexCol)->where('owner_uid', $user['user_uid'])->get();
                 foreach ($my as $key => $value) {
                     $channelId[] = $value->uid;
@@ -427,7 +433,7 @@ class ChannelController extends Controller
             }
 
             //获取共享channel
-            if ($request->get('owner') === 'all' || $request->get('owner') === 'collaborator') {
+            if ($request->input('owner') === 'all' || $request->input('owner') === 'collaborator') {
                 $allSharedChannels = ShareApi::getResList($user['user_uid'], 2);
                 foreach ($allSharedChannels as $key => $value) {
                     # code...
@@ -542,13 +548,13 @@ class ChannelController extends Controller
             return $this->error(__('auth.failed'), 401, 401);
         }
         //判断当前用户是否有指定的studio的权限
-        $studioId = StudioApi::getIdByName($request->get('studio'));
+        $studioId = StudioApi::getIdByName($request->input('studio'));
         if (!StudioApi::userCanManage($user['user_uid'], $studioId)) {
             return $this->error(__('auth.failed'), 403, 403);
         }
         $studio = StudioApi::getById($studioId);
         //查询是否重复
-        if (Channel::where('name', $request->get('name'))
+        if (Channel::where('name', $request->input('name'))
             ->where('owner_uid', $studioId)
             ->exists()
         ) {
@@ -557,10 +563,10 @@ class ChannelController extends Controller
 
         $channel = new Channel;
         $channel->id = app('snowflake')->id();
-        $channel->name = $request->get('name');
+        $channel->name = $request->input('name');
         $channel->owner_uid = $studioId;
-        $channel->type = $request->get('type');
-        $channel->lang = $request->get('lang');
+        $channel->type = $request->input('type');
+        $channel->lang = $request->input('lang');
         $channel->editor_id = $user['user_id'];
         if (isset($studio['roles'])) {
             if (in_array('basic', $studio['roles'])) {
@@ -630,16 +636,16 @@ class ChannelController extends Controller
         }
         if ($channel->owner_uid !== $user["user_uid"]) {
             //判断是否为协作
-            $power = ShareApi::getResPower($user["user_uid"], $request->get('id'));
+            $power = ShareApi::getResPower($user["user_uid"], $request->input('id'));
             if ($power < 30) {
                 return $this->error(__('auth.failed'), 403, 403);
             }
         }
-        $channel->name = $request->get('name');
-        $channel->type = $request->get('type');
-        $channel->summary = $request->get('summary');
-        $channel->lang = $request->get('lang');
-        $channel->status = $request->get('status');
+        $channel->name = $request->input('name');
+        $channel->type = $request->input('type');
+        $channel->summary = $request->input('summary');
+        $channel->lang = $request->input('lang');
+        $channel->status = $request->input('status');
         $channel->save();
         return $this->ok($channel);
     }
@@ -662,28 +668,28 @@ class ChannelController extends Controller
         }
         if ($channel->owner_uid !== $user["user_uid"]) {
             //判断是否为协作
-            $power = ShareApi::getResPower($user["user_uid"], $request->get('id'));
+            $power = ShareApi::getResPower($user["user_uid"], $request->input('id'));
             if ($power < 30) {
                 return $this->error(__('auth.failed'), [], 403);
             }
         }
         if ($request->has('name')) {
-            $channel->name = $request->get('name');
+            $channel->name = $request->input('name');
         }
         if ($request->has('type')) {
-            $channel->type = $request->get('type');
+            $channel->type = $request->input('type');
         }
         if ($request->has('summary')) {
-            $channel->summary = $request->get('summary');
+            $channel->summary = $request->input('summary');
         }
         if ($request->has('lang')) {
-            $channel->lang = $request->get('lang');
+            $channel->lang = $request->input('lang');
         }
         if ($request->has('status')) {
-            $channel->status = $request->get('status');
+            $channel->status = $request->input('status');
         }
         if ($request->has('config')) {
-            $channel->status = $request->get('config');
+            $channel->status = $request->input('config');
         }
         $channel->save();
         return $this->ok($channel);

+ 18 - 10
api-v12/app/Http/Controllers/ChannelIOController.php

@@ -15,24 +15,32 @@ class ChannelIOController extends Controller
     public function index(Request $request)
     {
         //
-        $table = Channel::select(['uid','name','summary',
-                                'type','owner_uid','lang',
-                                'status','updated_at','created_at']);
-        switch ($request->get('view')) {
+        $table = Channel::select([
+            'uid',
+            'name',
+            'summary',
+            'type',
+            'owner_uid',
+            'lang',
+            'status',
+            'updated_at',
+            'created_at'
+        ]);
+        switch ($request->input('view')) {
             case 'public':
-                $table->where('status',30)
-                      ->where('updated_at','>',$request->get('updated_at','2000-1-1'));
+                $table->where('status', 30)
+                    ->where('updated_at', '>', $request->input('updated_at', '2000-1-1'));
                 break;
         }
         $count = $table->count();
         //处理排序
-        $table->orderBy('updated_at','asc');
+        $table->orderBy('updated_at', 'asc');
         //处理分页
-        $table->skip($request->get("offset",0))
-              ->take($request->get("limit",200));
+        $table->skip($request->input("offset", 0))
+            ->take($request->input("limit", 200));
         //获取数据
         $result = $table->get();
-        return $this->ok(["rows"=>$result,"count"=>$count]);
+        return $this->ok(["rows" => $result, "count" => $count]);
     }
 
     /**

+ 286 - 0
api-v12/app/Http/Controllers/ChapterContentController.php

@@ -0,0 +1,286 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+
+use App\Models\Sentence;
+use App\Models\Channel;
+use App\Models\PaliText;
+
+use Illuminate\Support\Str;
+use App\Http\Api\MdRender;
+use App\Http\Api\ChannelApi;
+use App\Http\Api\StudioApi;
+
+
+use App\Http\Api\AuthApi;
+use App\Http\Resources\TocResource;
+use App\Services\PaliContentService;
+
+class ChapterContentController extends Controller
+{
+    protected $result = [
+        "uid" => '',
+        "title" => '',
+        "path" => [],
+        "sub_title" => '',
+        "summary" => '',
+        "content" => '',
+        "content_type" => "html",
+        "toc" => [],
+        "status" => 30,
+        "lang" => "",
+        "created_at" => "",
+        "updated_at" => "",
+    ];
+
+    protected $selectCol = [
+        'uid',
+        'book_id',
+        'paragraph',
+        'word_start',
+        "word_end",
+        'channel_uid',
+        'content',
+        'content_type',
+        'editor_uid',
+        'acceptor_uid',
+        'pr_edit_at',
+        'fork_at',
+        'create_time',
+        'modify_time',
+        'created_at',
+        'updated_at',
+    ];
+
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+
+     * @param  \Illuminate\Http\Request  $request
+     * @param string $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show(Request $request, string $id)
+    {
+        /**
+        $user = AuthApi::current($request);
+        if ($user) {
+            $this->userUuid = $user['user_uid'];
+        }
+         */
+        //
+
+
+
+        $channels = [];
+        if ($request->has('channels')) {
+            if (strpos($request->input('channels'), ',') === FALSE) {
+                $_channels = explode('_', $request->input('channels'));
+            } else {
+                $_channels = explode(',', $request->input('channels'));
+            }
+            foreach ($_channels as $key => $channel) {
+                if (Str::isUuid($channel)) {
+                    $channels[] = $channel;
+                }
+            }
+        }
+
+        $mode = $request->input('mode', 'read');
+        if ($mode === 'read') {
+            //阅读模式加载html格式原文
+            $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
+        } else {
+            //翻译模式加载json格式原文
+            $channelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
+        }
+
+        if ($channelId !== false) {
+            $channels[] = $channelId;
+        }
+        #获取channel索引表
+        $tranChannels = [];
+        $channelInfo = Channel::whereIn("uid", $channels)
+            ->select(['uid', 'type', 'lang', 'name'])->get();
+        foreach ($channelInfo as $key => $value) {
+            # code...
+            if ($value->type === "translation") {
+                $tranChannels[] = $value->uid;
+            }
+        }
+        $indexChannel = [];
+        $paliService = app(PaliContentService::class);
+        $indexChannel = $paliService->getChannelIndex($channels);
+        // the end of channel
+
+
+
+        //chapter info
+        $sentId = \explode('-', $id);
+        $chapter = PaliText::where('book', $sentId[0])->where('paragraph', $sentId[1])->first();
+        if (!$chapter) {
+            return $this->error("no data");
+        }
+
+
+        if (empty($chapter->toc)) {
+            $this->result['title'] = "unknown";
+        } else {
+            $this->result['title'] = $chapter->toc;
+            $this->result['sub_title'] = $chapter->toc;
+            $this->result['path'] = json_decode($chapter->path);
+        }
+
+
+        $title = Sentence::select($this->selectCol)
+            ->where('book_id', $sentId[0])
+            ->where('paragraph', $sentId[1])
+            ->whereIn('channel_uid', $tranChannels)
+            ->first();
+        if ($title) {
+            $this->result['title'] = MdRender::render($title->content, [$title->channel_uid]);
+            $mdRender = new MdRender(['format' => 'simple']);
+            $this->result['title_text'] = $mdRender->convert($title->content, [$title->channel_uid]);
+        }
+
+        /**
+         * 获取句子数据
+         * 算法:
+         * 1. 如果标题和下一级第一个标题之间有段落。只输出这些段落和子目录
+         * 2. 如果标题和下一级第一个标题之间没有间隔 且 chapter 长度大于10000个字符 且有子目录,只输出子目录
+         * 3. 如果二者都不是,lazy load
+         */
+        //1. 计算 标题和下一级第一个标题之间 是否有间隔
+
+        $paraFrom = $sentId[1];
+        $paraTo = $sentId[1] + $chapter->chapter_len - 1;
+        $nextChapter =  PaliText::where('book', $sentId[0])
+            ->where('paragraph', ">", $sentId[1])
+            ->where('level', '<', 8)
+            ->orderBy('paragraph')
+            ->value('paragraph');
+        $between = $nextChapter - $sentId[1];
+
+        //查找子目录
+        $chapterLen = $chapter->chapter_len;
+        $toc = PaliText::where('book', $sentId[0])
+            ->whereBetween('paragraph', [$paraFrom + 1, $paraFrom + $chapterLen - 1])
+            ->where('level', '<', 8)
+            ->orderBy('paragraph')
+            ->select(['book', 'paragraph', 'level', 'toc'])
+            ->get();
+        $maxLen = 3000;
+        if ($between > 1) {
+            //有间隔
+            $paraTo = $nextChapter - 1;
+        } else {
+            if ($chapter->chapter_strlen > $maxLen) {
+                if (count($toc) > 0) {
+                    //有子目录只输出标题和目录
+                    $paraTo = $paraFrom;
+                } else {
+                    //没有子目录 全部输出
+                }
+            } else {
+                //章节小。全部输出 不输出子目录
+                $toc = [];
+            }
+        }
+
+        $pFrom = $request->input('from', $paraFrom);
+        $pTo = $request->input('to', $paraTo);
+        //根据句子的长度找到这次应该加载的段落
+
+        $paliText = PaliText::select(['paragraph', 'lenght'])
+            ->where('book', $sentId[0])
+            ->whereBetween('paragraph', [$pFrom, $pTo])
+            ->orderBy('paragraph')
+            ->get();
+        $sumLen = 0;
+        $currTo = $pTo;
+        foreach ($paliText as $para) {
+            $sumLen += $para->lenght;
+            if ($sumLen > $maxLen) {
+                $currTo = $para->paragraph;
+                break;
+            }
+        }
+
+
+        //content
+        $record = Sentence::select($this->selectCol)
+            ->where('book_id', $sentId[0])
+            ->whereBetween('paragraph', [$pFrom, $currTo])
+            ->whereIn('channel_uid', $channels)
+            ->orderBy('paragraph')
+            ->orderBy('word_start')
+            ->get();
+        if (count($record) === 0) {
+            return $this->error("no data");
+        }
+        $this->result['content'] = json_encode($paliService->makeContentObj($record, $mode, $indexChannel), JSON_UNESCAPED_UNICODE);
+        $this->result['content_type'] = 'json';
+        if (!$request->has('from')) {
+            //第一次才显示toc
+            $this->result['toc'] = TocResource::collection($toc);
+        }
+        if ($currTo < $pTo) {
+            $this->result['from'] = $currTo + 1;
+            $this->result['to'] = $pTo;
+            $this->result['paraId'] = $id;
+            $this->result['channels'] = $request->input('channels');
+            $this->result['mode'] = $request->input('mode');
+        }
+
+        return $this->ok($this->result);
+    }
+
+
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}

+ 20 - 13
api-v12/app/Http/Controllers/ChapterController.php

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
 use App\Models\PaliText;
 use Illuminate\Http\Request;
 use App\Http\Resources\ChapterResource;
+use App\Services\PaliTextService;
 
 class ChapterController extends Controller
 {
@@ -16,14 +17,14 @@ class ChapterController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'toc':
-                $chapter = PaliText::where('book', $request->get('book'))
-                    ->where('paragraph', $request->get('para'))
+                $chapter = PaliText::where('book', $request->input('book'))
+                    ->where('paragraph', $request->input('para'))
                     ->first();
-                $start = $request->get('para');
-                $end = $request->get('para') + $chapter->chapter_len - 1;
-                $table = PaliText::where('book', $request->get('book'))
+                $start = $request->input('para');
+                $end = $request->input('para') + $chapter->chapter_len - 1;
+                $table = PaliText::where('book', $request->input('book'))
                     ->whereBetween('paragraph', [$start, $end])
                     ->where('level', '<', 100)
                     ->select(['book', 'paragraph', 'level', 'text', 'chapter_len', 'chapter_strlen', 'parent']);
@@ -33,12 +34,12 @@ class ChapterController extends Controller
         $count = $table->count();
         //处理排序
         $table = $table->orderBy(
-            $request->get("order", 'paragraph'),
-            $request->get("dir", 'asc')
+            $request->input("order", 'paragraph'),
+            $request->input("dir", 'asc')
         );
         //处理分页
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get("limit", 1000));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input("limit", 1000));
         $result = $table->get();
         return $this->ok([
             "rows" => ChapterResource::collection($result),
@@ -60,12 +61,18 @@ class ChapterController extends Controller
     /**
      * Display the specified resource.
      *
-     * @param  \App\Models\PaliText  $paliText
+     * @param  string  $id
      * @return \Illuminate\Http\Response
      */
-    public function show(PaliText $paliText)
+    public function show(string $id)
     {
-        //
+        $para = explode('-', $id);
+        if (count($para) < 2) {
+            return $this->error('参数错误', 400, 400);
+        }
+        $paliTextService = app(PaliTextService::class);
+        $paragraph = $paliTextService->getCurrChapter($para[0], $para[1]);
+        return $this->ok(new ChapterResource($paragraph));
     }
 
     /**

+ 21 - 12
api-v12/app/Http/Controllers/ChapterIOController.php

@@ -16,25 +16,34 @@ class ChapterIOController extends Controller
     public function index(Request $request)
     {
         //
-        $table = ProgressChapter::select(['uid','book','para',
-                                    'channel_id','progress','lang',
-                                    'title','summary','updated_at','created_at']);
-        switch ($request->get('view')) {
+        $table = ProgressChapter::select([
+            'uid',
+            'book',
+            'para',
+            'channel_id',
+            'progress',
+            'lang',
+            'title',
+            'summary',
+            'updated_at',
+            'created_at'
+        ]);
+        switch ($request->input('view')) {
             case 'public':
-                $channels = Channel::where('status',30)->select('uid')->get();
-                $table->whereIn('channel_id',$channels)
-                      ->where('updated_at','>',$request->get('updated_at','2000-1-1'));
-            break;
+                $channels = Channel::where('status', 30)->select('uid')->get();
+                $table->whereIn('channel_id', $channels)
+                    ->where('updated_at', '>', $request->input('updated_at', '2000-1-1'));
+                break;
         }
         $count = $table->count();
         //处理排序
-        $table->orderBy('updated_at','asc');
+        $table->orderBy('updated_at', 'asc');
         //处理分页
-        $table->skip($request->get("offset",0))
-              ->take($request->get("limit",200));
+        $table->skip($request->input("offset", 0))
+            ->take($request->input("limit", 200));
         //获取数据
         $result = $table->get();
-        return $this->ok(["rows"=>$result,"count"=>$count]);
+        return $this->ok(["rows" => $result, "count" => $count]);
     }
 
     /**

+ 15 - 13
api-v12/app/Http/Controllers/ChapterIndexController.php

@@ -16,29 +16,31 @@ class ChapterIndexController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'public':
-                $channels = Channel::where('status',30)->select('uid');
-                $table = ProgressChapter::whereIn('channel_id',$channels);
-            break;
+                $channels = Channel::where('status', 30)->select('uid');
+                $table = ProgressChapter::whereIn('channel_id', $channels);
+                break;
         }
-        if($request->has("updated_at")){
-            $table = $table->where('updated_at','>', $request->get("updated_at"));
+        if ($request->has("updated_at")) {
+            $table = $table->where('updated_at', '>', $request->input("updated_at"));
         }
-        if($request->has("created_at")){
-            $table = $table->where('created_at','>', $request->get("created_at"));
+        if ($request->has("created_at")) {
+            $table = $table->where('created_at', '>', $request->input("created_at"));
         }
         //获取记录总条数
         $count = $table->count();
         //处理排序
-        $table = $table->orderBy($request->get("order",'created_at'),
-                                 $request->get("dir",'desc'));
+        $table = $table->orderBy(
+            $request->input("order", 'created_at'),
+            $request->input("dir", 'desc')
+        );
         //处理分页
-        $table = $table->skip($request->get("offset",0))
-                       ->take($request->get("limit",200));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input("limit", 200));
         //获取数据
         $result = $table->get();
-        return $this->ok(["rows"=>$result,"count"=>$count]);
+        return $this->ok(["rows" => $result, "count" => $count]);
     }
 
     /**

+ 1 - 1
api-v12/app/Http/Controllers/ChatController.php

@@ -26,7 +26,7 @@ class ChatController extends Controller
         $total = $query->count();
 
         $chats = $query->orderBy('updated_at', 'desc')
-            ->paginate($request->get('limit', 20));
+            ->paginate($request->input('limit', 20));
 
         return $this->ok([
             'rows' => ChatResource::collection($chats),

+ 2 - 2
api-v12/app/Http/Controllers/ChatMessageController.php

@@ -20,11 +20,11 @@ class ChatMessageController extends Controller
     public function index(Request $request)
     {
         //
-        $query = ChatMessage::where('chat_id', $request->get('chat'));
+        $query = ChatMessage::where('chat_id', $request->input('chat'));
 
         $total = $query->count();
 
-        $messages = $query->orderBy('id')->paginate($request->get('limit', 500));
+        $messages = $query->orderBy('id')->paginate($request->input('limit', 500));
 
         return $this->ok([
             'data' => ChatMessageResource::collection($messages),

+ 106 - 192
api-v12/app/Http/Controllers/CollectionController.php

@@ -8,284 +8,198 @@ use Illuminate\Support\Str;
 use Illuminate\Support\Facades\Log;
 use App\Http\Api\AuthApi;
 use App\Http\Api\StudioApi;
-use App\Http\Api\ShareApi;
 use App\Http\Resources\CollectionResource;
+use App\Services\CollectionService;
 use Illuminate\Support\Facades\DB;
+use Illuminate\Database\Eloquent\Builder;
 
 class CollectionController extends Controller
 {
-    /**
-     * Display a listing of the resource.
-     *
-     * @return \Illuminate\Http\Response
-     */
+    public function __construct(protected CollectionService $service) {}
+
     public function index(Request $request)
     {
-
-        $result = false;
-        $indexCol = [
-            'uid',
-            'title',
-            'subtitle',
-            'summary',
-            'article_list',
-            'owner',
-            'status',
-            'default_channel',
-            'lang',
-            'updated_at',
-            'created_at'
-        ];
-        switch ($request->get('view')) {
-            case 'studio_list':
-                $indexCol = ['owner'];
-                //TODO ?
-                $table = Collection::select($indexCol)
-                    ->selectRaw('count(*) as count')
-                    ->where('status', 30)
-                    ->groupBy('owner');
-                break;
-            case 'studio':
-                $user = AuthApi::current($request);
-                if (!$user) {
-                    return $this->error(__('auth.failed'));
-                }
-                $studioId = StudioApi::getIdByName($request->get('name'));
-                //判断当前用户是否有指定的studio的权限
-                if ($user['user_uid'] !== $studioId) {
-                    return $this->error(__('auth.failed'));
-                }
-                $table = Collection::select($indexCol);
-                if ($request->get('view2', 'my') === 'my') {
-                    $table = $table->where('owner', $studioId);
-                } else {
-                    //协作
-                    $resList = ShareApi::getResList($studioId, 4);
-                    $resId = [];
-                    foreach ($resList as $res) {
-                        $resId[] = $res['res_id'];
-                    }
-                    $table = $table->whereIn('uid', $resId)->where('owner', '<>', $studioId);
-                }
-                break;
-            case 'public':
-                //全网公开
-                $table = Collection::select($indexCol)->where('status', 30);
-                if ($request->has('studio')) {
-                    $studioId = StudioApi::getIdByName($request->get('studio'));
-                    $table = $table->where('owner', $studioId);
-                }
-                break;
-            default:
-                # code...
-                return $this->error("无法识别的view参数", 200, 200);
-                break;
+        try {
+            $table = match ($request->input('view')) {
+                'studio_list' => $this->service->buildStudioListQuery(),
+                'studio'      => $this->buildStudioIndex($request),
+                'public'      => $this->service->buildPublicQuery(
+                    $request->has('studio')
+                        ? StudioApi::getIdByName($request->input('studio'))
+                        : null
+                ),
+                default       => throw new \InvalidArgumentException('无法识别的view参数'),
+            };
+        } catch (\Illuminate\Auth\AuthenticationException $e) {
+            return $this->error($e->getMessage(), 403, 403);
+        } catch (\InvalidArgumentException $e) {
+            return $this->error($e->getMessage(), 200, 200);
         }
-        if ($request->has("search") && !empty($request->has("search"))) {
-            $table = $table->where('title', 'like', "%" . $request->get("search") . "%");
+
+        if ($request->filled('search')) {
+            $table = $table->where('title', 'like', '%' . $request->input('search') . '%');
         }
+
         $count = $table->count();
-        if ($request->has("order") && $request->has("dir")) {
-            $table = $table->orderBy($request->get("order"), $request->get("dir"));
+
+        if ($request->has('order') && $request->has('dir')) {
+            $table = $table->orderBy($request->input('order'), $request->input('dir'));
         } else {
-            if ($request->get('view') === 'studio_list') {
-                $table = $table->orderBy('count', 'desc');
-            } else {
-                $table = $table->orderBy('updated_at', 'desc');
-            }
+            $orderCol = $request->input('view') === 'studio_list' ? 'count' : 'updated_at';
+            $table = $table->orderBy($orderCol, 'desc');
         }
 
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get("limit", 1000));
+        $result = $table
+            ->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000))
+            ->get();
 
-        $result = $table->get();
-        return $this->ok(["rows" => CollectionResource::collection($result), "count" => $count]);
+        return $this->ok([
+            'rows'  => CollectionResource::collection($result),
+            'count' => $count,
+        ]);
     }
 
-    /**
-     * Display a listing of the resource.
-     *
-     * @return \Illuminate\Http\Response
-     */
-    public function showMyNumber(Request $request)
+    // studio 分支的鉴权逻辑留在 controller
+    private function buildStudioIndex(Request $request): Builder
     {
         $user = AuthApi::current($request);
         if (!$user) {
-            return $this->error(__('auth.failed'));
+            throw new \Illuminate\Auth\AuthenticationException(__('auth.failed'));
         }
-        //判断当前用户是否有指定的studio的权限
-        $studioId = StudioApi::getIdByName($request->get('studio'));
+
+        $studioId = StudioApi::getIdByName($request->input('name'));
         if ($user['user_uid'] !== $studioId) {
-            return $this->error(__('auth.failed'));
+            throw new \Illuminate\Auth\AuthenticationException(__('auth.failed'));
         }
-        //我的
-        $my = Collection::where('owner', $studioId)->count();
-        //协作
-        $resList = ShareApi::getResList($studioId, 4);
-        $resId = [];
-        foreach ($resList as $res) {
-            $resId[] = $res['res_id'];
-        }
-        $collaboration = Collection::whereIn('uid', $resId)->where('owner', '<>', $studioId)->count();
 
-        return $this->ok(['my' => $my, 'collaboration' => $collaboration]);
+        return $this->service->buildStudioQuery(
+            $user['user_uid'],
+            $studioId,
+            $request->input('view2', 'my')
+        );
     }
 
-    public static function UserCanEdit($user_uid, $collection)
-    {
-        if ($collection->owner === $user_uid) {
-            return true;
-        }
-        //查协作
-        $currPower = ShareApi::getResPower($user_uid, $collection->uid);
-        if ($currPower >= 20) {
-            return true;
-        }
-        return false;
-    }
-    public static function UserCanRead($user_uid, $collection)
+    public function showMyNumber(Request $request)
     {
-        if ($collection->owner === $user_uid) {
-            return true;
-        }
-        //查协作
-        $currPower = ShareApi::getResPower($user_uid, $collection->uid);
-        if ($currPower >= 10) {
-            return true;
+        $result = $this->service->getMyNumber($request);
+
+        if (isset($result['error'])) {
+            return $this->error($result['error'], $result['code'], $result['code']);
         }
-        return false;
+
+        return $this->ok($result['data']);
     }
-    /**
-     * Store a newly created resource in storage.
-     *
-     * @param  \Illuminate\Http\Request  $request
-     * @return \Illuminate\Http\Response
-     */
+
     public function store(Request $request)
     {
-        $user = \App\Http\Api\AuthApi::current($request);
+        $user = AuthApi::current($request);
         if (!$user) {
             return $this->error(__('auth.failed'), 401, 401);
         }
-        //判断当前用户是否有指定的studio的权限
-        if ($user['user_uid'] !== \App\Http\Api\StudioApi::getIdByName($request->get('studio'))) {
+
+        if ($user['user_uid'] !== StudioApi::getIdByName($request->input('studio'))) {
             return $this->error(__('auth.failed'), 403, 403);
         }
-        //查询是否重复
-        if (Collection::where('title', $request->get('title'))->where('owner', $user['user_uid'])->exists()) {
+
+        if (Collection::where('title', $request->input('title'))->where('owner', $user['user_uid'])->exists()) {
             return $this->error(__('validation.exists'), 200, 200);
-        } else {
-            $newOne = new Collection;
-            $newOne->id = app('snowflake')->id();
-            $newOne->uid = Str::uuid();
-            $newOne->title = $request->get('title');
-            $newOne->lang = $request->get('lang');
-            $newOne->article_list = "[]";
-            $newOne->owner = $user['user_uid'];
-            $newOne->owner_id = $user['user_id'];
-            $newOne->editor_id = $user['user_id'];
-            $newOne->create_time = time() * 1000;
-            $newOne->modify_time = time() * 1000;
-            $newOne->save();
-            return $this->ok(new CollectionResource($newOne));
         }
+
+        $newOne = new Collection;
+        $newOne->id           = app('snowflake')->id();
+        $newOne->uid          = Str::uuid();
+        $newOne->title        = $request->input('title');
+        $newOne->lang         = $request->input('lang');
+        $newOne->article_list = '[]';
+        $newOne->owner        = $user['user_uid'];
+        $newOne->owner_id     = $user['user_id'];
+        $newOne->editor_id    = $user['user_id'];
+        $newOne->create_time  = time() * 1000;
+        $newOne->modify_time  = time() * 1000;
+        $newOne->save();
+
+        return $this->ok(new CollectionResource($newOne));
     }
 
-    /**
-     * Display the specified resource.
-     * @param  \Illuminate\Http\Request  $request
-     * @param  string  $id
-     * @return \Illuminate\Http\Response
-     */
-    public function show(Request  $request, $id)
+    public function show(Request $request, $id)
     {
-        $result  = Collection::where('uid', $id)->first();
+        $result = Collection::where('uid', $id)->first();
         if (!$result) {
             Log::warning("没有查询到数据 id={$id}");
             return $this->error("没有查询到数据 id={$id}");
         }
+
         if ($result->status < 30) {
-            //私有文章,判断权限
             Log::info('私有文章,判断权限' . $id);
-            $user = \App\Http\Api\AuthApi::current($request);
+            $user = AuthApi::current($request);
             if (!$user) {
                 Log::warning('未登录');
                 return $this->error(__('auth.failed'), 403, 403);
             }
-            //判断当前用户是否有指定的studio的权限
+
             if ($user['user_uid'] !== $result->owner) {
-                Log::info($user["user_uid"] . '私有文章,判断权限' . $id);
-                //非所有者
-                if (CollectionController::UserCanRead($user['user_uid'], $result) === false) {
-                    Log::warning($user["user_uid"] . '没有读取权限');
+                Log::info($user['user_uid'] . '私有文章,判断权限' . $id);
+                if (!$this->service->userCanRead($user['user_uid'], $result)) {
+                    Log::warning($user['user_uid'] . '没有读取权限');
                     return $this->error(__('auth.failed'), 403, 403);
                 }
             }
         }
+
         $result->fullArticleList = true;
         return $this->ok(new CollectionResource($result));
     }
 
-    /**
-     * Update the specified resource in storage.
-     *
-     * @param  \Illuminate\Http\Request  $request
-     * @param  string  $id
-     * @return \Illuminate\Http\Response
-     */
     public function update(Request $request, string $id)
     {
-        //
-        $collection  = Collection::find($id);
+        $collection = Collection::find($id);
         if (!$collection) {
-            return $this->error("no recorder");
+            return $this->error('no recorder');
         }
-        //鉴权
+
         $user = AuthApi::current($request);
         if (!$user) {
             return $this->error(__('auth.failed'), 401, 401);
         }
-        if (!CollectionController::UserCanEdit($user["user_uid"], $collection)) {
+
+        if (!$this->service->userCanEdit($user['user_uid'], $collection)) {
             return $this->error(__('auth.failed'), 403, 403);
         }
-        $collection->title = $request->get('title');
-        $collection->subtitle = $request->get('subtitle');
-        $collection->summary = $request->get('summary');
+
+        $collection->title           = $request->input('title');
+        $collection->subtitle        = $request->input('subtitle');
+        $collection->summary         = $request->input('summary');
+        $collection->lang            = $request->input('lang');
+        $collection->status          = $request->input('status');
+        $collection->default_channel = $request->input('default_channel');
+        $collection->modify_time     = time() * 1000;
+
         if ($request->has('aritcle_list')) {
-            $collection->article_list = \json_encode($request->get('aritcle_list'));
+            $collection->article_list = json_encode($request->input('aritcle_list'));
         }
-        $collection->lang = $request->get('lang');
-        $collection->status = $request->get('status');
-        $collection->default_channel = $request->get('default_channel');
-        $collection->modify_time = time() * 1000;
+
         $collection->save();
         return $this->ok(new CollectionResource($collection));
     }
 
-    /**
-     * Remove the specified resource from storage.
-     * @param  \Illuminate\Http\Request  $request
-     * @param  string  $id
-     * @return \Illuminate\Http\Response
-     */
     public function destroy(Request $request, string $id)
     {
-        //
         $user = AuthApi::current($request);
         if (!$user) {
             return $this->error(__('auth.failed'));
         }
-        //判断当前用户是否有指定的studio的权限
+
         $collection = Collection::find($id);
         if ($user['user_uid'] !== $collection['owner']) {
             return $this->error(__('auth.failed'));
         }
-        $delete = 0;
-        DB::transaction(function () use ($collection, $delete) {
-            //TODO 删除文集中的文章
-            $delete = $collection->delete();
+
+        DB::transaction(function () use ($collection) {
+            // TODO: 删除文集中的文章
+            $collection->delete();
         });
 
-        return $this->ok($delete);
+        return $this->ok(true);
     }
 }

+ 5 - 5
api-v12/app/Http/Controllers/CommandController.php

@@ -29,13 +29,13 @@ class CommandController extends Controller
     {
         //
         $user = AuthApi::current($request);
-        if(!$user || $user['user_uid'] !== 'ba5463f3-72d1-4410-858e-eadd10884713'){
-            return $this->error(__('auth.failed'),403,403);
+        if (!$user || $user['user_uid'] !== 'ba5463f3-72d1-4410-858e-eadd10884713') {
+            return $this->error(__('auth.failed'), 403, 403);
         }
 
-        Mq::publish('task',[
-            'name'=>$request->get('name'),
-            'param'=>$request->get('param'),
+        Mq::publish('task', [
+            'name' => $request->input('name'),
+            'param' => $request->input('param'),
         ]);
         return $this->ok('ok');
     }

+ 4 - 4
api-v12/app/Http/Controllers/CompoundController.php

@@ -21,7 +21,7 @@ class CompoundController extends Controller
         if (!$dict_id) {
             return $this->error('没有找到 robot_compound 字典');
         }
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'only-word':
                 $result = UserDict::where('dict_id', $dict_id)
                     ->groupBy('word')->select('word')->get();
@@ -55,9 +55,9 @@ class CompoundController extends Controller
         }
         //删除旧数据
         $del = UserDict::where('dict_id', $dict_id)
-            ->whereIn('word', $request->get('index'))
+            ->whereIn('word', $request->input('index'))
             ->delete();
-        foreach ($request->get('words') as $key => $word) {
+        foreach ($request->input('words') as $key => $word) {
             $new = new UserDict;
             $new->id = app('snowflake')->id();
             $new->word = $word['word'];
@@ -75,7 +75,7 @@ class CompoundController extends Controller
             $new->flag = 0; //标记为维护状态
             $new->save();
         }
-        return $this->ok(count($request->get('words')));
+        return $this->ok(count($request->input('words')));
     }
 
     /**

+ 256 - 31
api-v12/app/Http/Controllers/CorpusController.php

@@ -7,13 +7,11 @@ use Carbon\Carbon;
 use App\Models\Sentence;
 use App\Models\Channel;
 use App\Models\PaliText;
-use App\Models\WbwTemplate;
 use App\Models\WbwBlock;
 use App\Models\Wbw;
 use App\Models\Discussion;
 use App\Models\PaliSentence;
 use App\Models\SentSimIndex;
-use App\Models\CustomBookSentence;
 use App\Models\CustomBook;
 
 use Illuminate\Support\Str;
@@ -80,7 +78,7 @@ class CorpusController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'para':
                 return $this->showPara($request);
                 break;
@@ -173,7 +171,7 @@ class CorpusController extends Controller
         if ($user) {
             $this->userUuid = $user['user_uid'];
         }
-        $channels = \explode('_', $request->get('channels'));
+        $channels = \explode('_', $request->input('channels'));
 
         $this->result['uid'] = "";
         $this->result['title'] = "";
@@ -181,7 +179,7 @@ class CorpusController extends Controller
         $this->result['summary'] = "";
         $this->result['lang'] = "";
         $this->result['status'] = 30;
-        $this->result['content'] = $this->getSentTpl($id, $channels, $request->get('mode', 'edit'));
+        $this->result['content'] = $this->getSentTpl($id, $channels, $request->input('mode', 'edit'));
         return $this->ok($this->result);
     }
     /**
@@ -259,7 +257,7 @@ class CorpusController extends Controller
     public function showPara(Request $request)
     {
         if ($request->has('debug')) {
-            $this->debug = explode(',', $request->get('debug'));
+            $this->debug = explode(',', $request->input('debug'));
         }
         $user = AuthApi::current($request);
         if ($user) {
@@ -267,7 +265,7 @@ class CorpusController extends Controller
         }
         //
         $channels = [];
-        if ($request->get('mode') === 'edit') {
+        if ($request->input('mode') === 'edit') {
             //翻译模式加载json格式原文
             $channels[] = ChannelApi::getSysChannel('_System_Wbw_VRI_');
         } else {
@@ -276,19 +274,19 @@ class CorpusController extends Controller
         }
 
         if ($request->has('channels')) {
-            if (strpos($request->get('channels'), ',') === FALSE) {
-                $getChannel = explode('_', $request->get('channels'));
+            if (strpos($request->input('channels'), ',') === FALSE) {
+                $getChannel = explode('_', $request->input('channels'));
             } else {
-                $getChannel = explode(',', $request->get('channels'));
+                $getChannel = explode(',', $request->input('channels'));
             }
             $channels = array_merge($channels, $getChannel);
         }
-        $para = explode(",", $request->get('par'));
+        $para = explode(",", $request->input('par'));
 
         //段落所在章节
-        $parent = PaliText::where('book', $request->get('book'))
+        $parent = PaliText::where('book', $request->input('book'))
             ->where('paragraph', $para[0])->first();
-        $chapter = PaliText::where('book', $request->get('book'))
+        $chapter = PaliText::where('book', $request->input('book'))
             ->where('paragraph', $parent->parent)->first();
         if ($chapter) {
             if (empty($chapter->toc)) {
@@ -342,7 +340,7 @@ class CorpusController extends Controller
          * 获取句子数据
          */
         $record = Sentence::select($this->selectCol)
-            ->where('book_id', $request->get('book'))
+            ->where('book_id', $request->input('book'))
             ->whereIn('paragraph', $para)
             ->whereIn('channel_uid', $channels)
             ->orderBy('paragraph')
@@ -351,7 +349,7 @@ class CorpusController extends Controller
         if (count($record) === 0) {
             $this->result['content'] = "<span>No Data</span>";
         } else {
-            $this->result['content'] = $this->makeContent($record, $request->get('mode', 'read'), $indexChannel, $indexedHeading, false, true);
+            $this->result['content'] = $this->makeContent($record, $request->input('mode', 'read'), $indexChannel, $indexedHeading, false, true);
         }
 
         return $this->ok($this->result);
@@ -366,7 +364,7 @@ class CorpusController extends Controller
     public function showChapter(Request $request, string $id)
     {
         if ($request->has('debug')) {
-            $this->debug = explode(',', $request->get('debug'));
+            $this->debug = explode(',', $request->input('debug'));
         }
         $user = AuthApi::current($request);
         if ($user) {
@@ -376,10 +374,10 @@ class CorpusController extends Controller
         $sentId = \explode('-', $id);
         $channels = [];
         if ($request->has('channels')) {
-            if (strpos($request->get('channels'), ',') === FALSE) {
-                $_channels = explode('_', $request->get('channels'));
+            if (strpos($request->input('channels'), ',') === FALSE) {
+                $_channels = explode('_', $request->input('channels'));
             } else {
-                $_channels = explode(',', $request->get('channels'));
+                $_channels = explode(',', $request->input('channels'));
             }
             foreach ($_channels as $key => $channel) {
                 if (Str::isUuid($channel)) {
@@ -388,7 +386,7 @@ class CorpusController extends Controller
             }
         }
 
-        $mode = $request->get('mode', 'read');
+        $mode = $request->input('mode', 'read');
         if ($mode === 'read') {
             //阅读模式加载html格式原文
             $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
@@ -504,8 +502,8 @@ class CorpusController extends Controller
             }
         }
 
-        $pFrom = $request->get('from', $paraFrom);
-        $pTo = $request->get('to', $paraTo);
+        $pFrom = $request->input('from', $paraFrom);
+        $pTo = $request->input('to', $paraTo);
         //根据句子的长度找到这次应该加载的段落
 
         $paliText = PaliText::select(['paragraph', 'lenght'])
@@ -532,7 +530,8 @@ class CorpusController extends Controller
         if (count($record) === 0) {
             return $this->error("no data");
         }
-        $this->result['content'] = $this->makeContent($record, $mode, $indexChannel, $indexedHeading, false, true);
+        $this->result['content'] = json_encode($this->makeContent($record, $mode, $indexChannel), JSON_UNESCAPED_UNICODE);
+        $this->result['content_type'] = 'json';
         if (!$request->has('from')) {
             //第一次才显示toc
             $this->result['toc'] = TocResource::collection($toc);
@@ -541,8 +540,8 @@ class CorpusController extends Controller
             $this->result['from'] = $currTo + 1;
             $this->result['to'] = $pTo;
             $this->result['paraId'] = $id;
-            $this->result['channels'] = $request->get('channels');
-            $this->result['mode'] = $request->get('mode');
+            $this->result['channels'] = $request->input('channels');
+            $this->result['mode'] = $request->input('mode');
         }
 
         return $this->ok($this->result);
@@ -580,8 +579,6 @@ class CorpusController extends Controller
     private function makeContent($record, $mode, $indexChannel, $indexedHeading = [], $onlyProps = false, $paraMark = false, $format = 'react')
     {
         $content = [];
-        $lastSent = "0-0";
-        $sentCount = 0;
         $sent = [];
         $sent["origin"] = [];
         $sent["translation"] = [];
@@ -589,7 +586,7 @@ class CorpusController extends Controller
 
         //获取句子编号列表
         $sentList = [];
-        foreach ($record as $key => $value) {
+        foreach ($record as  $value) {
             $currSentId = "{$value->book_id}-{$value->paragraph}-{$value->word_start}-{$value->word_end}";
             $sentList[$currSentId] = [$value->book_id, $value->paragraph, $value->word_start, $value->word_end];
             $value->sid = "{$currSentId}_{$value->channel_uid}";
@@ -606,7 +603,6 @@ class CorpusController extends Controller
             if ($currPara !== $para) {
                 $currPara = $para;
                 //输出段落标记
-
                 if ($paraMark) {
                     $sentInPara = array();
                     foreach ($sentList as $sentId => $sentParam) {
@@ -812,6 +808,234 @@ class CorpusController extends Controller
         $output = \implode("", $content);
         return "<div>{$output}</div>";
     }
+
+    /**
+     * 根据句子库数据生成以段落为单位的文章内容
+     * $record 句子数据
+     * $mode read | edit | wbw
+     * $indexChannel channel索引
+     * $indexedHeading 标题索引 用于给段落加标题标签 <h1> ect.
+     */
+    private function makeContentObj($record, $mode, $indexChannel, $format = 'react')
+    {
+        $content = [];
+
+
+        //获取句子编号列表
+        $paraIndex = [];
+        foreach ($record as  $value) {
+            $currSentId = "{$value->book_id}-{$value->paragraph}-{$value->word_start}-{$value->word_end}";
+            $value->sid = "{$currSentId}_{$value->channel_uid}";
+
+            $currParaId = "{$value->book_id}-{$value->paragraph}";
+            if (!isset($paraIndex[$currParaId])) {
+                $paraIndex[$currParaId] = [];
+            }
+            $paraIndex[$currParaId][] = $value;
+        }
+        $channelsId = array();
+        foreach ($indexChannel as $channelId => $info) {
+            $channelsId[] = $channelId;
+        }
+        array_pop($channelsId);
+        //遍历列表查找每个句子的所有channel的数据,并填充
+        $paragraphs = [];
+        foreach ($paraIndex as $currParaId => $sentData) {
+            $arrParaId = explode('-', $currParaId);
+            $sentIndex = [];
+            foreach ($sentData as  $sent) {
+                $currSentId = "{$sent->book_id}-{$sent->paragraph}-{$sent->word_start}-{$sent->word_end}";
+                $sentIndex[$currSentId] = [$sent->book_id, $sent->paragraph, $sent->word_start, $sent->word_end];
+            }
+            $sentInPara = array_values($sentIndex);
+            $paraProps = [
+                'book' => $arrParaId[0],
+                'para' => $arrParaId[1],
+                'channels' => $channelsId,
+                'sentences' => $sentInPara,
+                'mode' => $mode,
+                'children' => [],
+            ];
+            //建立段落里面的句子列表
+            foreach ($sentIndex as $ids => $arrSentId) {
+                $sentNode = $this->newSent($arrSentId[0], $arrSentId[1], $arrSentId[2], $arrSentId[3]);
+                foreach ($indexChannel as $channelId => $info) {
+                    # code...
+                    $sid = "{$ids}_{$channelId}";
+                    if (isset($info->studio)) {
+                        $studioInfo = $info->studio;
+                    } else {
+                        $studioInfo = null;
+                    }
+                    $newSent = [
+                        "content" => "",
+                        "html" => "",
+                        "book" => $arrSentId[0],
+                        "para" => $arrSentId[1],
+                        "wordStart" => $arrSentId[2],
+                        "wordEnd" => $arrSentId[3],
+                        "channel" => [
+                            "name" => $info->name,
+                            "type" => $info->type,
+                            "id" => $info->uid,
+                            'lang' => $info->lang,
+                        ],
+                        "studio" => $studioInfo,
+                        "updateAt" => "",
+                        "suggestionCount" => SuggestionApi::getCountBySent($arrSentId[0], $arrSentId[1], $arrSentId[2], $arrSentId[3], $channelId),
+                    ];
+
+                    $row = Arr::first($sentData, function ($value, $key) use ($sid) {
+                        return $value->sid === $sid;
+                    });
+                    if ($row) {
+                        $newSent['id'] = $row->uid;
+                        $newSent['content'] = $row->content;
+                        $newSent['contentType'] = $row->content_type;
+                        $newSent['html'] = '';
+                        $newSent["editor"] = UserApi::getByUuid($row->editor_uid);
+                        /**
+                         * TODO 刷库改数据
+                         * 旧版api没有更新updated_at所以造成旧版的数据updated_at数据比modify_time 要晚
+                         */
+                        $newSent['forkAt'] =  $row->fork_at; //
+                        $newSent['updateAt'] =  $row->updated_at; //
+                        $newSent['updateAt'] = date("Y-m-d H:i:s.", $row->modify_time / 1000) . ($row->modify_time % 1000) . " UTC";
+
+                        $newSent['createdAt'] = $row->created_at;
+                        if ($mode !== "read") {
+                            if (isset($row->acceptor_uid) && !empty($row->acceptor_uid)) {
+                                $newSent["acceptor"] = UserApi::getByUuid($row->acceptor_uid);
+                                $newSent["prEditAt"] = $row->pr_edit_at;
+                            }
+                        }
+                        switch ($info->type) {
+                            case 'wbw':
+                            case 'original':
+                                //
+                                // 在编辑模式下。
+                                // 如果是原文,查看是否有逐词解析数据,
+                                // 有的话优先显示。
+                                // 阅读模式直接显示html原文
+                                // 传过来的数据一定有一个原文channel
+                                //
+                                if ($mode === "read") {
+                                    $newSent['content'] = "";
+                                    $newSent['html'] = MdRender::render(
+                                        $row->content,
+                                        [$row->channel_uid],
+                                        null,
+                                        $mode,
+                                        "translation",
+                                        $row->content_type,
+                                        $format
+                                    );
+                                } else {
+                                    if ($row->content_type === 'json') {
+                                        $newSent['channel']['type'] = "wbw";
+                                        if (isset($this->wbwChannels[0])) {
+                                            $newSent['channel']['name'] = $indexChannel[$this->wbwChannels[0]]->name;
+                                            $newSent['channel']['lang'] = $indexChannel[$this->wbwChannels[0]]->lang;
+                                            $newSent['channel']['id'] = $this->wbwChannels[0];
+                                            //存在一个translation channel
+                                            //尝试查找逐词解析数据。找到,替换现有数据
+                                            $wbwData = $this->getWbw(
+                                                $arrSentId[0],
+                                                $arrSentId[1],
+                                                $arrSentId[2],
+                                                $arrSentId[3],
+                                                $this->wbwChannels[0]
+                                            );
+                                            if ($wbwData) {
+                                                $newSent['content'] = $wbwData;
+                                                $newSent['contentType'] = 'json';
+                                                $newSent['html'] = "";
+                                                $newSent['studio'] = $indexChannel[$this->wbwChannels[0]]->studio;
+                                            }
+                                        }
+                                    } else {
+                                        $newSent['content'] = $row->content;
+                                        $newSent['html'] = MdRender::render(
+                                            $row->content,
+                                            [$row->channel_uid],
+                                            null,
+                                            $mode,
+                                            "translation",
+                                            $row->content_type,
+                                            $format
+                                        );
+                                    }
+                                }
+
+                                break;
+                            case 'nissaya':
+                                $newSent['html'] = Cache::remember(
+                                    "/sent/{$channelId}/{$ids}/{$format}",
+                                    config('mint.cache.expire'),
+                                    function () use ($row, $mode, $format) {
+                                        if ($row->content_type === 'markdown') {
+                                            return MdRender::render(
+                                                $row->content,
+                                                [$row->channel_uid],
+                                                null,
+                                                $mode,
+                                                "nissaya",
+                                                $row->content_type,
+                                                $format
+                                            );
+                                        } else {
+                                            return null;
+                                        }
+                                    }
+                                );
+                                break;
+                            case 'commentary':
+                                $options = [
+                                    'debug' => $this->debug,
+                                    'format' => $format,
+                                    'mode' => $mode,
+                                    'channelType' => 'translation',
+                                    'contentType' => $row->content_type,
+                                ];
+                                $mdRender = new MdRender($options);
+                                $newSent['html'] = $mdRender->convert($row->content, $channelsId);
+                                break;
+                            default:
+                                $options = [
+                                    'debug' => $this->debug,
+                                    'format' => $format,
+                                    'mode' => $mode,
+                                    'channelType' => 'translation',
+                                    'contentType' => $row->content_type,
+                                ];
+                                $mdRender = new MdRender($options);
+                                $newSent['html'] = $mdRender->convert($row->content, [$row->channel_uid]);
+                                //Log::debug('md render', ['content' => $row->content, 'options' => $options, 'render' => $newSent['html']]);
+                                break;
+                        }
+                    } else {
+                        Log::warning('no sentence record');
+                    }
+                    switch ($info->type) {
+                        case 'wbw':
+                        case 'original':
+                            array_push($sentNode["origin"], $newSent);
+                            break;
+                        case 'commentary':
+                            array_push($sentNode["commentaries"], $newSent);
+                            break;
+                        default:
+                            array_push($sentNode["translation"], $newSent);
+                            break;
+                    }
+                }
+                $paraProps['children'][] = $sentNode;
+            }
+            $paragraphs[] = $paraProps;
+        }
+        return $paragraphs;
+    }
+
     public function getWbw($book, $para, $start, $end, $channel)
     {
         /**
@@ -842,11 +1066,12 @@ class CorpusController extends Controller
             $xmlString = "<root>" . $wbw . "</root>";
             try {
                 $xmlWord = simplexml_load_string($xmlString);
+                $wordsList = $xmlWord->xpath('//word');
             } catch (\Exception $e) {
                 Log::error('corpus', ['error' => $e]);
-                continue;
+                return false;
             }
-            $wordsList = $xmlWord->xpath('//word');
+
             foreach ($wordsList as $word) {
                 $case = \str_replace(['#', '.'], ['$', ''], $word->case->__toString());
                 $case = \str_replace('$$', '$', $case);

+ 119 - 96
api-v12/app/Http/Controllers/CourseController.php

@@ -21,14 +21,26 @@ class CourseController extends Controller
     public function index(Request $request)
     {
         //
-		$result=false;
-		$indexCol = ['id','title','subtitle',
-                     'cover','content','content_type',
-                     'teacher','start_at','end_at',
-                     'sign_up_start_at','sign_up_end_at',
-                     'join','publicity','number',
-                     'updated_at','created_at'];
-		switch ($request->get('view')) {
+        $result = false;
+        $indexCol = [
+            'id',
+            'title',
+            'subtitle',
+            'cover',
+            'content',
+            'content_type',
+            'teacher',
+            'start_at',
+            'end_at',
+            'sign_up_start_at',
+            'sign_up_end_at',
+            'join',
+            'publicity',
+            'number',
+            'updated_at',
+            'created_at'
+        ];
+        switch ($request->input('view')) {
             case 'new':
                 //最新公开课程列表
                 $table = Course::where('publicity', 30);
@@ -41,7 +53,7 @@ class CourseController extends Controller
                  * 2. 课程开始时间比现在时间晚
                  */
                 $table = Course::where('publicity', 30)
-                            ->whereDate('start_at',">",date("Y-m-d",strtotime("today")));
+                    ->whereDate('start_at', ">", date("Y-m-d", strtotime("today")));
                 break;
             case 'close':
                 /**
@@ -51,32 +63,32 @@ class CourseController extends Controller
                  * 2. 课程开始时间比现在时间早
                  */
                 $table = Course::where('publicity', 30)
-                        ->whereDate('start_at',"<=",date("Y-m-d",strtotime("today")));
+                    ->whereDate('start_at', "<=", date("Y-m-d", strtotime("today")));
                 break;
             case 'create':
-	            # 获取 studio 建立的所有 course
+                # 获取 studio 建立的所有 course
                 $user = AuthApi::current($request);
-                if(!$user){
+                if (!$user) {
                     return $this->error(__('auth.failed'));
                 }
                 //判断当前用户是否有指定的studio的权限
-                if($user['user_uid'] !== StudioApi::getIdByName($request->get('studio'))){
+                if ($user['user_uid'] !== StudioApi::getIdByName($request->input('studio'))) {
                     return $this->error(__('auth.failed'));
                 }
 
                 $table = Course::where('studio_id', $user["user_uid"]);
-				break;
+                break;
             case 'study':
                 $user = AuthApi::current($request);
-                if(!$user){
+                if (!$user) {
                     return $this->error(__('auth.failed'));
                 }
                 //我学习的课程
-                $course = CourseMember::where('user_id',$user["user_uid"])
-                                      ->where('role','student')
-                                      ->where('is_current',true)
-                                      ->select('course_id')
-                                      ->get();
+                $course = CourseMember::where('user_id', $user["user_uid"])
+                    ->where('role', 'student')
+                    ->where('is_current', true)
+                    ->select('course_id')
+                    ->get();
                 $courseId = [];
                 foreach ($course as $key => $value) {
                     # code...
@@ -87,14 +99,14 @@ class CourseController extends Controller
             case 'teach':
                 //我任教的课程
                 $user = AuthApi::current($request);
-                if(!$user){
+                if (!$user) {
                     return $this->error(__('auth.failed'));
                 }
-                $course = CourseMember::where('user_id',$user["user_uid"])
-                                    ->whereIn('role',['assistant','manager','teacher'])
-                                      ->where('is_current',true)
-                                      ->select('course_id')
-                                    ->get();
+                $course = CourseMember::where('user_id', $user["user_uid"])
+                    ->whereIn('role', ['assistant', 'manager', 'teacher'])
+                    ->where('is_current', true)
+                    ->select('course_id')
+                    ->get();
                 $courseId = [];
                 foreach ($course as $key => $value) {
                     # code...
@@ -104,44 +116,46 @@ class CourseController extends Controller
                 break;
         }
         $table = $table->select($indexCol);
-        if($request->has('search')){
-            $table = $table->where('title', 'like', $request->get('search')."%");
+        if ($request->has('search')) {
+            $table = $table->where('title', 'like', $request->input('search') . "%");
         }
         $count = $table->count();
-        $table = $table->orderBy($request->get('order','updated_at'),
-                                 $request->get('dir','desc'));
+        $table = $table->orderBy(
+            $request->input('order', 'updated_at'),
+            $request->input('dir', 'desc')
+        );
 
-        $table = $table->skip($request->get('offset',0))
-                       ->take($request->get('limit',1000));
+        $table = $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
 
-		return $this->ok(["rows"=>CourseResource::collection($result),"count"=>$count]);
-
+        return $this->ok(["rows" => CourseResource::collection($result), "count" => $count]);
     }
     /**
      * Display a listing of the resource.
      *
      * @return \Illuminate\Http\Response
      */
-    public function showMyCourseNumber(Request $request){
+    public function showMyCourseNumber(Request $request)
+    {
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         //我建立的课程
         $create = Course::where('studio_id', $user["user_uid"])->count();
         //我学习的课程
-        $study = CourseMember::where('user_id',$user["user_uid"])
-                            ->where('role','student')
-                            ->where('is_current',true)
-                            ->count();
+        $study = CourseMember::where('user_id', $user["user_uid"])
+            ->where('role', 'student')
+            ->where('is_current', true)
+            ->count();
         //我任教的课程
-        $teach = CourseMember::where('user_id',$user["user_uid"])
-                            ->where('is_current',true)
-                            ->whereIn('role',['assistant','manager','teacher'])
-                            ->count();
-        return $this->ok(['create'=>$create,'teach'=>$teach,'study'=>$study]);
+        $teach = CourseMember::where('user_id', $user["user_uid"])
+            ->where('is_current', true)
+            ->whereIn('role', ['assistant', 'manager', 'teacher'])
+            ->count();
+        return $this->ok(['create' => $create, 'teach' => $teach, 'study' => $study]);
     }
     /**
      * Store a newly created resource in storage.
@@ -153,29 +167,30 @@ class CourseController extends Controller
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        $studio_id = StudioApi::getIdByName($request->get('studio'));
-        if($user['user_uid'] !== $studio_id){
+        $studio_id = StudioApi::getIdByName($request->input('studio'));
+        if ($user['user_uid'] !== $studio_id) {
             return $this->error(__('auth.failed'));
         }
         //查询是否重复
-        if(Course::where('title',$request->get('title'))
-                ->where('studio_id',$user['user_uid'])
-                ->exists()){
-            return $this->error(__('validation.exists',['name']));
+        if (Course::where('title', $request->input('title'))
+            ->where('studio_id', $user['user_uid'])
+            ->exists()
+        ) {
+            return $this->error(__('validation.exists', ['name']));
         }
 
         try {
             $course = new Course;
-            DB::transaction(function () use($course,$request,$studio_id,$user) {
+            DB::transaction(function () use ($course, $request, $studio_id, $user) {
                 $saveCourse = false;
                 $saveCourseMember = false;
 
                 $course->id = Str::uuid();
-                $course->title = $request->get('title');
+                $course->title = $request->input('title');
                 $course->studio_id = $studio_id;
                 $saveCourse = $course->save();
 
@@ -186,9 +201,8 @@ class CourseController extends Controller
                 $newMember->role = 'owner';
                 $saveCourseMember = $newMember->save();
             });
-
-        } catch(\Exception $e) {
-            return $this->error('course create fail',500,500);
+        } catch (\Exception $e) {
+            return $this->error('course create fail', 500, 500);
         }
 
         return $this->ok(new CourseResource($course));
@@ -204,17 +218,17 @@ class CourseController extends Controller
     {
         //
         return $this->ok(new CourseResource($course));
-
     }
 
-    private function userCanManage($courseId,$userUid){
-                    //判断是否是manager
-        $role = CourseMember::where('course_id',$courseId)
-                    ->where('is_current',true)
-                    ->where('user_id',$userUid)
-                    ->value('role');
-        $manager = ['owner','teacher','manager'];
-        if(in_array($role,$manager)){
+    private function userCanManage($courseId, $userUid)
+    {
+        //判断是否是manager
+        $role = CourseMember::where('course_id', $courseId)
+            ->where('is_current', true)
+            ->where('user_id', $userUid)
+            ->value('role');
+        $manager = ['owner', 'teacher', 'manager'];
+        if (in_array($role, $manager)) {
             return true;
         }
         return false;
@@ -231,39 +245,48 @@ class CourseController extends Controller
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        $canManage = $this->userCanManage($course->id,$user['user_uid']);
-        if(!$canManage){
-            return $this->error(__('auth.failed'),403,403);
+        $canManage = $this->userCanManage($course->id, $user['user_uid']);
+        if (!$canManage) {
+            return $this->error(__('auth.failed'), 403, 403);
         }
 
         //查询标题是否重复
-        if(Course::where('title',$request->get('title'))
-                ->where('studio_id',$user['user_uid'])
-                ->exists()){
-            if($course->title !== $request->get('title')){
-                return $this->error(__('validation.exists',['name']));
+        if (Course::where('title', $request->input('title'))
+            ->where('studio_id', $user['user_uid'])
+            ->exists()
+        ) {
+            if ($course->title !== $request->input('title')) {
+                return $this->error(__('validation.exists', ['name']));
             }
         }
-        $course->title = $request->get('title');
-        $course->subtitle = $request->get('subtitle');
-        $course->summary = $request->get('summary');
-        $course->number = $request->get('number',0);
-        if($request->has('cover')) {$course->cover = $request->get('cover');}
-        $course->content = $request->get('content');
-        $course->sign_up_message = $request->get('sign_up_message');
-        if($request->has('teacher_id')) {$course->teacher = $request->get('teacher_id');}
-        if($request->has('anthology_id')) {$course->anthology_id = $request->get('anthology_id');}
-        $course->channel_id = $request->get('channel_id');
-        if($request->has('publicity')) {$course->publicity = $request->get('publicity');}
-        $course->start_at = $request->get('start_at');
-        $course->end_at = $request->get('end_at');
-        $course->sign_up_start_at = $request->get('sign_up_start_at');
-        $course->sign_up_end_at = $request->get('sign_up_end_at');
-        $course->join = $request->get('join');
+        $course->title = $request->input('title');
+        $course->subtitle = $request->input('subtitle');
+        $course->summary = $request->input('summary');
+        $course->number = $request->input('number', 0);
+        if ($request->has('cover')) {
+            $course->cover = $request->input('cover');
+        }
+        $course->content = $request->input('content');
+        $course->sign_up_message = $request->input('sign_up_message');
+        if ($request->has('teacher_id')) {
+            $course->teacher = $request->input('teacher_id');
+        }
+        if ($request->has('anthology_id')) {
+            $course->anthology_id = $request->input('anthology_id');
+        }
+        $course->channel_id = $request->input('channel_id');
+        if ($request->has('publicity')) {
+            $course->publicity = $request->input('publicity');
+        }
+        $course->start_at = $request->input('start_at');
+        $course->end_at = $request->input('end_at');
+        $course->sign_up_start_at = $request->input('sign_up_start_at');
+        $course->sign_up_end_at = $request->input('sign_up_end_at');
+        $course->join = $request->input('join');
         $course->save();
         return $this->ok($course);
     }
@@ -274,21 +297,21 @@ class CourseController extends Controller
      * @param  \App\Models\Course  $course
      * @return \Illuminate\Http\Response
      */
-    public function destroy(Request $request,Course $course)
+    public function destroy(Request $request, Course $course)
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        if($user['user_uid'] !== $course->studio_id){
+        if ($user['user_uid'] !== $course->studio_id) {
             return $this->error(__('auth.failed'));
         }
         $delete = 0;
-        DB::transaction(function() use($delete,$course){
+        DB::transaction(function () use ($delete, $course) {
             //删除group member
-            $memberDelete = CourseMember::where('course_id',$course->id)->delete();
+            $memberDelete = CourseMember::where('course_id', $course->id)->delete();
             $delete = $course->delete();
         });
 

+ 120 - 112
api-v12/app/Http/Controllers/CourseMemberController.php

@@ -25,72 +25,80 @@ class CourseMemberController extends Controller
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error(__('auth.failed',[403],403));
+        if (!$user) {
+            return $this->error(__('auth.failed', [403], 403));
         }
         //判断当前用户是否有指定的 course 的权限
-        $role = CourseMember::where('course_id', $request->get('id',$request->get('course')))
-                            ->where('user_id',$user['user_uid'])
-                            ->value('role');
-        if(empty($role)){
-            return $this->error(__('auth.failed',[403],403));
+        $role = CourseMember::where('course_id', $request->input('id', $request->input('course')))
+            ->where('user_id', $user['user_uid'])
+            ->value('role');
+        if (empty($role)) {
+            return $this->error(__('auth.failed', [403], 403));
         }
 
-        $result=false;
-		$indexCol = ['id','user_id','course_id',
-                    'channel_id','role','editor_uid',
-                    'updated_at','created_at'];
-		switch ($request->get('view')) {
+        $result = false;
+        $indexCol = [
+            'id',
+            'user_id',
+            'course_id',
+            'channel_id',
+            'role',
+            'editor_uid',
+            'updated_at',
+            'created_at'
+        ];
+        switch ($request->input('view')) {
             case 'course':
-	            # 获取 course 内所有 成员
-                $table = CourseMember::where('course_id', $request->get('id'))
-                                    ->where('is_current',true);
-				break;
+                # 获取 course 内所有 成员
+                $table = CourseMember::where('course_id', $request->input('id'))
+                    ->where('is_current', true);
+                break;
             case 'timeline':
                 /**
                  * 编辑时间线
                  */
-                $table = CourseMember::where('user_id',$request->get('userId'));
-                if($request->get('timeline','current')==='current'){
-                    $table = $table->where('course_id', $request->get('course'));
+                $table = CourseMember::where('user_id', $request->input('userId'));
+                if ($request->input('timeline', 'current') === 'current') {
+                    $table = $table->where('course_id', $request->input('course'));
                 }
 
                 break;
             default:
-                return $this->error('无法识别的参数view',400,400);
-            break;
+                return $this->error('无法识别的参数view', 400, 400);
+                break;
         }
-        if(!empty($request->get("role")) && $request->get("role") !=='all'){
-            $table = $table->where('role', $request->get("role"));
+        if (!empty($request->input("role")) && $request->input("role") !== 'all') {
+            $table = $table->where('role', $request->input("role"));
         }
-        if(!empty($request->get("status"))){
-            $table = $table->whereIn('status', explode(',',$request->get("status")) );
+        if (!empty($request->input("status"))) {
+            $table = $table->whereIn('status', explode(',', $request->input("status")));
         }
-        if(!empty($request->get("search"))){
-            $usersId = UserInfo::where('nickname','like', '%'.$request->get("search")."%")
-                            ->select('userid')
-                            ->get();
+        if (!empty($request->input("search"))) {
+            $usersId = UserInfo::where('nickname', 'like', '%' . $request->input("search") . "%")
+                ->select('userid')
+                ->get();
             $table = $table->whereIn('user_id', $usersId);
         }
 
         $count = $table->count();
 
-        $table = $table->orderBy($request->get('order','created_at'),
-                                $request->get('dir','asc'));
+        $table = $table->orderBy(
+            $request->input('order', 'created_at'),
+            $request->input('dir', 'asc')
+        );
 
-        $table = $table->skip($request->get('offset',0))
-              ->take($request->get('limit',1000));
+        $table = $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
 
         //获取当前用户角色
-        $role = CourseMember::where('course_id', $request->get('id'))
-                            ->where('user_id', $user['user_uid'])
-                            ->where('is_current',true)
-                            ->value('role');
-
-		return $this->ok(["rows"=>CourseMemberResource::collection($result),'role'=>$role,"count"=>$count]);
+        $role = CourseMember::where('course_id', $request->input('id'))
+            ->where('user_id', $user['user_uid'])
+            ->where('is_current', true)
+            ->value('role');
 
+        return $this->ok(["rows" => CourseMemberResource::collection($result), 'role' => $role, "count" => $count]);
     }
 
     /**
@@ -103,8 +111,8 @@ class CourseMemberController extends Controller
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error(__('auth.failed',[403],403));
+        if (!$user) {
+            return $this->error(__('auth.failed', [403], 403));
         }
         $validated = $request->validate([
             'user_id' => 'required',
@@ -113,23 +121,24 @@ class CourseMemberController extends Controller
             'status' => 'required',
         ]);
         //查找重复的
-        if($validated['status'] !== 'invited'){
-            if(CourseMember::where('course_id', $validated['course_id'])
-                        ->where('user_id',$validated['user_id'])
-                        ->exists()){
-                return $this->error('member exists',[200],200);
+        if ($validated['status'] !== 'invited') {
+            if (CourseMember::where('course_id', $validated['course_id'])
+                ->where('user_id', $validated['user_id'])
+                ->exists()
+            ) {
+                return $this->error('member exists', [200], 200);
             }
         }
 
-        if($validated['status'] === 'invited'){
+        if ($validated['status'] === 'invited') {
             $userId = $validated['user_id'];
-        }else{
+        } else {
             $userId = $user['user_uid'];
         }
 
-        CourseMember::where('course_id',$validated['course_id'])
-            ->where('user_id',$userId)
-            ->update(['is_current'=>false]);
+        CourseMember::where('course_id', $validated['course_id'])
+            ->where('user_id', $userId)
+            ->update(['is_current' => false]);
 
         $newMember = new CourseMember();
         $newMember->course_id = $validated['course_id'];
@@ -144,29 +153,30 @@ class CourseMemberController extends Controller
          * manual: progressing
          */
         $course  = Course::find($validated['course_id']);
-        if(!$course){
+        if (!$course) {
             return $this->error('invalid course');
         }
         switch ($course->join) {
             case 'open': //开放学习课程
-                if($validated['status']!=='joined' &&
-                    $validated['status']!=='invited'
-                    ){
-                    return $this->error('invalid course',[200],200);
-                    }
+                if (
+                    $validated['status'] !== 'joined' &&
+                    $validated['status'] !== 'invited'
+                ) {
+                    return $this->error('invalid course', [200], 200);
+                }
                 break;
             case 'manual': //人工审核课程
-                if($validated['status']!=='applied' &&
-                    $validated['status']!=='invited'
-                    ){
-                    return $this->error('invalid course',[200],200);
-                    }
+                if (
+                    $validated['status'] !== 'applied' &&
+                    $validated['status'] !== 'invited'
+                ) {
+                    return $this->error('invalid course', [200], 200);
+                }
                 break;
         }
         $newMember->save();
 
         return $this->ok(new CourseMemberResource($newMember));
-
     }
 
     /**
@@ -176,25 +186,25 @@ class CourseMemberController extends Controller
      * @param  string  $courseId
      * @return \Illuminate\Http\Response
      */
-    public function show(Request $request,string $courseId)
+    public function show(Request $request, string $courseId)
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         $userId = $user['user_uid'];
-        if(!empty($request->get('user_uid'))){
-            $userId = $request->get('user_uid');
+        if (!empty($request->input('user_uid'))) {
+            $userId = $request->input('user_uid');
         }
-        $member = CourseMember::where('course_id',$courseId)
-                                ->where('user_id',$userId)
-                                ->where('is_current',true)
-                                ->first();
-        if($member){
+        $member = CourseMember::where('course_id', $courseId)
+            ->where('user_id', $userId)
+            ->where('is_current', true)
+            ->first();
+        if ($member) {
             return $this->ok(new CourseMemberResource($member));
-        }else{
-            return $this->error('no result',200,200);
+        } else {
+            return $this->error('no result', 200, 200);
         }
     }
 
@@ -213,7 +223,7 @@ class CourseMemberController extends Controller
          * 原有记录变为历史记录
          */
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
 
@@ -228,44 +238,41 @@ class CourseMemberController extends Controller
         $courseMember->is_current = false;
         $courseMember->save();
 
-        if($request->has('channel_id')) {
-            if($newMember->user_id !== $user['user_uid']){
+        if ($request->has('channel_id')) {
+            if ($newMember->user_id !== $user['user_uid']) {
                 return $this->error(__('auth.failed'));
             }
-            $newMember->channel_id = $request->get('channel_id');
+            $newMember->channel_id = $request->input('channel_id');
         }
-        if($request->has('status')) {
-            $newMember->status = $request->get('status');
+        if ($request->has('status')) {
+            $newMember->status = $request->input('status');
         }
         $newMember->save();
         return $this->ok(new CourseMemberResource($newMember));
-
     }
     public function set_channel(Request $request)
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
 
-        if($request->has('channel_id')) {
-            $courseMember = CourseMember::where('course_id',$request->get('course_id'))
-                                        ->where('user_id',$user['user_uid'])
-                                        ->where('is_current',true)
-                                        ->first();
-            if($courseMember){
-                $courseMember->channel_id = $request->get('channel_id');
+        if ($request->has('channel_id')) {
+            $courseMember = CourseMember::where('course_id', $request->input('course_id'))
+                ->where('user_id', $user['user_uid'])
+                ->where('is_current', true)
+                ->first();
+            if ($courseMember) {
+                $courseMember->channel_id = $request->input('channel_id');
                 $courseMember->save();
                 return $this->ok(new CourseMemberResource($courseMember));
-            }else{
+            } else {
                 return $this->error(__('auth.failed'));
             }
-        }else{
+        } else {
             return $this->error(__('auth.failed'));
         }
-
-
     }
 
     /**
@@ -275,25 +282,25 @@ class CourseMemberController extends Controller
      * @param  \App\Models\CourseMember  $courseMember
      * @return \Illuminate\Http\Response
      */
-    public function destroy(Request $request,CourseMember $courseMember)
+    public function destroy(Request $request, CourseMember $courseMember)
     {
         //查看删除者有没有删除权限
         //查询删除者的权限
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
 
-        $isOwner = Course::where('id',$courseMember->course_id)->where('studio_id',$user["user_uid"])->exists();
-        if(!$isOwner){
-            $courseUser = CourseMember::where('course_id',$courseMember->course_id)
-                ->where('user_id',$user["user_uid"])
+        $isOwner = Course::where('id', $courseMember->course_id)->where('studio_id', $user["user_uid"])->exists();
+        if (!$isOwner) {
+            $courseUser = CourseMember::where('course_id', $courseMember->course_id)
+                ->where('user_id', $user["user_uid"])
                 ->select('role')->first();
             //open 课程 可以删除自己
 
-            if(!$courseUser){
+            if (!$courseUser) {
                 //被删除的不是自己
-                if($courseUser->role ==="student"){
+                if ($courseUser->role === "student") {
                     //普通成员没有删除权限
                     return $this->error(__('auth.failed'));
                 }
@@ -313,25 +320,26 @@ class CourseMemberController extends Controller
     public function curr(Request $request)
     {
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
-        $courseUser = CourseMember::where('course_id',$request->get("course_id"))
-                                ->where('user_id',$user["user_uid"])
-                                ->where('is_current',true)
-                                ->select(['role','channel_id'])->first();
-        if($courseUser){
+        $courseUser = CourseMember::where('course_id', $request->input("course_id"))
+            ->where('user_id', $user["user_uid"])
+            ->where('is_current', true)
+            ->select(['role', 'channel_id'])->first();
+        if ($courseUser) {
             return $this->ok($courseUser);
-        }else{
+        } else {
             return $this->error("not member");
         }
     }
 
-    public function export(Request $request){
+    public function export(Request $request)
+    {
 
-        $courseUser = CourseMember::where('course_id',$request->get("course_id"))
-                                    ->where('is_current',true)
-                                    ->get();
+        $courseUser = CourseMember::where('course_id', $request->input("course_id"))
+            ->where('is_current', true)
+            ->get();
 
         $spreadsheet = new Spreadsheet();
         $activeWorksheet = $spreadsheet->getActiveSheet();

+ 47 - 51
api-v12/app/Http/Controllers/DhammaTermController.php

@@ -6,7 +6,6 @@ use Illuminate\Http\Request;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Str;
 use Illuminate\Support\Facades\App;
-use Illuminate\Support\Facades\Log;
 
 use App\Models\DhammaTerm;
 use App\Models\Channel;
@@ -19,9 +18,6 @@ use App\Http\Api\ShareApi;
 use App\Tools\Tools;
 use Illuminate\Support\Facades\Cache;
 
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
-use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
-
 
 class DhammaTermController extends Controller
 {
@@ -49,11 +45,11 @@ class DhammaTermController extends Controller
             'updated_at'
         ];
 
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'create-by-channel':
                 # 新建术语时。根据术语所在channel 给出新建术语所需数据。如语言,备选意思等。
                 #获取channel信息
-                $currChannel = Channel::where('uid', $request->get('channel'))->first();
+                $currChannel = Channel::where('uid', $request->input('channel'))->first();
                 if (!$currChannel) {
                     return $this->error(__('auth.failed'));
                 }
@@ -63,7 +59,7 @@ class DhammaTermController extends Controller
                     ->select(['name', 'uid'])
                     ->get();
                 #获取全网意思列表
-                $meanings = DhammaTerm::where('word', $request->get('word'))
+                $meanings = DhammaTerm::where('word', $request->input('word'))
                     ->where('language', $currChannel->lang)
                     ->select(['meaning', 'other_meaning'])
                     ->get();
@@ -91,7 +87,7 @@ class DhammaTermController extends Controller
                     $meaningCount[] = ['meaning' => $key, 'count' => $value];
                 }
                 return $this->ok([
-                    "word" => $request->get('word'),
+                    "word" => $request->input('word'),
                     "meaningCount" => $meaningCount,
                     "studioChannels" => $studioChannels,
                     "language" => $currChannel->lang,
@@ -105,7 +101,7 @@ class DhammaTermController extends Controller
                     return $this->error(__('auth.failed'), [], 401);
                 }
                 //判断当前用户是否有指定的studio的权限
-                if ($user['user_uid'] !== StudioApi::getIdByName($request->get('name'))) {
+                if ($user['user_uid'] !== StudioApi::getIdByName($request->input('name'))) {
                     return $this->error(__('auth.failed'), [], 403);
                 }
                 $table = DhammaTerm::select($indexCol)
@@ -118,19 +114,19 @@ class DhammaTermController extends Controller
                     return $this->error(__('auth.failed'));
                 }
                 //判断当前用户是否有指定的 channel 的权限
-                $channel = Channel::find($request->get('id'));
+                $channel = Channel::find($request->input('id'));
                 if ($user['user_uid'] !== $channel->owner_uid) {
                     //看是否为协作
-                    $power = ShareApi::getResPower($user['user_uid'], $request->get('id'));
+                    $power = ShareApi::getResPower($user['user_uid'], $request->input('id'));
                     if ($power === 0) {
                         return $this->error(__('auth.failed'), [], 403);
                     }
                 }
                 $table = DhammaTerm::select($indexCol)
-                    ->where('channal', $request->get('id'));
+                    ->where('channal', $request->input('id'));
                 break;
             case 'show':
-                return $this->ok(DhammaTerm::find($request->get('id')));
+                return $this->ok(DhammaTerm::find($request->input('id')));
                 break;
             case 'user':
                 # code...
@@ -139,32 +135,32 @@ class DhammaTermController extends Controller
                     return $this->error(__('auth.failed'));
                 }
                 $userUid = $user['user_uid'];
-                $search = $request->get('search');
+                $search = $request->input('search');
                 $table = DhammaTerm::select($indexCol)
                     ->where('owner', $userUid);
                 break;
             case 'word':
                 $table = DhammaTerm::select($indexCol)
-                    ->whereIn('word', explode(',', $request->get("word")))
-                    ->orWhereIn('meaning', explode(',', $request->get("word")));
+                    ->whereIn('word', explode(',', $request->input("word")))
+                    ->orWhereIn('meaning', explode(',', $request->input("word")));
                 break;
             case 'tag':
                 $table = DhammaTerm::select($indexCol)
-                    ->whereIn('tag', explode(',', $request->get("tag")));
+                    ->whereIn('tag', explode(',', $request->input("tag")));
                 break;
             case 'hot-meaning':
                 $key = 'term/hot_meaning';
                 $value = Cache::get($key, function () use ($request) {
                     $hotMeaning = [];
                     $words = DhammaTerm::select('word')
-                        ->where('language', $request->get("language"))
+                        ->where('language', $request->input("language"))
                         ->groupby('word')
                         ->get();
 
                     foreach ($words as $key => $word) {
                         # code...
                         $result = DhammaTerm::select(DB::raw('count(*) as word_count, meaning'))
-                            ->where('language', $request->get("language"))
+                            ->where('language', $request->input("language"))
                             ->where('word', $word['word'])
                             ->groupby('meaning')
                             ->orderby('word_count', 'desc')
@@ -173,7 +169,7 @@ class DhammaTermController extends Controller
                             $hotMeaning[] = [
                                 'word' => $word['word'],
                                 'meaning' => $result['meaning'],
-                                'language' => $request->get("language"),
+                                'language' => $request->input("language"),
                                 'owner' => '',
                             ];
                         }
@@ -188,7 +184,7 @@ class DhammaTermController extends Controller
                 break;
         }
 
-        $search = $request->get('search');
+        $search = $request->input('search');
         if (!empty($search)) {
             $table = $table->where(function ($query) use ($search) {
                 $query->where('word', 'like', $search . "%")
@@ -197,9 +193,9 @@ class DhammaTermController extends Controller
             });
         }
         $count = $table->count();
-        $table = $table->orderBy($request->get('order', 'updated_at'), $request->get('dir', 'desc'));
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 1000));
+        $table = $table->orderBy($request->input('order', 'updated_at'), $request->input('dir', 'desc'));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 1000));
         $result = $table->get();
 
         return $this->ok(["rows" => TermResource::collection($result), "count" => $count]);
@@ -228,13 +224,13 @@ class DhammaTermController extends Controller
          * 一个channel下面word+tag+language 唯一
          */
         $table = DhammaTerm::where('owner', $user["user_uid"])
-            ->where('word', $request->get("word"))
-            ->where('tag', $request->get("tag"));
-        if (!empty($request->get("channel"))) {
-            $isDoesntExist = $table->where('channal', $request->get("channel"))
+            ->where('word', $request->input("word"))
+            ->where('tag', $request->input("tag"));
+        if (!empty($request->input("channel"))) {
+            $isDoesntExist = $table->where('channal', $request->input("channel"))
                 ->doesntExist();
         } else {
-            $isDoesntExist = $table->whereNull('channal')->where('language', $request->get("language"))
+            $isDoesntExist = $table->whereNull('channal')->where('language', $request->input("language"))
                 ->doesntExist();
         }
 
@@ -243,21 +239,21 @@ class DhammaTermController extends Controller
             $term = new DhammaTerm;
             $term->id = app('snowflake')->id();
             $term->guid = Str::uuid();
-            $term->word = $request->get("word");
-            $term->word_en = Tools::getWordEn($request->get("word"));
-            $term->meaning = $request->get("meaning");
-            $term->other_meaning = $request->get("other_meaning");
-            $term->note = $request->get("note");
-            $term->tag = $request->get("tag");
-            $term->channal = $request->get("channel");
-            $term->language = $request->get("language");
-            if (!empty($request->get("channel"))) {
-                $channelInfo = ChannelApi::getById($request->get("channel"));
+            $term->word = $request->input("word");
+            $term->word_en = Tools::getWordEn($request->input("word"));
+            $term->meaning = $request->input("meaning");
+            $term->other_meaning = $request->input("other_meaning");
+            $term->note = $request->input("note");
+            $term->tag = $request->input("tag");
+            $term->channal = $request->input("channel");
+            $term->language = $request->input("language");
+            if (!empty($request->input("channel"))) {
+                $channelInfo = ChannelApi::getById($request->input("channel"));
                 if (!$channelInfo) {
                     return $this->error("channel id failed");
                 } else {
                     //查看有没有channel权限
-                    $power = ShareApi::getResPower($user["user_uid"], $request->get("channel"), 2);
+                    $power = ShareApi::getResPower($user["user_uid"], $request->input("channel"), 2);
                     if ($power < 20) {
                         return $this->error(__('auth.failed'));
                     }
@@ -266,9 +262,9 @@ class DhammaTermController extends Controller
                 }
             } else {
                 if ($request->has("studioId")) {
-                    $studioId = $request->get("studioId");
+                    $studioId = $request->input("studioId");
                 } else if ($request->has("studioName")) {
-                    $studioId = StudioApi::getIdByName($request->get("studioName"));
+                    $studioId = StudioApi::getIdByName($request->input("studioName"));
                 }
                 if (Str::isUuid($studioId)) {
                     $term->owner = $studioId;
@@ -350,13 +346,13 @@ class DhammaTermController extends Controller
             }
         }
 
-        $dhammaTerm->word = $request->get("word");
-        $dhammaTerm->word_en = Tools::getWordEn($request->get("word"));
-        $dhammaTerm->meaning = $request->get("meaning");
-        $dhammaTerm->other_meaning = $request->get("other_meaning");
-        $dhammaTerm->note = $request->get("note");
-        $dhammaTerm->tag = $request->get("tag");
-        $dhammaTerm->language = $request->get("language");
+        $dhammaTerm->word = $request->input("word");
+        $dhammaTerm->word_en = Tools::getWordEn($request->input("word"));
+        $dhammaTerm->meaning = $request->input("meaning");
+        $dhammaTerm->other_meaning = $request->input("other_meaning");
+        $dhammaTerm->note = $request->input("note");
+        $dhammaTerm->tag = $request->input("tag");
+        $dhammaTerm->language = $request->input("language");
         $dhammaTerm->editor_id = $user["user_id"];
         $dhammaTerm->create_time = time() * 1000;
         $dhammaTerm->modify_time = time() * 1000;
@@ -384,7 +380,7 @@ class DhammaTermController extends Controller
         $count = 0;
         if ($request->has("uuid")) {
             //查看是否有删除权限
-            foreach ($request->get("id") as $key => $uuid) {
+            foreach ($request->input("id") as $key => $uuid) {
                 $term = DhammaTerm::find($uuid);
                 if ($term->owner !== $user['user_uid']) {
                     if (!empty($term->channal)) {
@@ -402,7 +398,7 @@ class DhammaTermController extends Controller
                 $this->deleteCache($term);
             }
         } else {
-            $arrId = json_decode($request->get("id"), true);
+            $arrId = json_decode($request->input("id"), true);
             foreach ($arrId as $key => $id) {
                 # code...
                 $term = DhammaTerm::where('id', $id)

+ 3 - 4
api-v12/app/Http/Controllers/DictController.php

@@ -8,11 +8,10 @@ use App\Models\DictInfo;
 use App\Models\GroupMember;
 use Illuminate\Http\Request;
 use App\Tools\CaseMan;
-use Illuminate\Support\Facades\Log;
 use App\Http\Api\DictApi;
 use App\Http\Api\AuthApi;
 
-require_once __DIR__ . "/../../../public/app/dict/grm_abbr.php";
+require_once __DIR__ . "/../../Tools/grm_abbr.php";
 
 
 class DictController extends Controller
@@ -36,8 +35,8 @@ class DictController extends Controller
         $words = [];
         $word_base = [];
         $searched = [];
-        $words[$request->get('word')] = [];
-        $userLang = $request->get('lang', "zh");
+        $words[$request->input('word')] = [];
+        $userLang = $request->input('lang', "zh");
 
         /**
          * 临时代码判断是否在缅汉字典群里面。在群里的用户可以产看缅汉字典pdf

+ 11 - 9
api-v12/app/Http/Controllers/DictInfoController.php

@@ -16,9 +16,9 @@ class DictInfoController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'name':
-                $table = DictInfo::where('name',$request->get('name'));
+                $table = DictInfo::where('name', $request->input('name'));
                 break;
 
             default:
@@ -26,18 +26,20 @@ class DictInfoController extends Controller
                 break;
         }
 
-        $table = $table->orderBy($request->get('order','updated_at'),
-                                $request->get('dir','desc'));
+        $table = $table->orderBy(
+            $request->input('order', 'updated_at'),
+            $request->input('dir', 'desc')
+        );
 
-        $table = $table->skip($request->get('offset',0))
-                       ->take($request->get('limit',100));
+        $table = $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 100));
 
         $result = $table->get();
         $count = count($result);
         return $this->ok([
-                            "rows"=>DictInfoResource::collection($result),
-                            "count"=>$count
-                        ]);
+            "rows" => DictInfoResource::collection($result),
+            "count" => $count
+        ]);
     }
 
     /**

+ 2 - 2
api-v12/app/Http/Controllers/DictMeaningController.php

@@ -51,8 +51,8 @@ class DictMeaningController extends Controller
     public function index(Request $request)
     {
         //
-        $words = explode("-", $request->get('word'));
-        $lang = $request->get('lang');
+        $words = explode("-", $request->input('word'));
+        $lang = $request->input('lang');
         $key = "dict_first_mean/";
         $meaning = [];
         foreach ($words as $key => $word) {

+ 6 - 6
api-v12/app/Http/Controllers/DictPreferenceController.php

@@ -38,20 +38,20 @@ class DictPreferenceController extends Controller
                 'user_dicts.editor_id',
             ]);
         //处理搜索
-        if (!empty($request->get("keyword"))) {
-            $table = $table->where('word_indices.word', 'like', "%" . $request->get("keyword") . "%");
+        if (!empty($request->input("keyword"))) {
+            $table = $table->where('word_indices.word', 'like', "%" . $request->input("keyword") . "%");
         }
 
         //获取记录总条数
         $count = $table->count();
         //处理排序
         $table = $table->orderBy(
-            $request->get("order", 'word_indices.count'),
-            $request->get("dir", 'desc')
+            $request->input("order", 'word_indices.count'),
+            $request->input("dir", 'desc')
         );
         //处理分页
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get("limit", 200));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input("limit", 200));
         //获取数据
         $result = $table->get();
         return $this->ok([

+ 23 - 24
api-v12/app/Http/Controllers/DictVocabularyController.php

@@ -18,44 +18,43 @@ class DictVocabularyController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get("view")) {
+        switch ($request->input("view")) {
             case 'dict_name':
-                $id = DictInfo::where('name',$request->get("name"))->value('id');
-                if(!$id){
-                    return $this->error('name:'.$request->get("name").' can not found.',200,200);
+                $id = DictInfo::where('name', $request->input("name"))->value('id');
+                if (!$id) {
+                    return $this->error('name:' . $request->input("name") . ' can not found.', 200, 200);
                 }
-                $table = UserDict::where('dict_id',$id)
-                                ->groupBy('word')
-                                ->selectRaw('word,count(*)');
+                $table = UserDict::where('dict_id', $id)
+                    ->groupBy('word')
+                    ->selectRaw('word,count(*)');
                 break;
             case 'dict_short_name':
-                    $id = DictInfo::where('shortname',$request->get("name"))->value('id');
-                    if(!$id){
-                        return $this->error('name:'.$request->get("name").' can not found.',200,200);
-                    }
-                    $table = UserDict::where('dict_id',$id)
-                                    ->groupBy('word')
-                                    ->selectRaw('word,count(*)');
-                    break;
-
+                $id = DictInfo::where('shortname', $request->input("name"))->value('id');
+                if (!$id) {
+                    return $this->error('name:' . $request->input("name") . ' can not found.', 200, 200);
+                }
+                $table = UserDict::where('dict_id', $id)
+                    ->groupBy('word')
+                    ->selectRaw('word,count(*)');
+                break;
         }
-        if($request->get("stream") === 'true'){
+        if ($request->input("stream") === 'true') {
             return response()->streamDownload(function () use ($table) {
                 $result = $table->get();
                 echo json_encode($result);
-            },'dict.txt');
+            }, 'dict.txt');
         }
         $count = 2;
-        $table = $table->orderBy('word',$request->get('dir','asc'));
+        $table = $table->orderBy('word', $request->input('dir', 'asc'));
 
-        $table = $table->skip($request->get('offset',0))
-                       ->take($request->get('limit',1000));
+        $table = $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
         return $this->ok([
-                            "rows"=>DictVocabularyResource::collection($result),
-                            "count"=>$count
-                        ]);
+            "rows" => DictVocabularyResource::collection($result),
+            "count" => $count
+        ]);
     }
 
     /**

+ 54 - 54
api-v12/app/Http/Controllers/DiscussionController.php

@@ -35,10 +35,10 @@ class DiscussionController extends Controller
         if ($user) {
             $userInfo = UserApi::getByUuid($user['user_uid']);
         }
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'question-by-topic':
-                $topic = Discussion::where('id', $request->get('id'));
-                $topic->where('status', $request->get('status', 'active'))
+                $topic = Discussion::where('id', $request->input('id'));
+                $topic->where('status', $request->input('status', 'active'))
                     ->select('res_id')->first();
                 if (!$topic) {
                     return $this->error("无效的id");
@@ -48,7 +48,7 @@ class DiscussionController extends Controller
                     ->where('status', 'active')->count();
                 $closeNumber = Discussion::where('res_id', $topic->res_id)
                     ->where('status', 'close')->count();
-                $table->where('status', $request->get('status', 'active'))
+                $table->where('status', $request->input('status', 'active'))
                     ->where('parent', null);
                 break;
             case 'question':
@@ -58,7 +58,7 @@ class DiscussionController extends Controller
                  * basic用户看到别人在别人channel发表的discussion
                  *
                  */
-                if (!$user && $request->get('type') === 'discussion') {
+                if (!$user && $request->input('type') === 'discussion') {
                     return $this->ok([
                         "rows" => [],
                         "count" => 0,
@@ -68,14 +68,14 @@ class DiscussionController extends Controller
                         'can_reply' => false,
                     ]);
                 }
-                $resType = $request->get('res_type');
+                $resType = $request->input('res_type');
                 if ($user) {
                     switch ($resType) {
                         case 'sentence':
                             # code...
                             break;
                         case 'wbw':
-                            $block_uid = Wbw::where('uid', $request->get('id'))->value('block_uid');
+                            $block_uid = Wbw::where('uid', $request->input('id'))->value('block_uid');
                             if ($block_uid) {
                                 $channelId = WbwBlock::where('uid', $block_uid)->value('channel_uid');
                                 if ($channelId) {
@@ -90,8 +90,8 @@ class DiscussionController extends Controller
                 }
 
 
-                $resId = [$request->get('id')];
-                if (!empty($request->get('course'))) {
+                $resId = [$request->input('id')];
+                if (!empty($request->input('course'))) {
                     //
                     /**
                      * 如果res id 是答案,获取学员提问
@@ -99,12 +99,12 @@ class DiscussionController extends Controller
                      */
                     //获取学员提问
                     //获取学员channel
-                    if ($request->get('show_student') === 'true') {
-                        $channelsId = CourseApi::getStudentChannels($request->get('course'));
+                    if ($request->input('show_student') === 'true') {
+                        $channelsId = CourseApi::getStudentChannels($request->input('course'));
                         switch ($resType) {
                             case 'wbw':
                                 //获取答案单词编号
-                                $wbwWord = Wbw::where('uid', $request->get('id'))
+                                $wbwWord = Wbw::where('uid', $request->input('id'))
                                     ->first();
                                 $wbwId = WbwSentenceController::getWbwIdByChannels(
                                     $channelsId,
@@ -120,10 +120,10 @@ class DiscussionController extends Controller
                     }
                 }
                 $table = Discussion::whereIn('res_id', $resId)
-                    ->where('type', $request->get('type', 'discussion'))
-                    ->where('status', $request->get('status', 'active'))
+                    ->where('type', $request->input('type', 'discussion'))
+                    ->where('status', $request->input('status', 'active'))
                     ->where('parent', null);
-                if ($request->get('type') === 'discussion') {
+                if ($request->input('type') === 'discussion') {
                     if (
                         isset($userInfo) &&
                         isset($userInfo['roles']) &&
@@ -137,18 +137,18 @@ class DiscussionController extends Controller
                 }
                 $activeNumber = Discussion::whereIn('res_id', $resId)
                     ->where('parent', null)
-                    ->where('type', $request->get('type', 'discussion'))
+                    ->where('type', $request->input('type', 'discussion'))
                     ->where('status', 'active')->count();
                 $closeNumber = Discussion::whereIn('res_id', $resId)
                     ->where('parent', null)
-                    ->where('type', $request->get('type', 'discussion'))
+                    ->where('type', $request->input('type', 'discussion'))
                     ->where('status', 'close')->count();
                 break;
             case 'answer':
-                $table = Discussion::where('parent', $request->get('id'));
-                $activeNumber = Discussion::where('parent', $request->get('id'))
+                $table = Discussion::where('parent', $request->input('id'));
+                $activeNumber = Discussion::where('parent', $request->input('id'))
                     ->where('status', 'active')->count();
-                $closeNumber = Discussion::where('parent', $request->get('id'))
+                $closeNumber = Discussion::where('parent', $request->input('id'))
                     ->where('status', 'close')->count();
                 break;
             case 'res_id':
@@ -156,9 +156,9 @@ class DiscussionController extends Controller
                  * 先获取顶级节点
                  * 需要确定用户身份,manager查看全部topic 普通用户只显示自己提交的topic
                  */
-                $roots = Discussion::where('res_id', $request->get('id'))
-                    ->where('type', $request->get('type', 'discussion'))
-                    ->whereIn('status', explode(',', $request->get('status', 'active')))
+                $roots = Discussion::where('res_id', $request->input('id'))
+                    ->where('type', $request->input('type', 'discussion'))
+                    ->whereIn('status', explode(',', $request->input('status', 'active')))
                     ->where('parent', null)
                     ->select('id')
                     ->get();
@@ -167,11 +167,11 @@ class DiscussionController extends Controller
                     $query->whereIn('id', $roots)
                         ->orWhereIn('parent', $roots);
                 });
-                $activeNumber = Discussion::where('res_id', $request->get('id'))
-                    ->where('type', $request->get('type', 'discussion'))
+                $activeNumber = Discussion::where('res_id', $request->input('id'))
+                    ->where('type', $request->input('type', 'discussion'))
                     ->where('status', 'active')->count();
-                $closeNumber = Discussion::where('res_id', $request->get('id'))
-                    ->where('type', $request->get('type', 'discussion'))
+                $closeNumber = Discussion::where('res_id', $request->input('id'))
+                    ->where('type', $request->input('type', 'discussion'))
                     ->where('status', 'close')->count();
                 break;
             case 'topic-by-user':
@@ -183,16 +183,16 @@ class DiscussionController extends Controller
                     return $this->error('', 403, 403);
                 }
                 $table = Discussion::where('editor_uid', $user['user_uid'])
-                    ->where('type', $request->get('type', 'discussion'))
-                    ->whereIn('status', explode(',', $request->get('status', 'active')))
+                    ->where('type', $request->input('type', 'discussion'))
+                    ->whereIn('status', explode(',', $request->input('status', 'active')))
                     ->where('parent', null);
                 $activeNumber = Discussion::where('editor_uid', $user['user_uid'])
                     ->where('parent', null)
-                    ->where('type', $request->get('type', 'discussion'))
+                    ->where('type', $request->input('type', 'discussion'))
                     ->where('status', 'active')->count();
                 $closeNumber = Discussion::where('editor_uid', $user['user_uid'])
                     ->where('parent', null)
-                    ->where('type', $request->get('type', 'discussion'))
+                    ->where('type', $request->input('type', 'discussion'))
                     ->where('status', 'close')->count();
                 break;
             case 'all':
@@ -208,9 +208,9 @@ class DiscussionController extends Controller
         }
         $count = $table->count();
 
-        $table = $table->orderBy($request->get('order', 'created_at'), $request->get('dir', 'desc'));
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 100));
+        $table = $table->orderBy($request->input('order', 'created_at'), $request->input('dir', 'desc'));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 100));
 
         $result = $table->get();
 
@@ -218,11 +218,11 @@ class DiscussionController extends Controller
         $can_reply = false;
         $user = AuthApi::current($request);
 
-        switch ($request->get('type', 'discussion')) {
+        switch ($request->input('type', 'discussion')) {
             case 'qa':
-                switch ($request->get('res_type')) {
+                switch ($request->input('res_type')) {
                     case 'article':
-                        if ($user && ArticleController::userCanEditId($user['user_uid'], $request->get('id'))) {
+                        if ($user && ArticleController::userCanEditId($user['user_uid'], $request->input('id'))) {
                             $can_create = true;
                             $can_reply = true;
                         }
@@ -230,11 +230,11 @@ class DiscussionController extends Controller
                 }
                 break;
             case 'help':
-                switch ($request->get('res_type')) {
+                switch ($request->input('res_type')) {
                     case 'article':
                         if ($user) {
                             $can_reply = true;
-                            if (ArticleController::userCanEditId($user['user_uid'], $request->get('id'))) {
+                            if (ArticleController::userCanEditId($user['user_uid'], $request->input('id'))) {
                                 $can_create = true;
                             }
                         }
@@ -262,7 +262,7 @@ class DiscussionController extends Controller
     public function discussion_tree(Request $request)
     {
         $output = [];
-        $sentences = $request->get("data");
+        $sentences = $request->input("data");
         foreach ($sentences as $key => $sentence) {
             # 先查句子信息
             $sentInfo = Sentence::where('book_id', $sentence['book'])
@@ -313,7 +313,7 @@ class DiscussionController extends Controller
 
         if ($request->has('parent')) {
             $rules = [];
-            $parentInfo = Discussion::find($request->get('parent'));
+            $parentInfo = Discussion::find($request->input('parent'));
             if (!$parentInfo) {
                 return $this->error('no record');
             }
@@ -332,15 +332,15 @@ class DiscussionController extends Controller
             $discussion->res_id = $parentInfo->res_id;
             $discussion->res_type = $parentInfo->res_type;
         } else {
-            $discussion->res_id = $request->get('res_id');
-            $discussion->res_type = $request->get('res_type');
+            $discussion->res_id = $request->input('res_id');
+            $discussion->res_type = $request->input('res_type');
         }
-        $discussion->type = $request->get('type', 'discussion');
-        $discussion->tpl_id = $request->get('tpl_id');
-        $discussion->title = $request->get('title', null);
-        $discussion->content = $request->get('content', null);
-        $discussion->content_type = $request->get('content_type', "markdown");
-        $discussion->parent = $request->get('parent', null);
+        $discussion->type = $request->input('type', 'discussion');
+        $discussion->tpl_id = $request->input('tpl_id');
+        $discussion->title = $request->input('title', null);
+        $discussion->content = $request->input('content', null);
+        $discussion->content_type = $request->input('content_type', "markdown");
+        $discussion->parent = $request->input('parent', null);
         $discussion->editor_uid = $user['user_uid'];
         $discussion->save();
         //更新parent children_count
@@ -348,7 +348,7 @@ class DiscussionController extends Controller
             $parentInfo->increment('children_count', 1);
             $parentInfo->save();
         }
-        if ($request->get('notification', true)) {
+        if ($request->input('notification', true)) {
             Mq::publish('discussion', new DiscussionResource($discussion));
         }
 
@@ -454,11 +454,11 @@ class DiscussionController extends Controller
             return $this->error(__('auth.failed'), [403], 403);
         }
 
-        $discussion->title = $request->get('title', null);
-        $discussion->content = $request->get('content', null);
-        $discussion->status = $request->get('status', 'active');
+        $discussion->title = $request->input('title', null);
+        $discussion->content = $request->input('content', null);
+        $discussion->status = $request->input('status', 'active');
         if ($request->has('type')) {
-            $discussion->type = $request->get('type');
+            $discussion->type = $request->input('type');
         }
         //$discussion->editor_uid = $user['user_uid'];
         $discussion->save();

+ 66 - 67
api-v12/app/Http/Controllers/DiscussionCountController.php

@@ -64,67 +64,66 @@ class DiscussionCountController extends Controller
          * 4. 计算作业channel的结果
          */
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error('auth.failed',401,401);
+        if (!$user) {
+            return $this->error('auth.failed', 401, 401);
         }
         $studioIdForTag = $user["user_uid"];
-        if($request->has('course_id')){
+        if ($request->has('course_id')) {
             //判断我的角色
-            $my = CourseMember::where('user_id',$user["user_uid"])
-                                ->where('is_current',true)
-                                ->where('course_id',$request->get('course_id'))
-                                ->first();
-            if(!$my){
-                return $this->error('auth.failed',403,403);
+            $my = CourseMember::where('user_id', $user["user_uid"])
+                ->where('is_current', true)
+                ->where('course_id', $request->input('course_id'))
+                ->first();
+            if (!$my) {
+                return $this->error('auth.failed', 403, 403);
             }
             //获取全部成员列表
-            $allMembers = CourseMember::where('is_current',true)
-                                ->where('course_id',$request->get('course_id'))
-                                ->select('user_id')
-                                ->get();
-            Log::debug('allMembers',['members'=>$allMembers]);
+            $allMembers = CourseMember::where('is_current', true)
+                ->where('course_id', $request->input('course_id'))
+                ->select('user_id')
+                ->get();
+            Log::debug('allMembers', ['members' => $allMembers]);
             //找到全部相关channel
             $channels = array();
             //获取答案 channel
-            $answerChannel = Course::where('id',$request->get('course_id'))
-                            ->value('channel_id');
-            $exerciseChannels = CourseMember::where('is_current',true)
-                                    ->where('course_id',$request->get('course_id'))
-                                    ->select('channel_id')
-                                    ->get();
-            if($answerChannel){
-                array_push($channels,$answerChannel);
+            $answerChannel = Course::where('id', $request->input('course_id'))
+                ->value('channel_id');
+            $exerciseChannels = CourseMember::where('is_current', true)
+                ->where('course_id', $request->input('course_id'))
+                ->select('channel_id')
+                ->get();
+            if ($answerChannel) {
+                array_push($channels, $answerChannel);
             }
             $users = array();
-            if($my->role === 'student'){
+            if ($my->role === 'student') {
                 //自己的channel + 答案
-                if($my->channel_id){
-                    array_push($channels,$my->channel_id);
+                if ($my->channel_id) {
+                    array_push($channels, $my->channel_id);
                 }
-            }else{
+            } else {
                 //找到全部学员channel + 答案
 
                 foreach ($exerciseChannels as $key => $value) {
-                    array_push($channels,$value->channel_id);
+                    array_push($channels, $value->channel_id);
                 }
                 //找到
-                $courseStudioId = Course::where('id',$request->get('course_id'))
-                            ->value('studio_id');
-                if($courseStudioId){
+                $courseStudioId = Course::where('id', $request->input('course_id'))
+                    ->value('studio_id');
+                if ($courseStudioId) {
                     $studioIdForTag = $courseStudioId;
                 }
-
             }
         }
 
         //获取全部资源列表
         $resId = array();
-        $querySentId = $request->get('sentences');
+        $querySentId = $request->input('sentences');
         //译文
         $table = Sentence::select('uid')
-                        ->whereIns(['book_id','paragraph','word_start','word_end'],$querySentId);
-        if(isset($channels)){
-            $table = $table->whereIn('channel_uid',$channels);
+            ->whereIns(['book_id', 'paragraph', 'word_start', 'word_end'], $querySentId);
+        if (isset($channels)) {
+            $table = $table->whereIn('channel_uid', $channels);
         }
         $sentUid = $table->get();
 
@@ -134,49 +133,49 @@ class DiscussionCountController extends Controller
         //wbw
         $wbwBlockParagraphs = [];
         foreach ($querySentId as $key => $value) {
-            $wbwBlockParagraphs[] = [$value[0],$value[1]];
+            $wbwBlockParagraphs[] = [$value[0], $value[1]];
         }
         $table = WbwBlock::select('uid')
-                          ->whereIns(['book_id','paragraph'],$wbwBlockParagraphs);
-        if(isset($channels)){
-            $table = $table->whereIn('channel_uid',$channels);
+            ->whereIns(['book_id', 'paragraph'], $wbwBlockParagraphs);
+        if (isset($channels)) {
+            $table = $table->whereIn('channel_uid', $channels);
         }
         $wbwBlock = $table->get();
-        if($wbwBlock){
+        if ($wbwBlock) {
             //找到逐词解析数据
             foreach ($querySentId as $key => $value) {
-                $wbwData = Wbw::whereIn('block_uid',$wbwBlock)
-                                ->whereBetween('wid',[$value[2],$value[3]])
-                                ->select('uid')
-                                ->get();
+                $wbwData = Wbw::whereIn('block_uid', $wbwBlock)
+                    ->whereBetween('wid', [$value[2], $value[3]])
+                    ->select('uid')
+                    ->get();
                 foreach ($wbwData as $key => $value) {
                     $resId[] = $value->uid;
                 }
             }
         }
-        Log::debug('res id',['res'=>$resId]);
+        Log::debug('res id', ['res' => $resId]);
         //全部资源id获取完毕
         //获取discussion
-        $table = Discussion::select(['id','res_id','res_type','type','editor_uid'])
-                            ->where('status','active')
-                            ->whereNull('parent')
-                            ->whereIn('res_id',$resId);
-        if(isset($allMembers)){
-            $table = $table->whereIn('editor_uid',$allMembers);
+        $table = Discussion::select(['id', 'res_id', 'res_type', 'type', 'editor_uid'])
+            ->where('status', 'active')
+            ->whereNull('parent')
+            ->whereIn('res_id', $resId);
+        if (isset($allMembers)) {
+            $table = $table->whereIn('editor_uid', $allMembers);
         }
 
         $allDiscussions = $table->get();
         $discussions = DiscussionCountResource::collection($allDiscussions);
 
         //获取 tag
-        $tags = TagMap::select(['tag_maps.id','anchor_id','table_name','tag_id','editor_uid','tags.name','tags.color'])
-                            ->whereIn('anchor_id',$resId)
-                            ->where('owner_uid',$studioIdForTag)
-                            ->leftJoin('tags','tags.id', '=', 'tag_maps.tag_id')
-                            ->get();
-        Log::debug('response',['data'=>$discussions]);
+        $tags = TagMap::select(['tag_maps.id', 'anchor_id', 'table_name', 'tag_id', 'editor_uid', 'tags.name', 'tags.color'])
+            ->whereIn('anchor_id', $resId)
+            ->where('owner_uid', $studioIdForTag)
+            ->leftJoin('tags', 'tags.id', '=', 'tag_maps.tag_id')
+            ->get();
+        Log::debug('response', ['data' => $discussions]);
         return $this->ok([
-            'discussions'=>$discussions,
+            'discussions' => $discussions,
             'tags' => $tags,
         ]);
     }
@@ -190,22 +189,22 @@ class DiscussionCountController extends Controller
     public function show(string  $resId)
     {
         //
-        $allDiscussions = Discussion::where('status','active')
-                                    ->whereNull('parent')
-                                    ->where('res_id',$resId)
-                                    ->select(['id','res_id','res_type','type','editor_uid'])
-                                    ->get();
+        $allDiscussions = Discussion::where('status', 'active')
+            ->whereNull('parent')
+            ->where('res_id', $resId)
+            ->select(['id', 'res_id', 'res_type', 'type', 'editor_uid'])
+            ->get();
         $discussions = DiscussionCountResource::collection($allDiscussions);
 
         //获取 tag
-        $table = TagMap::select(['id','anchor_id','table_name','tag_id','editor_uid'])
-                       ->where('anchor_id',$resId);
+        $table = TagMap::select(['id', 'anchor_id', 'table_name', 'tag_id', 'editor_uid'])
+            ->where('anchor_id', $resId);
 
         $allTags = $table->get();
         $tags = TagMapResource::collection($allTags);
-        Log::debug('response',['discussions'=>$discussions]);
+        Log::debug('response', ['discussions' => $discussions]);
         return $this->ok([
-            'discussions'=>$discussions,
+            'discussions' => $discussions,
             'tags' => $tags,
         ]);
     }

+ 17 - 0
api-v12/app/Http/Controllers/DownloadController.php

@@ -0,0 +1,17 @@
+<?php
+// api-v12/app/Http/Controllers/DownloadController.php
+namespace App\Http\Controllers;
+
+use App\Services\PacketService;
+
+class DownloadController extends Controller
+{
+    //
+    public function index()
+    {
+        $packets = app(PacketService::class)->index();
+
+
+        return view('library.download', compact('packets'));
+    }
+}

+ 5 - 5
api-v12/app/Http/Controllers/EmailCertificationController.php

@@ -32,25 +32,25 @@ class EmailCertificationController extends Controller
     public function store(Request $request)
     {
         //查询是否重复
-        if (UserInfo::where('email', $request->get('email'))->exists()) {
+        if (UserInfo::where('email', $request->input('email'))->exists()) {
             return $this->error('email.exists', 'err.email.exists', 200);
         }
         $sender = config("mint.admin.root_uuid");
 
         $uuid = Str::uuid();
         $invite = Invite::firstOrNew(
-            ['email' => $request->get('email')],
+            ['email' => $request->input('email')],
             ['id' => $uuid]
         );
         $invite->user_uid = $sender;
         $invite->status = 'invited';
         $invite->save();
 
-        Mail::to($request->get('email'))
+        Mail::to($request->input('email'))
             ->send(new EmailCertif(
                 $invite->id,
-                $request->get('subject', 'sign up wikipali'),
-                $request->get('lang'),
+                $request->input('subject', 'sign up wikipali'),
+                $request->input('lang'),
             ));
         if (Mail::failures()) {
             return $this->error('send email fail', '', 200);

+ 15 - 15
api-v12/app/Http/Controllers/ExportController.php

@@ -24,15 +24,15 @@ class ExportController extends Controller
     {
         $queryId = Str::uuid();
         $token = AuthApi::getToken($request);
-        switch ($request->get('type', 'chapter')) {
+        switch ($request->input('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'),
+                    'book' => $request->input('book'),
+                    'para' => $request->input('par'),
+                    'channel' => $request->input('channel'),
+                    'format' => $request->input('format'),
+                    'origin' => $request->input('origin'),
+                    'translation' => $request->input('translation'),
                     'queryId' => $queryId,
                 ];
                 if ($token) {
@@ -42,14 +42,14 @@ class ExportController extends Controller
                 break;
             case 'article':
                 $data = [
-                    'id' => $request->get('id'),
-                    'channel' => $request->get('channel'),
-                    'format' => $request->get('format'),
-                    'origin' => $request->get('origin'),
-                    'translation' => $request->get('translation'),
+                    'id' => $request->input('id'),
+                    'channel' => $request->input('channel'),
+                    'format' => $request->input('format'),
+                    'origin' => $request->input('origin'),
+                    'translation' => $request->input('translation'),
                     'queryId' => $queryId,
-                    'anthology' => $request->get('anthology'),
-                    'channel' => $request->get('channel'),
+                    'anthology' => $request->input('anthology'),
+                    'channel' => $request->input('channel'),
                 ];
                 if ($token) {
                     $data['token'] = $token;
@@ -57,7 +57,7 @@ class ExportController extends Controller
                 Mq::publish('export_article', $data);
                 break;
             default:
-                return $this->error('unknown type ' . $request->get('type'), 400, 400);
+                return $this->error('unknown type ' . $request->input('type'), 400, 400);
                 break;
         }
 

+ 40 - 41
api-v12/app/Http/Controllers/ExportWbwController.php

@@ -17,83 +17,82 @@ class ExportWbwController extends Controller
     public function index(Request $request)
     {
         //
-        $sent = explode("\n",$request->get("sent"));
+        $sent = explode("\n", $request->input("sent"));
         $output = [];
         foreach ($sent as $key => $value) {
             # code...
             $sent = [];
             $value = trim($value);
-            $sentId = explode("-",$value);
+            $sentId = explode("-", $value);
             //先查wbw block 拿到block id
-            $block = WbwBlock::where('book_id',$sentId[0])
-                        ->where('paragraph',$sentId[1])
-                        ->select('uid')
-                        ->where('channel_uid',$request->get("channel"))->first();
-            if(!$block){
+            $block = WbwBlock::where('book_id', $sentId[0])
+                ->where('paragraph', $sentId[1])
+                ->select('uid')
+                ->where('channel_uid', $request->input("channel"))->first();
+            if (!$block) {
                 continue;
             }
-            $wbwdata = Wbw::where('book_id',$sentId[0])
-                        ->where('paragraph',$sentId[1])
-                        ->where('wid','>=',$sentId[2])
-                        ->where('wid','<=',$sentId[3])
-                        ->where('block_uid',$block->uid)
-                        ->get();
-            $sent['sid']=$value;
-            $sent['text'] = PaliSentence::where('book',$sentId[0])
-                                        ->where('paragraph',$sentId[1])
-                                        ->where('word_begin',$sentId[2])
-                                        ->where('word_end','<=',$sentId[3])
-                                        ->value('html');
-            $sent['data']=[];
+            $wbwdata = Wbw::where('book_id', $sentId[0])
+                ->where('paragraph', $sentId[1])
+                ->where('wid', '>=', $sentId[2])
+                ->where('wid', '<=', $sentId[3])
+                ->where('block_uid', $block->uid)
+                ->get();
+            $sent['sid'] = $value;
+            $sent['text'] = PaliSentence::where('book', $sentId[0])
+                ->where('paragraph', $sentId[1])
+                ->where('word_begin', $sentId[2])
+                ->where('word_end', '<=', $sentId[3])
+                ->value('html');
+            $sent['data'] = [];
             foreach ($wbwdata as  $wbw) {
                 # code...
-                $data = str_replace("&nbsp;",' ',$wbw->data);
-                $data = str_replace("<br>",' ',$data);
+                $data = str_replace("&nbsp;", ' ', $wbw->data);
+                $data = str_replace("<br>", ' ', $data);
 
                 $xmlString = "<root>" . $data . "</root>";
-                try{
+                try {
                     $xmlWord = simplexml_load_string($xmlString);
-                }catch(Exception $e){
+                } catch (Exception $e) {
                     continue;
                 }
 
                 $wordsList = $xmlWord->xpath('//word');
                 foreach ($wordsList as $word) {
                     $pali = $word->real->__toString();
-                    $case = explode("#",$word->case->__toString()) ;
-                    if(isset($case[0])){
+                    $case = explode("#", $word->case->__toString());
+                    if (isset($case[0])) {
                         $type = $case[0];
-                    }else{
+                    } else {
                         $type = "";
                     }
 
-                    if(isset($case[1])){
+                    if (isset($case[1])) {
                         $grammar = $case[1];
-                        $grammar = str_replace("null","",$grammar);
-                    }else{
+                        $grammar = str_replace("null", "", $grammar);
+                    } else {
                         $grammar = "";
                     }
 
                     $style = $word->style->__toString();
-                    $factormeaning = str_replace("
","",$word->om->__toString());
-                    $factormeaning = str_replace("↓↓","",$factormeaning);
-                    if($type !== '.ctl.' && $style !== 'note' && !empty($pali)){
-                        $sent['data'][]=[
-                            'pali'=>$word->real->__toString(),
-                            'mean' => str_replace("
","",$word->mean->__toString()),
-                            'type' => ltrim($type,'.'),
-                            'grammar' => ltrim(str_replace('$.',',',$grammar),'.') ,
+                    $factormeaning = str_replace("
", "", $word->om->__toString());
+                    $factormeaning = str_replace("↓↓", "", $factormeaning);
+                    if ($type !== '.ctl.' && $style !== 'note' && !empty($pali)) {
+                        $sent['data'][] = [
+                            'pali' => $word->real->__toString(),
+                            'mean' => str_replace("
", "", $word->mean->__toString()),
+                            'type' => ltrim($type, '.'),
+                            'grammar' => ltrim(str_replace('$.', ',', $grammar), '.'),
                             'parent' => $word->parent->__toString(),
                             'factors' => $word->org->__toString(),
                             'factormeaning' => $factormeaning
                         ];
                     }
-
                 }
             }
-            $output[]=$sent;
+            $output[] = $sent;
         }
-        return view('export_wbw',['sentences' => $output] );
+        return view('export_wbw', ['sentences' => $output]);
     }
 
     /**

+ 9 - 9
api-v12/app/Http/Controllers/ForgotPasswordController.php

@@ -29,21 +29,21 @@ class ForgotPasswordController extends Controller
     public function store(Request $request)
     {
         //
-        $user = UserInfo::where('email',$request->get('email'))->first();
-        if(!$user){
-            return $this->error('no user',404,404);
+        $user = UserInfo::where('email', $request->input('email'))->first();
+        if (!$user) {
+            return $this->error('no user', 404, 404);
         }
         $resetToken = Str::uuid();
         $user->reset_password_token = $resetToken;
         $ok = $user->save();
-        if(!$ok){
-            return $this->error('fail on update reset_password_token',500,500);
+        if (!$ok) {
+            return $this->error('fail on update reset_password_token', 500, 500);
         }
 
-        Mail::to($request->get('email'))
-            ->send(new ForgotPassword($resetToken,$request->get('lang'),$request->get('dashboard')));
-        if(Mail::failures()){
-            return $this->error('send email fail',[],200);
+        Mail::to($request->input('email'))
+            ->send(new ForgotPassword($resetToken, $request->input('lang'), $request->input('dashboard')));
+        if (Mail::failures()) {
+            return $this->error('send email fail', [], 200);
         }
         return $this->ok('');
     }

+ 18 - 18
api-v12/app/Http/Controllers/GroupController.php

@@ -25,7 +25,7 @@ class GroupController extends Controller
         //
         $result = false;
         $indexCol = ['uid', 'name', 'description', 'owner', 'updated_at', 'created_at'];
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'studio':
                 # 获取studio内所有group
                 $user = AuthApi::current($request);
@@ -33,13 +33,13 @@ class GroupController extends Controller
                     return $this->error(__('auth.failed'));
                 }
                 //判断当前用户是否有指定的studio的权限
-                $studioId = StudioApi::getIdByName($request->get('name'));
+                $studioId = StudioApi::getIdByName($request->input('name'));
                 if ($user['user_uid'] !== $studioId) {
                     return $this->error(__('auth.failed'));
                 }
 
                 $table = GroupInfo::select($indexCol);
-                if ($request->get('view2', 'my') === 'my') {
+                if ($request->input('view2', 'my') === 'my') {
                     $table = $table->where('owner', $studioId);
                 } else {
                     //我参加的group
@@ -56,20 +56,20 @@ class GroupController extends Controller
                 break;
         }
         if ($request->has("search")) {
-            $table = $table->where('name', 'like', "%" . $request->get("search") . "%");
+            $table = $table->where('name', 'like', "%" . $request->input("search") . "%");
         }
         $count = $table->count();
 
-        if ($request->get('view') === 'studio_list') {
+        if ($request->input('view') === 'studio_list') {
             $table = $table->orderBy('count', 'desc');
         } else {
             $table = $table->orderBy(
-                $request->get('order', 'updated_at'),
-                $request->get('dir', 'desc')
+                $request->input('order', 'updated_at'),
+                $request->input('dir', 'desc')
             );
         }
-        $table->skip($request->get('offset', 0))
-            ->take($request->get('limit', 1000));
+        $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
         if ($result) {
@@ -90,7 +90,7 @@ class GroupController extends Controller
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        $studioId = StudioApi::getIdByName($request->get('studio'));
+        $studioId = StudioApi::getIdByName($request->input('studio'));
         if ($user['user_uid'] !== $studioId) {
             return $this->error(__('auth.failed'));
         }
@@ -115,19 +115,19 @@ class GroupController extends Controller
             return $this->error(__('auth.failed'));
         }
         //判断当前用户是否有指定的studio的权限
-        if ($user['user_uid'] !== StudioApi::getIdByName($request->get('studio_name'))) {
+        if ($user['user_uid'] !== StudioApi::getIdByName($request->input('studio_name'))) {
             return $this->error(__('auth.failed'));
         }
         //查询是否重复
-        if (GroupInfo::where('name', $request->get('name'))->where('owner', $user['user_uid'])->exists()) {
+        if (GroupInfo::where('name', $request->input('name'))->where('owner', $user['user_uid'])->exists()) {
             return $this->error(__('validation.exists', ['name']));
         }
-        $studioId = StudioApi::getIdByName($request->get('studio_name'));
+        $studioId = StudioApi::getIdByName($request->input('studio_name'));
         $group = new GroupInfo;
         DB::transaction(function () use ($group, $request, $user, $studioId) {
             $group->id = app('snowflake')->id();
             $group->uid = Str::uuid();
-            $group->name = $request->get('name');
+            $group->name = $request->input('name');
             $group->owner = $studioId;
             $group->create_time = time() * 1000;
             $group->modify_time = time() * 1000;
@@ -138,7 +138,7 @@ class GroupController extends Controller
             $newMember->user_id = $studioId;
             $newMember->group_id = $group->uid;
             $newMember->power = 0;
-            $newMember->group_name = $request->get('name');
+            $newMember->group_name = $request->input('name');
             $newMember->save();
         });
 
@@ -199,10 +199,10 @@ class GroupController extends Controller
         if ($user['user_uid'] !== $group->owner) {
             return $this->error(__('auth.failed'));
         }
-        $group->name = $request->get('name');
-        $group->description = $request->get('description');
+        $group->name = $request->input('name');
+        $group->description = $request->input('description');
         if ($request->has('status')) {
-            $group->status = $request->get('status');
+            $group->status = $request->input('status');
         }
         $group->create_time = time() * 1000;
         $group->modify_time = time() * 1000;

+ 6 - 6
api-v12/app/Http/Controllers/GroupMemberController.php

@@ -24,17 +24,17 @@ class GroupMemberController extends Controller
         }
         $result = false;
         $indexCol = ['id', 'user_id', 'group_id', 'power', 'level', 'status', 'updated_at', 'created_at'];
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'group':
                 # 获取 group 内所有 成员
                 //判断当前用户是否有指定的 group 的权限
-                if (GroupMember::where('group_id', $request->get('id'))
+                if (GroupMember::where('group_id', $request->input('id'))
                     ->where('user_id', $user['user_uid'])
                     ->exists()
                 ) {
-                    $table = GroupMember::where('group_id', $request->get('id'));
+                    $table = GroupMember::where('group_id', $request->input('id'));
                     //当前用户角色
-                    $power = GroupMember::where('group_id', $request->get('id'))
+                    $power = GroupMember::where('group_id', $request->input('id'))
                         ->where('user_id', $user['user_uid'])
                         ->value('power');
                     $roles = ["owner", "manager", "member"];
@@ -57,8 +57,8 @@ class GroupMemberController extends Controller
             $table = $table->orderBy('created_at');
         }
 
-        $table->skip($request->get('offset', 0))
-            ->take($request->get('limit', 1000));
+        $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
 

+ 17 - 17
api-v12/app/Http/Controllers/InteractiveController.php

@@ -40,36 +40,36 @@ class InteractiveController extends Controller
         //
         $user = AuthApi::current($request);
         $data = [];
-        switch ($request->get('res_type')) {
+        switch ($request->input('res_type')) {
             case 'article':
                 /* qa */
                 $data['qa'] = [
                     'can_create' => false,
                     'can_reply' => false,
                 ];
-                if($user && ArticleController::userCanEditId($user['user_uid'],$res_id)){
+                if ($user && ArticleController::userCanEditId($user['user_uid'], $res_id)) {
                     $data['qa']['can_create'] = true;
                     $data['qa']['can_reply'] = true;
                 }
-                $data['qa']['count'] = Discussion::where('res_id',$res_id)
-                                                ->where('type','qa')
-                                                ->where('status','close')
-                                                ->count();
+                $data['qa']['count'] = Discussion::where('res_id', $res_id)
+                    ->where('type', 'qa')
+                    ->where('status', 'close')
+                    ->count();
                 /* help */
                 $data['help'] = [
                     'can_create' => false,
                     'can_reply' => false,
                 ];
-                if($user){
+                if ($user) {
                     $data['help']['can_reply'] = true;
-                    if(ArticleController::userCanEditId($user['user_uid'],$res_id)){
+                    if (ArticleController::userCanEditId($user['user_uid'], $res_id)) {
                         $data['help']['can_create'] = true;
                     }
                 }
-                $data['help']['count'] = Discussion::where('res_id',$res_id)
-                                                ->where('type','help')
-                                                ->where('status','active')
-                                                ->count();
+                $data['help']['count'] = Discussion::where('res_id', $res_id)
+                    ->where('type', 'help')
+                    ->where('status', 'active')
+                    ->count();
 
 
 
@@ -78,14 +78,14 @@ class InteractiveController extends Controller
                     'can_create' => false,
                     'can_reply' => false,
                 ];
-                if($user){
+                if ($user) {
                     $data['discussion']['can_reply'] = true;
                     $data['discussion']['can_create'] = true;
                 }
-                $data['discussion']['count'] = Discussion::where('res_id',$res_id)
-                                                ->where('type','discussion')
-                                                ->where('status','active')
-                                                ->count();
+                $data['discussion']['count'] = Discussion::where('res_id', $res_id)
+                    ->where('type', 'discussion')
+                    ->where('status', 'active')
+                    ->count();
                 break;
         }
 

+ 17 - 17
api-v12/app/Http/Controllers/InviteController.php

@@ -35,13 +35,13 @@ class InviteController extends Controller
             'created_at',
             'updated_at'
         ]);
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'studio':
-                if (empty($request->get('studio'))) {
+                if (empty($request->input('studio'))) {
                     return $this->error(__('auth.failed'));
                 }
                 //判断当前用户是否有指定的studio的权限
-                if ($user['user_uid'] !== StudioApi::getIdByName($request->get('studio'))) {
+                if ($user['user_uid'] !== StudioApi::getIdByName($request->input('studio'))) {
                     return $this->error(__('auth.failed'));
                 }
                 $table = $table->where('user_uid', $user["user_uid"]);
@@ -54,16 +54,16 @@ class InviteController extends Controller
                 break;
         }
         if ($request->has('search')) {
-            $table = $table->where('email', 'like', '%' . $request->get('search') . "%");
+            $table = $table->where('email', 'like', '%' . $request->input('search') . "%");
         }
         $count = $table->count();
         $table = $table->orderBy(
-            $request->get('order', 'updated_at'),
-            $request->get('dir', 'desc')
+            $request->input('order', 'updated_at'),
+            $request->input('dir', 'desc')
         );
 
-        $table = $table->skip($request->get('offset', 0))
-            ->take($request->get('limit', 1000));
+        $table = $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
         return $this->ok(["rows" => InviteResource::collection($result), "count" => $count]);
@@ -79,13 +79,13 @@ class InviteController extends Controller
     {
         //
         $sender = '';
-        if (!empty($request->get('studio'))) {
+        if (!empty($request->input('studio'))) {
             $user = AuthApi::current($request);
             if (!$user) {
                 return $this->error(__('auth.failed'), 401, 401);
             }
             //判断当前用户是否有指定的studio的权限
-            $studio_id = StudioApi::getIdByName($request->get('studio'));
+            $studio_id = StudioApi::getIdByName($request->input('studio'));
             if ($user['user_uid'] !== $studio_id) {
                 return $this->error(__('auth.failed'));
             }
@@ -96,26 +96,26 @@ class InviteController extends Controller
 
         //查询是否重复
         if (
-            Invite::where('email', $request->get('email'))->exists() ||
-            UserInfo::where('email', $request->get('email'))->exists()
+            Invite::where('email', $request->input('email'))->exists() ||
+            UserInfo::where('email', $request->input('email'))->exists()
         ) {
             return $this->error('email.exists', __('validation.exists', ['email']), 200);
         }
 
         $uuid = Str::uuid();
-        Mail::to($request->get('email'))
+        Mail::to($request->input('email'))
             ->send(new InviteMail(
                 $uuid,
-                $request->get('subject', 'sign up wikipali'),
-                $request->get('lang'),
-                $request->get('dashboard')
+                $request->input('subject', 'sign up wikipali'),
+                $request->input('lang'),
+                $request->input('dashboard')
             ));
         if (Mail::failures()) {
             return $this->error('send email fail', '', 200);
         } else {
             $invite = new Invite;
             $invite->id = $uuid;
-            $invite->email = $request->get('email');
+            $invite->email = $request->input('email');
             $invite->user_uid = $sender;
             $invite->status = 'invited';
             $invite->save();

+ 213 - 0
api-v12/app/Http/Controllers/Library/AnthologyController.php

@@ -0,0 +1,213 @@
+<?php
+
+namespace App\Http\Controllers\Library;
+
+use App\Http\Controllers\Controller;
+use App\Services\CollectionService;
+use Illuminate\Http\Request;
+
+class AnthologyController extends Controller
+{
+    // 封面渐变色池:uid 首字节取余循环,保证同一文集颜色稳定
+    private array $coverGradients = [
+        'linear-gradient(160deg, #2d2010, #4a3010)',
+        'linear-gradient(160deg, #1a2d10, #2d4a18)',
+        'linear-gradient(160deg, #0d1f3c, #1a3660)',
+        'linear-gradient(160deg, #2d1020, #4a1830)',
+        'linear-gradient(160deg, #1a1a2d, #2a2a50)',
+        'linear-gradient(160deg, #1a2820, #2d4438)',
+    ];
+
+    // 作者色池:同上,根据 studio.id 首字节取余
+    private array $authorColors = [
+        '#c8860a',
+        '#2e7d32',
+        '#1565c0',
+        '#6a1b9a',
+        '#c62828',
+        '#00695c',
+        '#4e342e',
+        '#37474f',
+    ];
+
+    public function __construct(private CollectionService $collectionService) {}
+
+    // -------------------------------------------------------------------------
+    // 从 uid / id 字符串中提取一个稳定的整数,用于色池取余
+    // -------------------------------------------------------------------------
+    private function colorIndex(string $uid): int
+    {
+        return hexdec(substr(str_replace('-', '', $uid), 0, 4)) % 255;
+    }
+
+    // -------------------------------------------------------------------------
+    // 将 studio 对象转换为 blade 所需的 author 数组
+    // -------------------------------------------------------------------------
+    private function formatAuthor(array $studio): array
+    {
+        $name     = $studio['nickName'] ?? $studio['studioName'] ?? '未知';
+        $initials = mb_substr($name, 0, 2);
+        $colorIdx = $this->colorIndex($studio['id'] ?? '0');
+
+        return [
+            'name'     => $name,
+            'initials' => $initials,
+            'color'    => $this->authorColors[$colorIdx % count($this->authorColors)],
+            'avatar'   => $studio['avatar'] ?? null,
+        ];
+    }
+
+    // -------------------------------------------------------------------------
+    // 将 CollectionResource 单条转换为 index 卡片所需格式
+    // -------------------------------------------------------------------------
+    private function formatForCard(array $item, int $index): array
+    {
+        $uid      = $item['uid'];
+        $colorIdx = $this->colorIndex($uid);
+
+        // article_list => 章节标题列表
+        $chapters = collect($item['article_list'] ?? [])
+            ->pluck('title')
+            ->toArray();
+
+        return [
+            'id'             => $uid,
+            'title'          => $item['title'],
+            'subtitle'       => $item['subtitle'] ?? null,
+            'description'    => $item['summary'] ?? null,
+            'cover_image'    => null, // 暂无封面图字段,留空走渐变
+            'cover_gradient' => $this->coverGradients[$colorIdx % count($this->coverGradients)],
+            'author'         => $this->formatAuthor($item['studio'] ?? []),
+            'chapters'       => $chapters,
+            'children_number' => $item['childrenNumber'] ?? count($chapters),
+            'updated_at'     => isset($item['updated_at'])
+                ? \Carbon\Carbon::parse($item['updated_at'])->format('Y-m-d')
+                : '',
+        ];
+    }
+
+    // =========================================================================
+    // index
+    // =========================================================================
+    public function index(Request $request)
+    {
+        $perPage     = 10;
+        $currentPage = (int) $request->input('page', 1);
+
+        $result = $this->collectionService->getPublicList($perPage, $currentPage);
+
+        // $result['data'] 是 CollectionResource collection,转为数组逐条加工
+        $items = collect($result['data'])
+            ->values()
+            ->map(fn($item, $i) => $this->formatForCard(
+                is_array($item) ? $item : $item->toArray(request()),
+                $i
+            ));
+
+        $total = $result['total'];
+
+        $paginator = new \Illuminate\Pagination\LengthAwarePaginator(
+            $items,
+            $total,
+            $perPage,
+            $currentPage,
+            ['path' => $request->url(), 'query' => $request->query()]
+        );
+
+        // 侧边栏作者列表:从当页数据聚合(如需完整列表可单独查询)
+        $authors = $items
+            ->groupBy(fn($i) => $i['author']['name'])
+            ->map(fn($group, $name) => [
+                'name'     => $name,
+                'initials' => $group->first()['author']['initials'],
+                'color'    => $group->first()['author']['color'],
+                'avatar'   => $group->first()['author']['avatar'],
+                'count'    => $group->count(),
+            ])
+            ->values();
+
+        return view('library.anthology.index', [
+            'anthologies' => $paginator,
+            'authors'     => $authors,
+            'total'       => $total,
+        ]);
+    }
+
+    // =========================================================================
+    // show
+    // =========================================================================
+    public function show(string $uid)
+    {
+        $result = $this->collectionService->getCollection($uid);
+
+        if (isset($result['error'])) {
+            abort($result['code'] ?? 404, $result['error']);
+        }
+
+        $raw = is_array($result['data'])
+            ? $result['data']
+            : $result['data']->toArray(request());
+
+        $colorIdx = $this->colorIndex($raw['uid']);
+        $author   = $this->formatAuthor($raw['studio'] ?? []);
+
+        // 只保留 level=1 的顶级章节
+        $articles = collect($raw['article_list'] ?? [])
+            ->filter(fn($a) => ($a['level'] ?? 1) === 1)
+            ->values()
+            ->map(fn($a, $i) => [
+                'id'    => $a['article_id'],
+                'order' => $i + 1,
+                'title' => $a['title'],
+            ])
+            ->toArray();
+
+        $anthology = [
+            'id'             => $raw['uid'],
+            'title'          => $raw['title'],
+            'subtitle'       => $raw['subtitle'] ?? null,
+            'description'    => $raw['summary'] ?? $raw['subtitle'] ?? null,
+            'about'          => $raw['summary'] ?? null,
+            'cover_image'    => null,
+            'cover_gradient' => $this->coverGradients[$colorIdx % count($this->coverGradients)],
+            'tags'           => array_filter([$raw['lang'] ?? null]),
+            'language'       => $raw['lang'] ?? null,
+            'category'       => null,
+            'created_at'     => isset($raw['created_at'])
+                ? \Carbon\Carbon::parse($raw['created_at'])->format('Y-m-d')
+                : '',
+            'updated_at'     => isset($raw['updated_at'])
+                ? \Carbon\Carbon::parse($raw['updated_at'])->format('Y-m-d')
+                : '',
+            'children_number' => $raw['childrenNumber'] ?? 0,
+            'author'         => array_merge($author, [
+                'bio'           => null,
+                'article_count' => $raw['childrenNumber'] ?? 0,
+            ]),
+            'articles'       => $articles,
+        ];
+
+        // 相关文集:同作者其他文集
+        $relatedResult = $this->collectionService->getPublicList(20, 1);
+        $related = collect($relatedResult['data'])
+            ->map(fn($item) => is_array($item) ? $item : $item->toArray(request()))
+            ->filter(
+                fn($item) =>
+                $item['uid'] !== $uid &&
+                    ($item['studio']['id'] ?? '') === ($raw['studio']['id'] ?? '')
+            )
+            ->take(3)
+            ->map(fn($item) => [
+                'id'             => $item['uid'],
+                'title'          => $item['title'],
+                'author_name'    => $item['studio']['nickName'] ?? $item['studio']['studioName'] ?? '',
+                'cover_gradient' => $this->coverGradients[$this->colorIndex($item['uid']) % count($this->coverGradients)],
+            ])
+            ->values();
+
+        return view('library.anthology.show', [
+            'anthology' => $anthology,
+            'related'   => $related,
+        ]);
+    }
+}

+ 212 - 0
api-v12/app/Http/Controllers/Library/AnthologyReadController.php

@@ -0,0 +1,212 @@
+<?php
+
+namespace App\Http\Controllers\Library;
+
+use App\Http\Controllers\Controller;
+use App\Services\CollectionService;
+use App\Services\ArticleService;
+use Illuminate\Http\Request;
+
+class AnthologyReadController extends Controller
+{
+    public function __construct(
+        private CollectionService $collectionService,
+        private ArticleService    $articleService,
+    ) {}
+
+    // =========================================================================
+    // read
+    // GET /library/anthology/{anthology}/read/{article}
+    // =========================================================================
+    public function read(Request $request, string $anthologyId, string $articleId)
+    {
+        // ── 1. 获取文集信息 ───────────────────────────────────────────────────
+        $colResult = $this->collectionService->getCollection($anthologyId);
+        if (isset($colResult['error'])) {
+            abort($colResult['code'] ?? 404, $colResult['error']);
+        }
+
+        $col = is_array($colResult['data'])
+            ? $colResult['data']
+            : $colResult['data']->toArray(request());
+
+        // ── 2. 构建目录(方案A:展开祖先链,其余折叠) ───────────────────────
+        $fullArticleList = collect($col['article_list'] ?? []);
+        $toc = $this->buildCollapsedToc($fullArticleList->toArray(), $articleId);
+
+        // ── 3. 获取当前文章内容 ───────────────────────────────────────────────
+        $artResult = $this->articleService->getArticle($articleId);
+        if (isset($artResult['error'])) {
+            abort($artResult['code'] ?? 404, $artResult['error']);
+        }
+
+        // ArticleResource 需要 format=html
+        $urlParam = [
+            'mode' => 'read',
+            'format' => 'html',
+            'anthology' => $anthologyId,
+            'origin' => 'true',
+            'paragraph' => true,
+        ];
+        if ($request->has('channel')) {
+            $urlParam['channel'] = $request->input('channel');
+        }
+        $fakeRequest = Request::create('', 'GET', $urlParam);
+        $artResource = $artResult['data'];
+        $artArray    = $artResource->toArray($fakeRequest);
+
+        // content 统一包装成 book.read 期望的格式:
+        // [ ['id'=>..., 'level'=>1, 'text'=>[[ html_string ]]], ... ]
+        $content = [[
+            'id'    => $articleId,
+            'level' => 100,
+            'text'  => [[$artArray['html'] ?? $artArray['content'] ?? 'null']],
+        ]];
+
+        // ── 4. 计算翻页(pagination) ─────────────────────────────────────────
+
+        $currentIndex = $fullArticleList->search(
+            fn($a) => $a['article_id'] === $articleId
+        );
+
+        $prev = null;
+        $next = null;
+
+        if ($currentIndex !== false) {
+            if ($currentIndex > 0) {
+                $prevItem = $fullArticleList[$currentIndex - 1];
+                $prev = [
+                    'id'    => $prevItem['article_id'],
+                    'title' => $prevItem['title'],
+                ];
+            }
+            if ($currentIndex < $fullArticleList->count() - 1) {
+                $nextItem = $fullArticleList[$currentIndex + 1];
+                $next = [
+                    'id'    => $nextItem['article_id'],
+                    'title' => $nextItem['title'],
+                ];
+            }
+        }
+
+        $pagination = [
+            'start' => 0,
+            'end'   => 0,
+            'prev'  => $prev,
+            'next'  => $next,
+        ];
+
+        // ── 5. 组装 $book(对齐 book.read 的数据结构) ───────────────────────
+        $studio = $col['studio'] ?? [];
+
+        // blade 用 $book['publisher']->username / ->nickname,必须是对象
+        $publisher = (object) [
+            'username' => $studio['studioName'] ?? '',
+            'nickname' => $studio['nickName']   ?? $studio['studioName'] ?? '',
+        ];
+
+        $book = [
+            'id'          => $articleId,
+            'title'       => $artArray['title'] ?? ($col['title'] ?? ''),
+            'author'      => $studio['nickName'] ?? $studio['studioName'] ?? '',
+            'publisher'   => $publisher,
+            'type'        => '',
+            'category_id' => null,
+            'cover'       => null,
+            'description' => $col['summary'] ?? '',
+            'language'    => $col['lang'] ?? '',
+            'anthology'   => [
+                'id'    => $anthologyId,
+                'title' => $col['title'] ?? '',
+            ],
+            'categories'  => [],
+            'tags'        => [],
+            'downloads'   => [],
+            'toc'         => $toc,
+            'pagination'  => $pagination,
+            'content'     => $content,
+        ];
+        $channels = $this->articleService->articleChannels($articleId);
+        // blade 里有 $relatedBooks,传空数组防止 undefined variable
+        $relatedBooks = [];
+        $editor_link = config('mint.server.dashboard_base_path') . "/workspace/anthology/{$anthologyId}/{$book['id']}";
+
+        // 翻页路由需要 anthologyId,传给 blade 供覆盖路由使用
+        return view('library.book.read', compact('book', 'relatedBooks', 'anthologyId', 'channels', 'editor_link'));
+    }
+
+    // =========================================================================
+    // buildCollapsedToc
+    //
+    // 规则:
+    //   1. 所有 level=1 节点始终显示
+    //   2. 找出当前节点的「祖先链」(从 root 到当前节点经过的每个节点)
+    //   3. 祖先链上每个节点的「直接子节点」都展开显示
+    //   4. 当前节点本身若有子节点,同样展开一级
+    //   5. 其余节点隐藏
+    // =========================================================================
+    private function buildCollapsedToc(array $list, string $currentId): array
+    {
+        // ── Step 1:为每个节点推导父节点 id ──────────────────────────────────
+        // article_list 是有序的,父节点 = 当前节点之前最近的 level 更小的节点
+        $parents = [];  // article_id => parent_article_id | null
+        $stack   = [];  // 维护祖先栈 [ ['id'=>..., 'level'=>...], ... ]
+
+        foreach ($list as $item) {
+            $id    = $item['article_id'];
+            $level = (int) ($item['level'] ?? 1);
+
+            // 弹出所有 level >= 当前 level 的栈顶
+            while (!empty($stack) && $stack[count($stack) - 1]['level'] >= $level) {
+                array_pop($stack);
+            }
+
+            $parents[$id] = empty($stack) ? null : $stack[count($stack) - 1]['id'];
+            $stack[]      = ['id' => $id, 'level' => $level];
+        }
+
+        // ── Step 2:找出当前节点的祖先链 ─────────────────────────────────────
+        $ancestorIds = [];
+        $cursor      = $currentId;
+        while (isset($parents[$cursor]) && $parents[$cursor] !== null) {
+            $cursor        = $parents[$cursor];
+            $ancestorIds[] = $cursor;
+        }
+        $ancestorSet = array_flip($ancestorIds);
+
+        // ── Step 3:需要展开子节点的集合 = 祖先链 + 当前节点 ─────────────────
+        $expandParentSet              = $ancestorSet;
+        $expandParentSet[$currentId]  = true;
+
+        // ── Step 4:过滤构建 toc ──────────────────────────────────────────────
+        $toc = [];
+        foreach ($list as $item) {
+            $id       = $item['article_id'];
+            $level    = (int) ($item['level'] ?? 1);
+            $parentId = $parents[$id];
+            $isActive = $id === $currentId;
+
+            // 显示条件:
+            //   a) level=1(始终显示)
+            //   b) 父节点在 expandParentSet 中(祖先链或当前节点的直接子节点)
+            $visible = $level === 1
+                || ($parentId !== null && isset($expandParentSet[$parentId]));
+
+            if (!$visible) {
+                continue;
+            }
+
+            $toc[] = [
+                'id'       => $id,
+                'title'    => $item['title'],
+                'summary'  => '',
+                'progress' => 0,
+                'level'    => $level,
+                'disabled' => $isActive,
+                'active'   => $isActive,
+            ];
+        }
+
+        return $toc;
+    }
+}

+ 19 - 8
api-v12/app/Http/Controllers/BookController.php → api-v12/app/Http/Controllers/Library/BookController.php

@@ -1,13 +1,16 @@
 <?php
 
-namespace App\Http\Controllers;
+namespace App\Http\Controllers\Library;
+
+use App\Http\Controllers\Controller;
 
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Storage;
 use App\Models\ProgressChapter;
 use App\Models\PaliText;
 use App\Models\Sentence;
 
+use App\Services\ChapterService;
+
 class BookController extends Controller
 {
     protected $maxChapterLen = 50000;
@@ -45,9 +48,13 @@ class BookController extends Controller
     }
 
 
-    public function read($id)
+    public function read(Request $request, $id)
     {
-        $bookRaw = $this->loadBook($id);
+        $chapterService = app(ChapterService::class);
+        [$book, $para] = explode('-', $id);
+        $channelId = $request->input('channel');
+        $chapterUid = $chapterService->getUidByChannel($book, $para, $channelId);
+        $bookRaw = $this->loadBook($chapterUid);
 
         if (!$bookRaw) {
             abort(404);
@@ -59,8 +66,11 @@ class BookController extends Controller
         $book['categories'] = $this->getBookCategory($bookRaw->book, $bookRaw->para);
         $book['tags'] = [];
         $book['pagination'] = $this->pagination($bookRaw);
-        $book['content'] = $this->getBookContent($id);
-        return view('library.book.read', compact('book'));
+        $book['content'] = $this->getBookContent($chapterUid);
+        $channels = $chapterService->publicChannels($bookRaw->book, $bookRaw->para);
+        $editor_link = config('mint.server.dashboard_base_path') . "/workspace/tipitaka/chapter/{$id}?channel={$channelId}";
+
+        return view('library.book.read', compact('book', 'channels', 'editor_link'));
     }
 
     private function loadBook($id)
@@ -111,7 +121,7 @@ class BookController extends Controller
             ->whereBetween('para', [$start, $end])
             ->where('channel_id', $channelId)->orderBy('para')->get();
 
-        $toc = $paliTexts->map(function ($paliText) use ($chapters) {
+        $toc = $paliTexts->map(function ($paliText) use ($chapters, $channelId) {
             $title = $paliText->toc;
             if (count($chapters) > 0) {
                 $found = array_filter($chapters->toArray(), function ($chapter) use ($paliText) {
@@ -130,7 +140,8 @@ class BookController extends Controller
                 }
             }
             return [
-                "id" => $id ?? '',
+                "id" => "{$paliText->book}-{$paliText->paragraph}",
+                "channel" => $channelId,
                 "title" => $title,
                 "summary" => $summary ?? "",
                 "progress" => $progress ?? 0,

+ 82 - 0
api-v12/app/Http/Controllers/Library/SearchController.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Http\Controllers\Library;
+
+use App\Http\Controllers\Controller;
+
+use Illuminate\Http\Request;
+use App\Services\OpenSearchService;
+use App\DTO\Search\SearchDataDTO;
+
+
+class SearchController extends Controller
+{
+    public function search(Request $request)
+    {
+        $query    = trim($request->input('q', ''));
+        $category = $request->input('category', 'all');
+        $page     = max(1, (int) $request->input('page', 1));
+        $perPage  = 10;
+        $lang = $request->input('lang');
+
+        $search = app(OpenSearchService::class);
+        // 组装搜索参数
+        $params = [
+            'query'        => $query,
+            'pageSize'     => $perPage,
+            'page'     => $page,
+            'language'     => $lang,
+            'resourceType'     => $request->input('type'),
+        ];
+        $result = $search->search($params);
+
+        $dto = SearchDataDTO::fromArray($result);
+        $results = [];
+        foreach ($dto->hits->items as $key => $item) {
+            $results[] = [
+                'word'     => 'word',
+                'zh'       => $item->title,
+                'lang'     => 'pi',
+                'category' => '法义术语',
+                'quality'  => 'featured',
+                'snippet'  => $item->highlight,
+                'updated'  => '2025-11-12',
+            ];
+        }
+
+        $category = $dto->aggregations->category->buckets;
+
+        // 分页对象(兼容 Blade paginator 风格)
+        $pagination = [
+            'total'        => $dto->hits->total,
+            'per_page'     => $perPage,
+            'current_page' => $page,
+            'last_page'    => max(1, (int) ceil($dto->hits->total / $perPage)),
+        ];
+
+        return view('wiki.search', [
+            'lang'           => $lang,
+            'query'          => $query,
+            'results'        => $results,
+            'pagination'     => $pagination,
+            'category'       => 'all',
+            'filters'     => $category,
+            'categories'     => $this->types(),
+            'recentUpdates'  => [],
+        ]);
+    }
+    // ── Helpers ──────────────────────────────────────────────────
+
+    private function types(): array
+    {
+        return [
+            ['slug' => 'all',      'label' => '全部'],
+            ['slug' => 'term',     'label' => '法义术语'],
+            ['slug' => 'original_text',   'label' => '原文'],
+            ['slug' => 'translation',     'label' => '译文'],
+            ['slug' => 'article',   'label' => '文章'],
+            ['slug' => 'course', 'label' => '课程'],
+            ['slug' => 'dictionary',    'label' => '字典'],
+        ];
+    }
+}

+ 62 - 58
api-v12/app/Http/Controllers/LikeController.php

@@ -19,21 +19,23 @@ class LikeController extends Controller
     public function index(Request $request)
     {
         //
-        switch ($request->get("view")) {
+        switch ($request->input("view")) {
             case 'count':
                 # code...
-                $result = Like::where("target_id",$request->get("target_id"))
-                                ->groupBy("type")
-                                ->select("type")
-                                ->selectRaw("count(*)")
-                                ->get();
+                $result = Like::where("target_id", $request->input("target_id"))
+                    ->groupBy("type")
+                    ->select("type")
+                    ->selectRaw("count(*)")
+                    ->get();
                 $user = AuthApi::current($request);
-                if($user){
+                if ($user) {
                     foreach ($result as $key => $value) {
-                        $curr = Like::where(["target_id"=>$request->get("target_id"),
-                                        'type'=>$value->type,
-                                        'user_id'=>$user["user_uid"]])->first();
-                        if($curr){
+                        $curr = Like::where([
+                            "target_id" => $request->input("target_id"),
+                            'type' => $value->type,
+                            'user_id' => $user["user_uid"]
+                        ])->first();
+                        if ($curr) {
                             $result[$key]->selected = true;
                             $result[$key]->my_id = $curr->id;
                         }
@@ -42,20 +44,20 @@ class LikeController extends Controller
                 return $this->ok($result);
                 break;
             case 'target':
-                $table = Like::where("target_id",$request->get("target_id"));
+                $table = Like::where("target_id", $request->input("target_id"));
                 break;
             default:
                 # code...
                 break;
         }
-        if($request->has("type")){
-            $table = $table->where('type',$request->get("type"));
+        if ($request->has("type")) {
+            $table = $table->where('type', $request->input("type"));
         }
         $count = $table->count();
         $result = $table->get();
         return $this->ok([
-            "rows"=>LikeResource::collection($result),
-            "count"=>$count
+            "rows" => LikeResource::collection($result),
+            "count" => $count
         ]);
     }
 
@@ -69,31 +71,33 @@ class LikeController extends Controller
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
         $param = $request->all();
-        $user_id = $request->get('user_id',$user["user_uid"]);
-        $like = Like::firstOrNew([
-            'type'=>$param['type'],
-            'target_id'=>$param['target_id'],
-            'target_type'=>$param['target_type'],
-            'user_id' =>  $user_id,
-        ],
-        [
-            'id'=>Str::uuid(),
-        ]);
+        $user_id = $request->input('user_id', $user["user_uid"]);
+        $like = Like::firstOrNew(
+            [
+                'type' => $param['type'],
+                'target_id' => $param['target_id'],
+                'target_type' => $param['target_type'],
+                'user_id' =>  $user_id,
+            ],
+            [
+                'id' => Str::uuid(),
+            ]
+        );
         $like->save();
         $output = [
-            'id'=>$like->id,
-            'type'=>$param['type'],
-            'target_id'=>$param['target_id'],
-            'target_type'=>$param['target_type'],
+            'id' => $like->id,
+            'type' => $param['type'],
+            'target_id' => $param['target_id'],
+            'target_type' => $param['target_type'],
             'user_id' => $user_id,
-            'count'=>Like::where('target_id',$param['target_id'])
-                        ->where('type',$param['type'])->count(),
-            'selected'=>true,
-            'my_id'=>$like->id,
+            'count' => Like::where('target_id', $param['target_id'])
+                ->where('type', $param['type'])->count(),
+            'selected' => true,
+            'my_id' => $like->id,
             'user' => UserApi::getByUuid($user_id),
         ];
         return $this->ok($output);
@@ -128,47 +132,47 @@ class LikeController extends Controller
      * @param  \App\Models\Like  $like
      * @return \Illuminate\Http\Response
      */
-    public function destroy(Request $request,Like $like)
+    public function destroy(Request $request, Like $like)
     {
         //
         $user = AuthApi::current($request);
-        if(!$user){
+        if (!$user) {
             return $this->error(__('auth.failed'));
         }
-        if($like->user_id===$user["user_uid"]){
+        if ($like->user_id === $user["user_uid"]) {
             //移除自己
             $delete = $like->delete();
-            if($delete){
+            if ($delete) {
                 $output = [
-                    'type'=>$like['type'],
-                    'count'=>Like::where('target_id',$like['target_id'])
-                                ->where('type',$like['type'])->count(),
-                    'selected'=>false,
+                    'type' => $like['type'],
+                    'count' => Like::where('target_id', $like['target_id'])
+                        ->where('type', $like['type'])->count(),
+                    'selected' => false,
                 ];
                 return $this->ok($output);
-            }else{
-                $this->error('未知错误',200,200);
+            } else {
+                $this->error('未知错误', 200, 200);
             }
-
-        }else{
-            return $this->error(_('auth.failed'),403,403);
+        } else {
+            return $this->error(_('auth.failed'), 403, 403);
         }
     }
-    public function delete(Request $request){
-        if(!isset($_COOKIE["user_uid"])){
+    public function delete(Request $request)
+    {
+        if (!isset($_COOKIE["user_uid"])) {
             return $this->error("no login");
         }
         $param = [
-            "id"=>$request->get('id'),
-            'user_id'=>$_COOKIE["user_uid"]
+            "id" => $request->input('id'),
+            'user_id' => $_COOKIE["user_uid"]
         ];
         $del = Like::where($param)->delete();
-        $count = Like::where('target_id',$request->get('target_id'))
-                    ->where('type',$request->get('type'))
-                    ->count();
+        $count = Like::where('target_id', $request->input('target_id'))
+            ->where('type', $request->input('type'))
+            ->count();
         return $this->ok([
-            'deleted'=>$del,
-            'count'=>$count
-            ]);
+            'deleted' => $del,
+            'count' => $count
+        ]);
     }
 }

+ 15 - 15
api-v12/app/Http/Controllers/ModelLogController.php

@@ -22,10 +22,10 @@ class ModelLogController extends Controller
         if (!$user) {
             return $this->error(__('auth.failed'));
         }
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'model':
                 # code..
-                $table = ModelLog::where('model_id', $request->get('id'));
+                $table = ModelLog::where('model_id', $request->input('id'));
                 break;
 
             default:
@@ -33,16 +33,16 @@ class ModelLogController extends Controller
                 break;
         }
         if ($request->has('search')) {
-            $table = $table->where('email', 'like', '%' . $request->get('search') . "%");
+            $table = $table->where('email', 'like', '%' . $request->input('search') . "%");
         }
         $count = $table->count();
         $table = $table->orderBy(
-            $request->get('order', 'updated_at'),
-            $request->get('dir', 'desc')
+            $request->input('order', 'updated_at'),
+            $request->input('dir', 'desc')
         );
 
-        $table = $table->skip($request->get('offset', 0))
-            ->take($request->get('limit', 20));
+        $table = $table->skip($request->input('offset', 0))
+            ->take($request->input('limit', 20));
 
         $result = $table->get();
         return $this->ok(["rows" => ModelLogResource::collection($result), "total" => $count]);
@@ -59,14 +59,14 @@ class ModelLogController extends Controller
         //
         $modelLog = new ModelLog();
         $modelLog->uid = Str::uuid();
-        $modelLog->model_id = $request->get('model_id');
-        $modelLog->request_at = $request->get('request_at');
-        $modelLog->request_headers = $request->get('request_headers');
-        $modelLog->request_data = $request->get('request_data');
-        $modelLog->response_headers = $request->get('response_headers');
-        $modelLog->response_data = $request->get('response_data');
-        $modelLog->status = $request->get('status');
-        $modelLog->success = $request->get('success', true);
+        $modelLog->model_id = $request->input('model_id');
+        $modelLog->request_at = $request->input('request_at');
+        $modelLog->request_headers = $request->input('request_headers');
+        $modelLog->request_data = $request->input('request_data');
+        $modelLog->response_headers = $request->input('response_headers');
+        $modelLog->response_data = $request->input('response_data');
+        $modelLog->status = $request->input('status');
+        $modelLog->success = $request->input('success', true);
         $modelLog->save();
         return $this->ok(new ModelLogResource($modelLog));
     }

+ 4 - 4
api-v12/app/Http/Controllers/NissayaCardController.php

@@ -45,9 +45,9 @@ class NissayaCardController extends Controller
     {
         //
         $cardData = [];
-        App::setLocale($request->get('lang'));
+        App::setLocale($request->input('lang'));
         $localTerm = ChannelApi::getSysChannel(
-            "_System_Grammar_Term_" . strtolower($request->get('lang')) . "_",
+            "_System_Grammar_Term_" . strtolower($request->input('lang')) . "_",
             "_System_Grammar_Term_en_"
         );
         if (!$localTerm) {
@@ -166,7 +166,7 @@ class NissayaCardController extends Controller
                  */
                 $arrLocalEnding = array();
                 $localEndings = NissayaEnding::where('relation', $relation['name'])
-                    ->where('lang', $request->get('lang'))
+                    ->where('lang', $request->input('lang'))
                     ->get();
                 foreach ($localEndings as $localEnding) {
                     if (empty($localEnding->from) || $localEnding->from === $relation->from) {
@@ -186,7 +186,7 @@ class NissayaCardController extends Controller
             }
         }
 
-        if ($request->get('content_type', 'markdown') === 'markdown') {
+        if ($request->input('content_type', 'markdown') === 'markdown') {
             $m = new \Mustache_Engine(array('entity_flags' => ENT_QUOTES));
             $tpl = file_get_contents(resource_path("mustache/nissaya_ending_card.tpl"));
             $result = $m->render($tpl, $cardData);

+ 25 - 25
api-v12/app/Http/Controllers/NissayaEndingController.php

@@ -38,33 +38,33 @@ class NissayaEndingController extends Controller
         ]);
 
         if (($request->has('case'))) {
-            $table->whereIn('case', explode(",", $request->get('case')));
+            $table->whereIn('case', explode(",", $request->input('case')));
         }
 
         if (($request->has('lang'))) {
-            $table->whereIn('lang', explode(",", $request->get('lang')));
+            $table->whereIn('lang', explode(",", $request->input('lang')));
         }
 
         if (($request->has('relation'))) {
-            $table->where('relation', $request->get('relation'));
+            $table->where('relation', $request->input('relation'));
         }
         if (($request->has('case'))) {
-            $table->where('case', $request->get('case'));
+            $table->where('case', $request->input('case'));
         }
 
         if (($request->has('search'))) {
-            $table->where('ending', 'like', "%" . $request->get('search') . "%");
+            $table->where('ending', 'like', "%" . $request->input('search') . "%");
         }
 
         $count = $table->count();
 
         $table->orderBy(
-            $request->get('order', 'updated_at'),
-            $request->get('dir', 'desc')
+            $request->input('order', 'updated_at'),
+            $request->input('dir', 'desc')
         );
 
-        $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 1000));
+        $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 1000));
         $result = $table->get();
 
         return $this->ok(["rows" => NissayaEndingResource::collection($result), "count" => $count]);
@@ -73,7 +73,7 @@ class NissayaEndingController extends Controller
     public function vocabulary(Request $request)
     {
         $result = NissayaEnding::select(['ending'])
-            ->where('lang', $request->get('lang'))
+            ->where('lang', $request->input('lang'))
             ->groupBy('ending')
             ->get();
         return $this->ok(["rows" => $result, "count" => count($result)]);
@@ -100,10 +100,10 @@ class NissayaEndingController extends Controller
         $new->ending = $validated['ending'];
         $new->strlen = mb_strlen($validated['ending'], "UTF-8");
         $new->lang = $validated['lang'];
-        $new->relation = $request->get('relation');
-        $new->case = $request->get('case');
+        $new->relation = $request->input('relation');
+        $new->case = $request->input('case');
         if ($request->has('from')) {
-            $new->from = json_encode($request->get('from'), JSON_UNESCAPED_UNICODE);
+            $new->from = json_encode($request->input('from'), JSON_UNESCAPED_UNICODE);
         } else {
             $new->from = null;
         }
@@ -140,26 +140,26 @@ class NissayaEndingController extends Controller
         }
         //查询是否重复
         /*
-        $table = NissayaEnding::where('ending',$request->get('ending'))
-                 ->where('lang',$request->get('lang'))
-                 ->where('relation',$request->get('relation'));
-        $from = json_encode($request->get('from'),JSON_UNESCAPED_UNICODE);
+        $table = NissayaEnding::where('ending',$request->input('ending'))
+                 ->where('lang',$request->input('lang'))
+                 ->where('relation',$request->input('relation'));
+        $from = json_encode($request->input('from'),JSON_UNESCAPED_UNICODE);
         if(empty($from)){
             $table = $table->whereNull('from');
         }else{
-            $json = $request->get('from');
+            $json = $request->input('from');
             $table = $table->whereJsonContains('from',['case'=>$json['case']]);
         }
         if($table->exists()){
             return $this->error(__('validation.exists',['name']));
         }
 */
-        $nissayaEnding->ending = $request->get('ending');
-        $nissayaEnding->strlen = mb_strlen($request->get('ending'), "UTF-8");
-        $nissayaEnding->lang = $request->get('lang');
-        $nissayaEnding->relation = $request->get('relation');
-        if ($request->has('from') && !empty($request->get('from'))) {
-            $nissayaEnding->from = json_encode($request->get('from'), JSON_UNESCAPED_UNICODE);
+        $nissayaEnding->ending = $request->input('ending');
+        $nissayaEnding->strlen = mb_strlen($request->input('ending'), "UTF-8");
+        $nissayaEnding->lang = $request->input('lang');
+        $nissayaEnding->relation = $request->input('relation');
+        if ($request->has('from') && !empty($request->input('from'))) {
+            $nissayaEnding->from = json_encode($request->input('from'), JSON_UNESCAPED_UNICODE);
         } else {
             $nissayaEnding->from = null;
         }
@@ -222,7 +222,7 @@ class NissayaEndingController extends Controller
             return $this->error(__('auth.failed'));
         }
 
-        $filename = $request->get('filename');
+        $filename = $request->input('filename');
         $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
         $reader->setReadDataOnly(true);
         $spreadsheet = $reader->load($filename);

+ 12 - 12
api-v12/app/Http/Controllers/NotificationController.php

@@ -24,7 +24,7 @@ class NotificationController extends Controller
             Log::error('notification auth failed {request}', ['request' => $request]);
             return $this->error(__('auth.failed'), 401, 401);
         }
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'to':
                 $table = Notification::where('to', $user['user_uid']);
                 $unread = Notification::where('to', $user['user_uid'])
@@ -33,14 +33,14 @@ class NotificationController extends Controller
         }
 
         if ($request->has('status')) {
-            $table = $table->whereIn('status', explode(',', $request->get('status')));
+            $table = $table->whereIn('status', explode(',', $request->input('status')));
         }
         $count = $table->count();
 
-        $table = $table->orderBy($request->get('order', 'created_at'), $request->get('dir', 'desc'));
+        $table = $table->orderBy($request->input('order', 'created_at'), $request->input('dir', 'desc'));
 
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 10));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 10));
 
         $result = $table->get();
 
@@ -70,12 +70,12 @@ class NotificationController extends Controller
         $new = new Notification;
         $new->id = Str::uuid();
         $new->from = $user['user_uid'];
-        $new->to = $request->get('to');
-        $new->url = $request->get('url');
-        $new->content = $request->get('content');
-        $new->res_type = $request->get('res_type');
-        $new->res_id = $request->get('res_id');
-        $new->channel = $request->get('channel');
+        $new->to = $request->input('to');
+        $new->url = $request->input('url');
+        $new->content = $request->input('content');
+        $new->res_type = $request->input('res_type');
+        $new->res_id = $request->input('res_id');
+        $new->channel = $request->input('channel');
         $new->save();
 
         return $this->ok(new NotificationResource($new));
@@ -125,7 +125,7 @@ class NotificationController extends Controller
             return $this->error(__('auth.failed'), 401, 401);
         }
         if ($notification->to === $user['user_uid']) {
-            $notification->status = $request->get('status', 'read');
+            $notification->status = $request->input('status', 'read');
             $notification->save();
             $unread = Notification::where('to', $notification->to)
                 ->where('status', 'unread')

+ 5 - 47
api-v12/app/Http/Controllers/OfflineIndexController.php

@@ -3,14 +3,12 @@
 namespace App\Http\Controllers;
 
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Redis;
-use Illuminate\Support\Facades\Log;
-use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Facades\App;
+
+use App\Services\PacketService;
 
 class OfflineIndexController extends Controller
 {
+
     /**
      * Display a listing of the resource.
      *
@@ -19,50 +17,10 @@ class OfflineIndexController extends Controller
     public function index(Request $request)
     {
         //
-        $key = '/offline/index';
+        $index = app(PacketService::class)->index($request->input('file', null));
 
-        if (!Cache::has($key)) {
-            return [];
-        }
-        $fileInfo = Cache::get($key);
-        $output = [];
-        foreach ($fileInfo as $key => $file) {
-            if ($request->has('file')) {
-                if ($file['id'] !== $request->get('file')) {
-                    continue;
-                }
-            }
-            $zipFile = $file['filename'];
-            $bucket = config('mint.attachments.bucket_name.temporary');
-            $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)',
-            ];
-            $file['url'] = $url;
-            Log::debug('offline-index: file info=', ['data' => $file]);
-            $output[] = $file;
-        }
         return response()->json(
-            $output,
+            $index,
             200,
             [
                 'Content-Type' => 'application/json;charset=UTF-8',

+ 1 - 1
api-v12/app/Http/Controllers/PaliBookCategoryController.php

@@ -35,7 +35,7 @@ class PaliBookCategoryController extends Controller
      */
     public function show($file)
     {
-        $data = file_get_contents(public_path("app/palicanon/category/{$file}.json"));
+        $data = file_get_contents(public_path("data/category/{$file}.json"));
         if ($data === false) {
             return $this->error('no file');
         }

+ 17 - 17
api-v12/app/Http/Controllers/PaliTextController.php

@@ -22,13 +22,13 @@ class PaliTextController extends Controller
     {
         //
         $all_count = 0;
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'chapter-tag':
                 $tm = (new TagMap)->getTable();
                 $tg = (new Tag)->getTable();
                 $pt = (new PaliText)->getTable();
-                if ($request->get('tags') && $request->get('tags') !== '') {
-                    $tags = explode(',', $request->get('tags'));
+                if ($request->input('tags') && $request->input('tags') !== '') {
+                    $tags = explode(',', $request->input('tags'));
                     foreach ($tags as $tag) {
                         # code...
                         if (!empty($tag)) {
@@ -76,8 +76,8 @@ class PaliTextController extends Controller
                 break;
 
             case 'chapter':
-                if ($request->get('tags') && $request->get('tags') !== '') {
-                    $tags = explode(',', $request->get('tags'));
+                if ($request->input('tags') && $request->input('tags') !== '') {
+                    $tags = explode(',', $request->input('tags'));
                     foreach ($tags as $tag) {
                         # code...
                         if (!empty($tag)) {
@@ -125,19 +125,19 @@ class PaliTextController extends Controller
                 $all_count = count($chapters);
                 break;
             case 'chapter_children':
-                $table = PaliText::where('book', $request->get('book'))
-                    ->where('parent', $request->get('para'))
+                $table = PaliText::where('book', $request->input('book'))
+                    ->where('parent', $request->input('para'))
                     ->where('level', '<', 8);
                 $all_count = $table->count();
                 $chapters = $table->orderBy('paragraph')->get();
                 break;
             case 'children':
                 if ($request->has('id')) {
-                    $root = PaliText::where('uid', $request->get('id'))
+                    $root = PaliText::where('uid', $request->input('id'))
                         ->first();
                 } else {
-                    $root = PaliText::where('book', $request->get('book'))
-                        ->where('paragraph', $request->get('para'))
+                    $root = PaliText::where('book', $request->input('book'))
+                        ->where('paragraph', $request->input('para'))
                         ->first();
                 }
 
@@ -168,8 +168,8 @@ class PaliTextController extends Controller
                 $all_count = count($chapters);
                 break;
             case 'paragraph':
-                $result = PaliText::where('book', $request->get('book'))
-                    ->where('paragraph', $request->get('para'))
+                $result = PaliText::where('book', $request->input('book'))
+                    ->where('paragraph', $request->input('para'))
                     ->first();
                 if ($result) {
                     return $this->ok($result);
@@ -191,13 +191,13 @@ class PaliTextController extends Controller
                  */
 
                 if ($request->has('series')) {
-                    $book_title = $request->get('series');
+                    $book_title = $request->input('series');
                     //获取丛书书目列表
-                    $books = BookTitle::where('title', $request->get('series'))->get();
+                    $books = BookTitle::where('title', $request->input('series'))->get();
                 } else {
                     //查询这个目录的顶级目录
-                    $path = PaliText::where('book', $request->get('book'))
-                        ->where('paragraph', $request->get('para'))
+                    $path = PaliText::where('book', $request->input('book'))
+                        ->where('paragraph', $request->input('para'))
                         ->select('path')->first();
                     if (!$path) {
                         return $this->error("no data");
@@ -248,7 +248,7 @@ class PaliTextController extends Controller
                 break;
         }
 
-        if ($request->get('view') !== 'book-toc') {
+        if ($request->input('view') !== 'book-toc') {
             foreach ($chapters as $key => $value) {
                 if (is_object($value)) {
                     //TODO $value->book 可能不存在

+ 121 - 0
api-v12/app/Http/Controllers/ParagraphContentController.php

@@ -0,0 +1,121 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+
+use App\Models\Sentence;
+use App\Models\PaliText;
+use Illuminate\Support\Str;
+use App\Http\Api\ChannelApi;
+
+
+
+
+use App\Services\PaliContentService;
+
+class ParagraphContentController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     */
+    public function index(Request $request, PaliContentService $paliService)
+    {
+        $data = $request->validate([
+            'book' => 'required|integer',
+            'para' => 'required|integer',
+            'to' => 'integer',
+        ]);
+
+        $channels = [];
+        if ($request->has('channels')) {
+            $_channels = explode(',', str_replace('_', ',', $request->input('channels')));
+            foreach ($_channels as $key => $channel) {
+                if (Str::isUuid($channel)) {
+                    $channels[] = $channel;
+                }
+            }
+        }
+
+        $mode = $request->input('mode', 'read');
+        if ($mode === 'read') {
+            //阅读模式加载html格式原文
+            $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
+        } else {
+            //翻译模式加载json格式原文
+            $channelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
+        }
+
+        if ($channelId !== false) {
+            $channels[] = $channelId;
+        }
+
+        $chapter = PaliText::where('book', $data['book'])
+            ->where('paragraph', $data['para'])->first();
+        if (!$chapter) {
+            return $this->error("no data");
+        }
+
+        #获取channel索引表
+
+        $indexChannel = [];
+        $indexChannel = $paliService->getChannelIndex($channels);
+        $from = $data['para'];
+        if (isset($data['to'])) {
+            $to = $data['to'];
+        } else {
+            $to = $data['para'];
+        }
+        $record = Sentence::where('book_id', $data['book'])
+            ->whereBetween('paragraph', [$from, $to])
+            ->whereIn('channel_uid', $channels)
+            ->orderBy('paragraph')
+            ->orderBy('word_start')
+            ->get();
+        if (count($record) === 0) {
+            return $this->error("no data");
+        }
+        $result = $paliService->makeContentObj($record, $mode, $indexChannel);
+
+        return $this->ok([
+            'items' => $result,
+            'pagination' => [
+                'page' => 1,
+                'pageSize' => $to - $from + 1,
+                'total' => $to - $from + 1
+            ],
+        ]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *      * @param  \Illuminate\Http\Request  $request
+     * @param string $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show(Request $request, string $id) {}
+
+    /**
+     * Update the specified resource in storage.
+     */
+    public function update(Request $request, string $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     */
+    public function destroy(string $id)
+    {
+        //
+    }
+}

+ 5 - 5
api-v12/app/Http/Controllers/PgPaliDictDownloadController.php

@@ -15,14 +15,14 @@ class PgPaliDictDownloadController extends Controller
      */
     public function index(Request $request)
     {
-        $currPage = $request->get('page',1);
+        $currPage = $request->input('page', 1);
         $path = 'export/fts/pali';
-        $filename = $path."/pali-{$currPage}.syn";
-        if(Redis::exists($filename)){
+        $filename = $path . "/pali-{$currPage}.syn";
+        if (Redis::exists($filename)) {
             $content = Redis::get($filename);
             return $this->ok($content);
-        }else{
-            return $this->error('no file',200,200);
+        } else {
+            return $this->error('no file', 200, 200);
         }
     }
 

+ 33 - 33
api-v12/app/Http/Controllers/ProgressChapterController.php

@@ -25,38 +25,38 @@ class ProgressChapterController extends Controller
     public function index(Request $request)
     {
 
-        $minProgress = (float)$request->get('progress', 0.8);
+        $minProgress = (float)$request->input('progress', 0.8);
 
-        $offset = (int)$request->get('offset', 0);
+        $offset = (int)$request->input('offset', 0);
 
-        $limit = (int)$request->get('limit', 20);
+        $limit = (int)$request->input('limit', 20);
 
-        $channel_id = $request->get('channel');
+        $channel_id = $request->input('channel');
 
         //
 
         $chapters = false;
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'ids':
-                $aChannel = explode(',', $request->get('channel'));
+                $aChannel = explode(',', $request->input('channel'));
                 $chapters = ProgressChapter::select("channel_id")->selectRaw("uid as id")
                     ->with(['channel' => function ($query) {  //city对应上面province模型中定义的city方法名  闭包内是子查询
                         return $query->select('*');
                     }])
-                    ->where("book", $request->get('book'))
-                    ->where("para", $request->get('par'))
+                    ->where("book", $request->input('book'))
+                    ->where("para", $request->input('par'))
                     ->whereIn('channel_id', $aChannel)->get();
                 $all_count = count($chapters);
                 break;
             case 'studio':
                 #查询该studio的channel
-                $name = $request->get('name');
-                $studioId = StudioApi::getIdByName($request->get('name'));
+                $name = $request->input('name');
+                $studioId = StudioApi::getIdByName($request->input('name'));
                 if ($studioId === false) {
                     return $this->error('no user');
                 }
                 $table = Channel::where('owner_uid', $studioId);
-                if ($request->get('public') === "true") {
+                if ($request->input('public') === "true") {
                     $table = $table->where('status', 30);
                 }
                 $channels = $table->select('uid')->get();
@@ -67,8 +67,8 @@ class ProgressChapterController extends Controller
                     })
                     ->where('progress', '>', 0.85)
                     ->orderby('progress_chapters.created_at', 'desc')
-                    ->skip($request->get("offset", 0))
-                    ->take($request->get("limit", 1000))
+                    ->skip($request->input("offset", 0))
+                    ->take($request->input("limit", 1000))
                     ->get();
                 $all_count = ProgressChapter::whereIn('progress_chapters.channel_id', $channels)
                     ->where('progress', '>', 0.85)->count();
@@ -91,8 +91,8 @@ class ProgressChapterController extends Controller
                 }
                 break;
             case 'chapter-tag':
-                if ($request->get('tags') && $request->get('tags') !== '') {
-                    $tags = explode(',', $request->get('tags'));
+                if ($request->input('tags') && $request->input('tags') !== '') {
+                    $tags = explode(',', $request->input('tags'));
                     foreach ($tags as $tag) {
                         # code...
                         if (!empty($tag)) {
@@ -173,11 +173,11 @@ class ProgressChapterController extends Controller
                     ->leftJoin('channels', 'progress_chapters.channel_id', '=', 'channels.uid')
                     ->where("progress", ">", $minProgress)
                     ->where('channels.status', '>=', 30);
-                if (!empty($request->get('channel_type'))) {
-                    $chapters =  $chapters->where('channels.type', $request->get('channel_type'));
+                if (!empty($request->input('channel_type'))) {
+                    $chapters =  $chapters->where('channels.type', $request->input('channel_type'));
                 }
-                if (!empty($request->get('lang'))) {
-                    $chapters =  $chapters->where('progress_chapters.lang', $request->get('lang'));
+                if (!empty($request->input('lang'))) {
+                    $chapters =  $chapters->where('progress_chapters.lang', $request->input('lang'));
                 }
                 $chapters =  $chapters->groupBy('channel_id')
                     ->orderBy('count', 'desc')
@@ -204,8 +204,8 @@ class ProgressChapterController extends Controller
                     'progress_chapters.updated_at'
                 )
                     ->leftJoin('channels', 'progress_chapters.channel_id', '=', 'channels.uid')
-                    ->where("book", $request->get('book'))
-                    ->where("para", $request->get('par'))
+                    ->where("book", $request->input('book'))
+                    ->where("para", $request->input('par'))
                     ->orderBy('progress', 'desc')
                     ->get();
                 foreach ($chapters as $key => $value) {
@@ -246,8 +246,8 @@ class ProgressChapterController extends Controller
                 $pt = (new PaliText)->getTable();
 
                 //标签过滤
-                if ($request->has('tags') && !empty($request->get('tags'))) {
-                    $tags = explode(',', $request->get('tags'));
+                if ($request->has('tags') && !empty($request->input('tags'))) {
+                    $tags = explode(',', $request->input('tags'));
                     foreach ($tags as $tag) {
                         # code...
                         if (!empty($tag)) {
@@ -265,9 +265,9 @@ class ProgressChapterController extends Controller
                     $in1 = " ";
                 }
                 if ($request->has('studio')) {
-                    $studioId = StudioApi::getIdByName($request->get('studio'));
+                    $studioId = StudioApi::getIdByName($request->input('studio'));
                     $table = Channel::where('owner_uid', $studioId);
-                    if ($request->get('public') === "true") {
+                    if ($request->input('public') === "true") {
                         $table = $table->where('status', 30);
                     }
                     $channels = $table->select('uid')->get();
@@ -291,16 +291,16 @@ class ProgressChapterController extends Controller
                 $param[] = $minProgress;
 
                 //语言过滤
-                if (!empty($request->get('lang'))) {
+                if (!empty($request->input('lang'))) {
                     $whereLang = " and pc.lang = ? ";
-                    $param[] = $request->get('lang');
+                    $param[] = $request->input('lang');
                 } else {
                     $whereLang = "   ";
                 }
                 //channel type过滤
-                if ($request->has('channel_type') && !empty($request->get('channel_type'))) {
+                if ($request->has('channel_type') && !empty($request->input('channel_type'))) {
                     $channel_type = "and ch.type = ? ";
-                    $param[] = $request->get('channel_type');
+                    $param[] = $request->input('channel_type');
                 } else {
                     $channel_type = "";
                 }
@@ -381,13 +381,13 @@ class ProgressChapterController extends Controller
             case 'top':
                 break;
             case 'search':
-                $key = $request->get('key');
+                $key = $request->input('key');
                 $table = ProgressChapter::where('title', 'like', "%{$key}%");
                 //获取记录总条数
                 $all_count = $table->count();
                 //处理排序
                 if ($request->has("order") && $request->has("dir")) {
-                    $table = $table->orderBy($request->get("order"), $request->get("dir"));
+                    $table = $table->orderBy($request->input("order"), $request->input("dir"));
                 } else {
                     //默认排序
                     $table = $table->orderBy('updated_at', 'desc');
@@ -395,11 +395,11 @@ class ProgressChapterController extends Controller
                 //处理分页
                 if ($request->has("limit")) {
                     if ($request->has("offset")) {
-                        $offset = $request->get("offset");
+                        $offset = $request->input("offset");
                     } else {
                         $offset = 0;
                     }
-                    $table = $table->skip($offset)->take($request->get("limit"));
+                    $table = $table->skip($offset)->take($request->input("limit"));
                 }
                 //获取数据
                 $chapters = $table->get();

+ 28 - 28
api-v12/app/Http/Controllers/ProjectController.php

@@ -27,23 +27,23 @@ class ProjectController extends Controller
             return $this->error(__('auth.failed'), 401, 401);
         }
         if ($request->has('studio')) {
-            $studioId = StudioApi::getIdByName($request->get('studio'));
+            $studioId = StudioApi::getIdByName($request->input('studio'));
         } else {
             $studioId = $user['user_uid'];
         }
 
-        switch ($request->get('view')) {
+        switch ($request->input('view')) {
             case 'studio':
                 $table = Project::where('owner_id', $studioId)
                     ->whereNull('parent_id')
-                    ->where('type', $request->get('type', 'instance'));
+                    ->where('type', $request->input('type', 'instance'));
                 break;
             case 'project-tree':
-                $table = Project::where('uid', $request->get('project_id'))
-                    ->orWhereJsonContains('path', $request->get('project_id'));
+                $table = Project::where('uid', $request->input('project_id'))
+                    ->orWhereJsonContains('path', $request->input('project_id'));
                 break;
             case 'shared':
-                $type = $request->get('type', 'instance');
+                $type = $request->input('type', 'instance');
                 $resList = ShareApi::getResList($studioId, $type === 'instance' ? 7 : 6);
                 $resId = [];
                 foreach ($resList as $res) {
@@ -55,7 +55,7 @@ class ProjectController extends Controller
                 $table = Project::where('owner_id', '<>', $studioId)
                     ->whereNull('parent_id')
                     ->where('privacy', 'public')
-                    ->where('type', $request->get('type', 'instance'));
+                    ->where('type', $request->input('type', 'instance'));
                 break;
             default:
                 return $this->error('view', 200, 200);
@@ -63,20 +63,20 @@ class ProjectController extends Controller
         }
 
         if ($request->has('keyword')) {
-            $table = $table->where('title', 'like', '%' . $request->get('keyword') . '%');
+            $table = $table->where('title', 'like', '%' . $request->input('keyword') . '%');
         }
         if ($request->has('status')) {
-            $table = $table->whereIn('status', explode(',', $request->get('status')));
+            $table = $table->whereIn('status', explode(',', $request->input('status')));
         }
         $count = $table->count();
 
         $sql = $table->toSql();
         Log::debug('sql', ['sql' => $sql]);
 
-        $table = $table->orderBy($request->get('order', 'id'), $request->get('dir', 'asc'));
+        $table = $table->orderBy($request->input('order', 'id'), $request->input('dir', 'asc'));
 
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 10000));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 10000));
 
         $result = $table->get();
 
@@ -106,26 +106,26 @@ class ProjectController extends Controller
         if (!$user) {
             return $this->error(__('auth.failed'), 401, 401);
         }
-        $studioId = StudioApi::getIdByName($request->get('studio_name'));
+        $studioId = StudioApi::getIdByName($request->input('studio_name'));
         if (!self::canEdit($user['user_uid'], $studioId)) {
             return $this->error(__('auth.failed'), 403, 403);
         }
-        $new = Project::firstOrNew(['uid' => $request->get('id')]);
-        if (Str::isUuid($request->get('id'))) {
-            $new->uid = $request->get('id');
+        $new = Project::firstOrNew(['uid' => $request->input('id')]);
+        if (Str::isUuid($request->input('id'))) {
+            $new->uid = $request->input('id');
         } else {
             $new->uid =  Str::uuid();
         }
-        $new->title = $request->get('title');
-        $new->description = $request->get('description');
-        $new->parent_id = $request->get('parent_id');
+        $new->title = $request->input('title');
+        $new->description = $request->input('description');
+        $new->parent_id = $request->input('parent_id');
         $new->editor_id = $user['user_uid'];
         $new->owner_id = $studioId;
-        $new->type = $request->get('type', 'instance');
+        $new->type = $request->input('type', 'instance');
 
 
-        if (Str::isUuid($request->get('parent_id'))) {
-            $parentPath = Project::where('uid', $request->get('parent_id'))->value('path');
+        if (Str::isUuid($request->input('parent_id'))) {
+            $parentPath = Project::where('uid', $request->input('parent_id'))->value('path');
             $parentPath = json_decode($parentPath);
             if (!is_array($parentPath)) {
                 $parentPath = array();
@@ -169,15 +169,15 @@ class ProjectController extends Controller
             return $this->error(__('auth.failed'), 403, 403);
         }
 
-        $project->title = $request->get('title');
-        $project->description = $request->get('description');
-        $project->parent_id = $request->get('parent_id');
+        $project->title = $request->input('title');
+        $project->description = $request->input('description');
+        $project->parent_id = $request->input('parent_id');
         $project->editor_id = $user['user_uid'];
-        $project->privacy = $request->get('privacy');
+        $project->privacy = $request->input('privacy');
 
 
-        if (Str::isUuid($request->get('parent_id'))) {
-            $parentPath = Project::where('uid', $request->get('parent_id'))->value('path');
+        if (Str::isUuid($request->input('parent_id'))) {
+            $parentPath = Project::where('uid', $request->input('parent_id'))->value('path');
             $parentPath = json_decode($parentPath);
             if (!is_array($parentPath)) {
                 $parentPath = array();

+ 6 - 6
api-v12/app/Http/Controllers/ProjectTreeController.php

@@ -35,12 +35,12 @@ class ProjectTreeController extends Controller
             Log::error('notification auth failed {request}', ['request' => $request]);
             return $this->error(__('auth.failed'), 401, 401);
         }
-        $studioId = StudioApi::getIdByName($request->get('studio_name'));
+        $studioId = StudioApi::getIdByName($request->input('studio_name'));
         if (!ProjectController::canEdit($user['user_uid'], $studioId)) {
             return $this->error(__('auth.failed'), 403, 403);
         }
         $newData = [];
-        foreach ($request->get('data') as $key => $value) {
+        foreach ($request->input('data') as $key => $value) {
             $data = [
                 'uid' => Str::uuid(),
                 'old_id' => $value['id'],
@@ -80,14 +80,14 @@ class ProjectTreeController extends Controller
                 } else {
                     $newData[$key]['parent_id'] = null;
                 }
-            } else if (!empty($request->get('parent_id'))) {
-                $pPath = Project::where('uid', $request->get('parent_id'))->value('path');
+            } else if (!empty($request->input('parent_id'))) {
+                $pPath = Project::where('uid', $request->input('parent_id'))->value('path');
                 $parentPath = json_decode($pPath);
                 if (!is_array($parentPath)) {
                     $parentPath = [];
                 }
-                $newData[$key]['path'] = json_encode([...$parentPath, $request->get('parent_id')], JSON_UNESCAPED_UNICODE);
-                $newData[$key]['parent_id'] = $request->get('parent_id');
+                $newData[$key]['path'] = json_encode([...$parentPath, $request->input('parent_id')], JSON_UNESCAPED_UNICODE);
+                $newData[$key]['parent_id'] = $request->input('parent_id');
             }
         }
         $output = [];

+ 16 - 15
api-v12/app/Http/Controllers/RecentController.php

@@ -20,20 +20,22 @@ class RecentController extends Controller
         //
         switch ($request->view) {
             case 'user':
-                $table = Recent::where('user_uid',$request->get('id'));
+                $table = Recent::where('user_uid', $request->input('id'));
                 break;
             default:
                 return $this->error('known view');
                 break;
         }
-
-        $table->orderBy($request->get('order','updated_at'),$request->get('dir','desc'));
+        if ($request->has('type')) {
+            $table->where('type', $request->input('type'));
+        }
+        $table->orderBy($request->input('order', 'updated_at'), $request->input('dir', 'desc'));
         $count = $table->count();
-        $table->skip($request->get("offset",0))
-              ->take($request->get('limit',1000));
+        $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 1000));
 
         $result = $table->get();
-		return $this->ok(["rows"=>RecentResource::collection($result),"count"=>$count]);
+        return $this->ok(["rows" => RecentResource::collection($result), "count" => $count]);
     }
 
     /**
@@ -45,8 +47,8 @@ class RecentController extends Controller
     public function store(Request $request)
     {
         $user = AuthApi::current($request);
-        if(!$user){
-            return $this->error(__('auth.failed'),[],401);
+        if (!$user) {
+            return $this->error(__('auth.failed'), [], 401);
         }
 
         $validated = $request->validate([
@@ -55,13 +57,13 @@ class RecentController extends Controller
         ]);
 
         $row = Recent::firstOrNew([
-            "type"=>$request->get("type"),
-            "article_id"=>$request->get("article_id"),
-            "user_uid"=>$user['user_uid'],
-        ],[
-            "id"=>Str::uuid(),
+            "type" => $request->input("type"),
+            "article_id" => $request->input("article_id"),
+            "user_uid" => $user['user_uid'],
+        ], [
+            "id" => Str::uuid(),
         ]);
-        $row->param = $request->get("param",null);
+        $row->param = $request->input("param", null);
         $row->save();
         return $this->ok(new RecentResource($row));
     }
@@ -76,7 +78,6 @@ class RecentController extends Controller
     {
         //
         return $this->ok(new RecentResource($recent));
-
     }
 
     /**

+ 14 - 13
api-v12/app/Http/Controllers/RelatedParagraphController.php

@@ -28,6 +28,7 @@
  * 制作时包含全部段落。做好后把没有相关段落的段落删掉??
  *
  */
+
 namespace App\Http\Controllers;
 
 use App\Models\RelatedParagraph;
@@ -44,26 +45,26 @@ class RelatedParagraphController extends Controller
     public function index(Request $request)
     {
         //
-        $first = RelatedParagraph::where('book',$request->get('book'))
-                                    ->where('para',$request->get('para'))
-                                    ->where('cs_para','>',0)
-                                    ->first();
-        $result = RelatedParagraph::where('book_name',$first->book_name)
-                                    ->where('cs_para',$first->cs_para)
-                                    ->orderBy('book_id')
-                                    ->orderBy('para')
-                                    ->get();
-        $books=[];
+        $first = RelatedParagraph::where('book', $request->input('book'))
+            ->where('para', $request->input('para'))
+            ->where('cs_para', '>', 0)
+            ->first();
+        $result = RelatedParagraph::where('book_name', $first->book_name)
+            ->where('cs_para', $first->cs_para)
+            ->orderBy('book_id')
+            ->orderBy('para')
+            ->get();
+        $books = [];
         foreach ($result as $value) {
             # 把段落整合成书。有几本书就有几条输出纪录
-            if(!isset($books[$value->book_id])){
+            if (!isset($books[$value->book_id])) {
                 $books[$value->book_id]['book'] = $value->book;
                 $books[$value->book_id]['book_id'] = $value->book_id;
                 $books[$value->book_id]['cs6_para'] = $value->cs_para;
             }
-            $books[$value->book_id]['para'][]=$value->para;
+            $books[$value->book_id]['para'][] = $value->para;
         }
-        return $this->ok(["rows"=>RelatedParagraphResource::collection($books),"count"=>count($books)]);
+        return $this->ok(["rows" => RelatedParagraphResource::collection($books), "count" => count($books)]);
     }
 
     /**

+ 23 - 23
api-v12/app/Http/Controllers/RelationController.php

@@ -40,31 +40,31 @@ class RelationController extends Controller
             'created_at'
         ]);
         if (($request->has('case'))) {
-            $table = $table->whereIn('case', explode(",", $request->get('case')));
+            $table = $table->whereIn('case', explode(",", $request->input('case')));
         }
         if (($request->has('search'))) {
-            $table = $table->where('name', 'like', $request->get('search') . "%");
+            $table = $table->where('name', 'like', $request->input('search') . "%");
         }
         if (($request->has('name'))) {
-            $table = $table->where('name', $request->get('name'));
+            $table = $table->where('name', $request->input('name'));
         }
         if (($request->has('from'))) {
-            $table = $table->whereJsonContains('from->case', $request->get('from'));
+            $table = $table->whereJsonContains('from->case', $request->input('from'));
         }
         if (($request->has('to'))) {
-            $table = $table->whereJsonContains('to', $request->get('to'));
+            $table = $table->whereJsonContains('to', $request->input('to'));
         }
         if (($request->has('match'))) {
-            $table = $table->whereJsonContains('match', $request->get('match'));
+            $table = $table->whereJsonContains('match', $request->input('match'));
         }
         if (($request->has('category'))) {
-            $table = $table->where('category', $request->get('category'));
+            $table = $table->where('category', $request->input('category'));
         }
-        $table = $table->orderBy($request->get('order', 'updated_at'), $request->get('dir', 'desc'));
+        $table = $table->orderBy($request->input('order', 'updated_at'), $request->input('dir', 'desc'));
         $count = $table->count();
 
-        $table = $table->skip($request->get("offset", 0))
-            ->take($request->get('limit', 1000));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 1000));
         $result = $table->get();
 
         $output = ["rows" => RelationResource::collection($result), "count" => $count];
@@ -95,25 +95,25 @@ class RelationController extends Controller
         $validated = $request->validate([
             'name' => 'required',
         ]);
-        $case = $request->get('case', '');
+        $case = $request->input('case', '');
         $new = new Relation;
         $new->name = $validated['name'];
 
-        $new->case = $request->get('case');
-        $new->category = $request->get('category');
+        $new->case = $request->input('case');
+        $new->category = $request->input('category');
 
         if ($request->has('from')) {
-            $new->from = json_encode($request->get('from'), JSON_UNESCAPED_UNICODE);
+            $new->from = json_encode($request->input('from'), JSON_UNESCAPED_UNICODE);
         } else {
             $new->from = null;
         }
         if ($request->has('to')) {
-            $new->to = json_encode($request->get('to'), JSON_UNESCAPED_UNICODE);
+            $new->to = json_encode($request->input('to'), JSON_UNESCAPED_UNICODE);
         } else {
             $new->to = null;
         }
         if ($request->has('match')) {
-            $new->match = json_encode($request->get('match'), JSON_UNESCAPED_UNICODE);
+            $new->match = json_encode($request->input('match'), JSON_UNESCAPED_UNICODE);
         } else {
             $new->match = null;
         }
@@ -150,22 +150,22 @@ class RelationController extends Controller
             return $this->error(__('auth.failed'));
         }
 
-        $relation->name = $request->get('name');
-        $relation->case = $request->get('case');
-        $relation->category = $request->get('category');
+        $relation->name = $request->input('name');
+        $relation->case = $request->input('case');
+        $relation->category = $request->input('category');
 
         if ($request->has('from')) {
-            $relation->from = json_encode($request->get('from'), JSON_UNESCAPED_UNICODE);
+            $relation->from = json_encode($request->input('from'), JSON_UNESCAPED_UNICODE);
         } else {
             $relation->from = null;
         }
         if ($request->has('to')) {
-            $relation->to = json_encode($request->get('to'), JSON_UNESCAPED_UNICODE);
+            $relation->to = json_encode($request->input('to'), JSON_UNESCAPED_UNICODE);
         } else {
             $relation->to = null;
         }
         if ($request->has('match')) {
-            $relation->match = json_encode($request->get('match'), JSON_UNESCAPED_UNICODE);
+            $relation->match = json_encode($request->input('match'), JSON_UNESCAPED_UNICODE);
         } else {
             $relation->match = null;
         }
@@ -231,7 +231,7 @@ class RelationController extends Controller
             return $this->error(__('auth.failed'));
         }
 
-        $filename = $request->get('filename');
+        $filename = $request->input('filename');
         $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
         $reader->setReadDataOnly(true);
         $spreadsheet = $reader->load($filename);

+ 15 - 16
api-v12/app/Http/Controllers/ResetPasswordController.php

@@ -26,24 +26,23 @@ class ResetPasswordController extends Controller
     public function store(Request $request)
     {
         //
-        $user = UserInfo::where('reset_password_token',$request->get('token'))
-                        ->where('username',$request->get('username'))
-                        ->first();
-        if(!$user){
-        return $this->error('no token',404,404);
+        $user = UserInfo::where('reset_password_token', $request->input('token'))
+            ->where('username', $request->input('username'))
+            ->first();
+        if (!$user) {
+            return $this->error('no token', 404, 404);
         }
-        if(mb_strlen($request->get('password'),'UTF-8')<6){
-            return $this->error('input is invalid',402,402);
+        if (mb_strlen($request->input('password'), 'UTF-8') < 6) {
+            return $this->error('input is invalid', 402, 402);
         }
-        $user->password = md5($request->get('password'));
+        $user->password = md5($request->input('password'));
         $user->reset_password_token = null;
         $ok = $user->save();
-        if($ok){
+        if ($ok) {
             return $this->ok($user);
-        }else{
-            return $this->error('fail to set password',500,500);
+        } else {
+            return $this->error('fail to set password', 500, 500);
         }
-
     }
 
     /**
@@ -55,10 +54,10 @@ class ResetPasswordController extends Controller
     public function show($token)
     {
         //
-        $user = UserInfo::where('reset_password_token',$token)
-                        ->select(['username'])->first();
-        if(!$user){
-            return $this->error('no token',404,404);
+        $user = UserInfo::where('reset_password_token', $token)
+            ->select(['username'])->first();
+        if (!$user) {
+            return $this->error('no token', 404, 404);
         }
         return $this->ok($user);
     }

+ 117 - 114
api-v12/app/Http/Controllers/SearchController.php

@@ -28,14 +28,15 @@ class SearchController extends Controller
      *
      * @return \Illuminate\Http\Response
      */
-    public function index(Request $request){
-        switch ($request->get('view','pali')) {
+    public function index(Request $request)
+    {
+        switch ($request->input('view', 'pali')) {
             case 'pali':
-                $pageHead = ['M','P','T','V','O'];
-                $key = $request->get('key');
-                if(substr($key,0,4) === 'para' || in_array(substr($key,0,1),$pageHead)){
+                $pageHead = ['M', 'P', 'T', 'V', 'O'];
+                $key = $request->input('key');
+                if (substr($key, 0, 4) === 'para' || in_array(substr($key, 0, 1), $pageHead)) {
                     return $this->page($request);
-                }else{
+                } else {
                     return $this->pali_rpc($request);
                 }
                 break;
@@ -43,29 +44,29 @@ class SearchController extends Controller
                 return $this->page($request);
                 break;
             case 'title':
-                $key = strtolower($request->get('key'));
-                $table = PaliText::where('level','<',8)
-                                 ->where(function ($query) use($key){
-                                     $query->where('title_en','like',"%{$key}%")
-                                         ->orWhere('title','like',"%{$key}%");
-                                 });
+                $key = strtolower($request->input('key'));
+                $table = PaliText::where('level', '<', 8)
+                    ->where(function ($query) use ($key) {
+                        $query->where('title_en', 'like', "%{$key}%")
+                            ->orWhere('title', 'like', "%{$key}%");
+                    });
                 Log::info($table->toSql());
-                if($request->has('tags')){
+                if ($request->has('tags')) {
                     //查询搜索范围
-                    $tagItems = explode(';',$request->get('tags'));
+                    $tagItems = explode(';', $request->input('tags'));
                     $bookId = [];
                     foreach ($tagItems as $tagItem) {
                         # code...
-                        $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+                        $bookId = array_merge($bookId, $this->getBookIdByTags(explode(',', $tagItem)));
                     }
-                    $table = $table->whereIn('pcd_book_id',$bookId);
+                    $table = $table->whereIn('pcd_book_id', $bookId);
                 }
                 $count = $table->count();
-                $table = $table->orderBy($request->get('orderby','book'),$request->get('dir','asc'));
-                $table = $table->skip($request->get("offset",0))
-                               ->take($request->get('limit',10));
+                $table = $table->orderBy($request->input('orderby', 'book'), $request->input('dir', 'asc'));
+                $table = $table->skip($request->input("offset", 0))
+                    ->take($request->input('limit', 10));
                 $result = $table->get();
-                return $this->ok(["rows"=>SearchTitleResource::collection($result),"count"=>$count]);
+                return $this->ok(["rows" => SearchTitleResource::collection($result), "count" => $count]);
                 break;
             default:
                 # code...
@@ -76,15 +77,15 @@ class SearchController extends Controller
     {
         //
         $bookId = [];
-        if($request->has('book')){
-            $bookId = [(int)$request->get('book')];
-        }else if($request->has('tags')){
+        if ($request->has('book')) {
+            $bookId = [(int)$request->input('book')];
+        } else if ($request->has('tags')) {
             //查询搜索范围
             //查询搜索范围
-            $tagItems = explode(';',$request->get('tags'));
+            $tagItems = explode(';', $request->input('tags'));
 
             foreach ($tagItems as $tagItem) {
-                $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+                $bookId = array_merge($bookId, $this->getBookIdByTags(explode(',', $tagItem)));
             }
         }
 
@@ -92,13 +93,13 @@ class SearchController extends Controller
         $searchBooks = [];
         $searchBookId = [];
         $queryBookId = '';
-        if(count($bookId) > 0){
-            $queryBookId = ' AND pcd_book_id in ('.implode(',',$bookId).') ';
+        if (count($bookId) > 0) {
+            $queryBookId = ' AND pcd_book_id in (' . implode(',', $bookId) . ') ';
         }
-        $key = explode(';',$request->get('key')) ;
+        $key = explode(';', $request->input('key'));
         $param = [];
         $countParam = [];
-        switch ($request->get('match','case')) {
+        switch ($request->input('match', 'case')) {
             case 'complete':
             case 'case':
                 # code...
@@ -106,13 +107,13 @@ class SearchController extends Controller
                                                 full_text_search_weighted,
                                                 websearch_to_tsquery('pali', ?)) ";
                 $querySelect_rank_head = implode('+', array_fill(0, count($key), $querySelect_rank_base));
-                $param = array_merge($param,$key);
+                $param = array_merge($param, $key);
                 $querySelect_rank = " {$querySelect_rank_head} AS rank, ";
                 $querySelect_highlight = " ts_headline('pali', content,
                                             websearch_to_tsquery('pali', ?),
                                             'StartSel = ~~, StopSel = ~~,MaxWords=3500, MinWords=3500,HighlightAll=TRUE')
                                             AS highlight,";
-                array_push($param,implode(' ',$key));
+                array_push($param, implode(' ', $key));
                 break;
             case 'similar':
                 # 形似,去掉变音符号
@@ -122,7 +123,7 @@ class SearchController extends Controller
                         full_text_search_weighted_unaccent,
                         websearch_to_tsquery('pali_unaccent', ?))
                     AS rank, ";
-                    $param[] = $key;
+                $param[] = $key;
                 $querySelect_highlight = " ts_headline('pali_unaccent', content,
                         websearch_to_tsquery('pali_unaccent', ?),
                         'StartSel = ~~, StopSel = ~~,MaxWords=3500, MinWords=3500,HighlightAll=TRUE')
@@ -130,18 +131,18 @@ class SearchController extends Controller
                 $param[] = $key;
                 break;
         }
-        $_queryWhere = $this->getQueryWhere($request->get('key'),$request->get('match','case'));
+        $_queryWhere = $this->getQueryWhere($request->input('key'), $request->input('match', 'case'));
         $queryWhere = $_queryWhere['query'];
-        $param = array_merge($param,$_queryWhere['param']);
+        $param = array_merge($param, $_queryWhere['param']);
 
         $querySelect_2 = "  book,paragraph,content ";
 
         $queryCount = "SELECT count(*) as co FROM fts_texts WHERE {$queryWhere} {$queryBookId};";
         $resultCount = DB::select($queryCount, $_queryWhere['param']);
 
-        $limit = $request->get('limit',10);
-        $offset = $request->get('offset',0);
-        switch ( $request->get('orderby',"rank")) {
+        $limit = $request->input('limit', 10);
+        $offset = $request->input('offset', 0);
+        switch ($request->input('orderby', "rank")) {
             case 'rank':
                 $orderby = " ORDER BY rank DESC ";
                 break;
@@ -167,30 +168,30 @@ class SearchController extends Controller
 
         $result = DB::select($query, $param);
 
-        return $this->ok(["rows"=>SearchResource::collection($result),"count"=>$resultCount[0]->co]);
+        return $this->ok(["rows" => SearchResource::collection($result), "count" => $resultCount[0]->co]);
     }
     public function pali_rpc(Request $request)
     {
         //
         $bookId = [];
-        if($request->has('book')){
-            $bookId = [(int)$request->get('book')];
-        }else if($request->has('tags')){
+        if ($request->has('book')) {
+            $bookId = [(int)$request->input('book')];
+        } else if ($request->has('tags')) {
             //查询搜索范围
             //查询搜索范围
-            $tagItems = explode(';',$request->get('tags'));
+            $tagItems = explode(';', $request->input('tags'));
 
             foreach ($tagItems as $tagItem) {
-                $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+                $bookId = array_merge($bookId, $this->getBookIdByTags(explode(',', $tagItem)));
             }
         }
 
-        $key = explode(';',$request->get('key')) ;
-        $limit = $request->get('limit',10);
-        $offset = $request->get('offset',0);
-        $matchMode = $request->get('match','case');
-        $result = PaliSearch::search($key,$bookId,$matchMode,$offset,$limit);
-        return $this->ok(["rows"=>SearchResource::collection(collect($result['rows'])),"count"=>$result['total']]);
+        $key = explode(';', $request->input('key'));
+        $limit = $request->input('limit', 10);
+        $offset = $request->input('offset', 0);
+        $matchMode = $request->input('match', 'case');
+        $result = PaliSearch::search($key, $bookId, $matchMode, $offset, $limit);
+        return $this->ok(["rows" => SearchResource::collection(collect($result['rows'])), "count" => $result['total']]);
     }
 
     public function page(Request $request)
@@ -201,97 +202,99 @@ class SearchController extends Controller
         $searchBookId = [];
         $queryBookId = '';
         $bookId = [];
-        if($request->has('book')){
-            $bookId[] = $request->get('book');
-        }else if($request->has('tags')){
+        if ($request->has('book')) {
+            $bookId[] = $request->input('book');
+        } else if ($request->has('tags')) {
             //查询搜索范围
             //查询搜索范围
-            $tagItems = explode(';',$request->get('tags'));
+            $tagItems = explode(';', $request->input('tags'));
             foreach ($tagItems as $tagItem) {
                 # code...
-                $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+                $bookId = array_merge($bookId, $this->getBookIdByTags(explode(',', $tagItem)));
             }
         }
 
-        $key = $request->get('key');
+        $key = $request->input('key');
         $searchKey = '';
-        $page = explode('.',$key);
-        if(count($page)===2){
-            $table = PageNumber::where('type',$request->get('type'))
-                               ->where('volume',(int)$page[0])
-                               ->where('page',(int)$page[1]);
-        }else{
-            if(is_numeric($key)){
-                $table = PageNumber::where('type',$request->get('type'))->where('page',$key);
-            }else{
-                $table = PageNumber::where('type',$request->get('type'))->where('page',(int)$key);
+        $page = explode('.', $key);
+        if (count($page) === 2) {
+            $table = PageNumber::where('type', $request->input('type'))
+                ->where('volume', (int)$page[0])
+                ->where('page', (int)$page[1]);
+        } else {
+            if (is_numeric($key)) {
+                $table = PageNumber::where('type', $request->input('type'))->where('page', $key);
+            } else {
+                $table = PageNumber::where('type', $request->input('type'))->where('page', (int)$key);
             }
         }
 
 
 
-        if(count($bookId)>0){
-            $table = $table->whereIn('pcd_book_id',$bookId);
+        if (count($bookId) > 0) {
+            $table = $table->whereIn('pcd_book_id', $bookId);
         }
         $count = $table->count();
-        $table = $table->select(['book','paragraph']);
-        $table->skip($request->get("offset",0))->take($request->get('limit',10));
+        $table = $table->select(['book', 'paragraph']);
+        $table->skip($request->input("offset", 0))->take($request->input('limit', 10));
         $result = $table->get();
 
-        return $this->ok(["rows"=>SearchResource::collection($result),"count"=>$count]);
+        return $this->ok(["rows" => SearchResource::collection($result), "count" => $count]);
     }
 
-    public function book_list(Request $request){
+    public function book_list(Request $request)
+    {
         $searchChapters = [];
         $searchBooks = [];
         $queryBookId = '';
 
         $bookId = [];
-        if($request->has('tags')){
+        if ($request->has('tags')) {
             //查询搜索范围
-            $tagItems = explode(';',$request->get('tags'));
+            $tagItems = explode(';', $request->input('tags'));
 
             foreach ($tagItems as $tagItem) {
                 # code...
-                $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+                $bookId = array_merge($bookId, $this->getBookIdByTags(explode(',', $tagItem)));
             }
-            $queryBookId = ' AND pcd_book_id in ('.implode(',',$bookId).') ';
+            $queryBookId = ' AND pcd_book_id in (' . implode(',', $bookId) . ') ';
         }
-        $key = $request->get('key');
-        switch ($request->get('view','pali')) {
+        $key = $request->input('key');
+        switch ($request->input('view', 'pali')) {
             case 'pali':
                 # code...
-                $pageHead = ['M','P','T','V','O'];
-                if(substr($key,0,4) === 'para' || in_array(substr($key,0,1),$pageHead)){
+                $pageHead = ['M', 'P', 'T', 'V', 'O'];
+                if (substr($key, 0, 4) === 'para' || in_array(substr($key, 0, 1), $pageHead)) {
                     $queryWhere = "type='.ctl.' AND word = ?";
                     $query = "SELECT pcd_book_id, count(*) as co FROM wbw_templates WHERE {$queryWhere} {$queryBookId} GROUP BY pcd_book_id ORDER BY co DESC;";
                     $result = DB::select($query, [$key]);
+                } else {
 
-                }else{
-
-                    $rpc_result = PaliSearch::book_list(explode(';',$key),
-                                                        $bookId,
-                                                        $request->get('match','case'));
+                    $rpc_result = PaliSearch::book_list(
+                        explode(';', $key),
+                        $bookId,
+                        $request->input('match', 'case')
+                    );
                     $result = collect($rpc_result['rows']);
                     /*
-                        $queryWhere = $this->getQueryWhere($key,$request->get('match','case'));
+                        $queryWhere = $this->getQueryWhere($key,$request->input('match','case'));
                         $query = "SELECT pcd_book_id, count(*) as co FROM fts_texts WHERE {$queryWhere['query']} {$queryBookId} GROUP BY pcd_book_id ORDER BY co DESC;";
                         $result = DB::select($query, $queryWhere['param']);
                     */
                 }
                 break;
             case 'page':
-                $type = $request->get('type','P');
+                $type = $request->input('type', 'P');
                 $word = "{$type}%0{$key}";
                 $queryWhere = "type='.ctl.' AND word like ?";
                 $query = "SELECT pcd_book_id, count(*) as co FROM wbw_templates WHERE {$queryWhere} {$queryBookId} GROUP BY pcd_book_id ORDER BY co DESC;";
                 $result = DB::select($query, [$word]);
                 break;
             case 'title':
-                $keyLike = '%'.$key.'%';
+                $keyLike = '%' . $key . '%';
                 $queryWhere = "\"level\" < 8 and (\"title_en\"::text like ? or \"title\"::text like ?)";
                 $query = "SELECT pcd_book_id, count(*) as co FROM pali_texts WHERE {$queryWhere} {$queryBookId} GROUP BY pcd_book_id ORDER BY co DESC;";
-                $result = DB::select($query, [$keyLike,$keyLike]);
+                $result = DB::select($query, [$keyLike, $keyLike]);
                 break;
             default:
                 # code...
@@ -299,16 +302,16 @@ class SearchController extends Controller
                 break;
         }
 
-        if($result){
-            return $this->ok(["rows"=>SearchBookResource::collection($result),"count"=>count($result)]);
-        }else{
-            return $this->ok(["rows"=>[],"count"=>0]);
+        if ($result) {
+            return $this->ok(["rows" => SearchBookResource::collection($result), "count" => count($result)]);
+        } else {
+            return $this->ok(["rows" => [], "count" => 0]);
         }
-
     }
 
-    private function getQueryWhere($key,$match){
-        $key = explode(';',$key) ;
+    private function getQueryWhere($key, $match)
+    {
+        $key = explode(';', $key);
         $param = [];
         $queryWhere = '';
         switch ($match) {
@@ -318,7 +321,7 @@ class SearchController extends Controller
                 $queryWhereBase = " full_text_search_weighted @@ websearch_to_tsquery('pali', ?) ";
                 $queryWhereBody = implode(' or ', array_fill(0, count($key), $queryWhereBase));
                 $queryWhere = " ({$queryWhereBody}) ";
-                $param = array_merge($param,$key);
+                $param = array_merge($param, $key);
                 break;
             case 'similar':
                 # 形似,去掉变音符号
@@ -327,49 +330,49 @@ class SearchController extends Controller
                 $param = [$key];
                 break;
         };
-        return (['query'=>$queryWhere,'param'=>$param]);
+        return (['query' => $queryWhere, 'param' => $param]);
     }
 
-    public function getBookIdByTags($tags){
+    public function getBookIdByTags($tags)
+    {
         $searchBookId = [];
-        if(empty($tags)){
+        if (empty($tags)) {
             return $searchBookId;
         }
 
         //查询搜索范围
-        $tagIds = Tag::whereIn('name',$tags)->select('id')->get();
-        $paliTextIds = TagMap::where('table_name','pali_texts')->whereIn('tag_id',$tagIds)->select('anchor_id')->get();
-        $paliPara=[];
+        $tagIds = Tag::whereIn('name', $tags)->select('id')->get();
+        $paliTextIds = TagMap::where('table_name', 'pali_texts')->whereIn('tag_id', $tagIds)->select('anchor_id')->get();
+        $paliPara = [];
         foreach ($paliTextIds as $key => $value) {
             # code...
-            if(isset($paliPara[$value->anchor_id])){
+            if (isset($paliPara[$value->anchor_id])) {
                 $paliPara[$value->anchor_id]++;
-            }else{
-                $paliPara[$value->anchor_id]=1;
+            } else {
+                $paliPara[$value->anchor_id] = 1;
             }
         }
-        $paliId=[];
+        $paliId = [];
         foreach ($paliPara as $key => $value) {
             # code...
-            if($value===count($tags)){
+            if ($value === count($tags)) {
                 $paliId[] = $key;
             }
         }
-        $para = PaliText::where('level',1)->whereIn('uid',$paliId)->get();
+        $para = PaliText::where('level', 1)->whereIn('uid', $paliId)->get();
 
-        if(count($para)>0){
+        if (count($para) > 0) {
             foreach ($para as $key => $value) {
                 # code...
-                $book_id = BookTitle::where('book',$value['book'])
-                                    ->where('paragraph',$value['paragraph'])
-                                    ->value('sn');
-                if(!empty($book_id)){
+                $book_id = BookTitle::where('book', $value['book'])
+                    ->where('paragraph', $value['paragraph'])
+                    ->value('sn');
+                if (!empty($book_id)) {
                     $searchBookId[] = $book_id;
                 }
             }
         }
         return $searchBookId;
-
     }
 
     /**

+ 53 - 49
api-v12/app/Http/Controllers/SearchPaliDataController.php

@@ -1,8 +1,10 @@
 <?php
+
 /**
  * 输出巴利语全文搜索数据
  * 提供给搜索引擎
  */
+
 namespace App\Http\Controllers;
 
 use Illuminate\Http\Request;
@@ -19,83 +21,85 @@ class SearchPaliDataController extends Controller
     public function index(Request $request)
     {
         //
-        $book = $request->get('book');
+        $book = $request->input('book');
 
-        $maxParagraph = WbwTemplate::where('book',$book)->max('paragraph');
-        $pageSize = $request->get('page_size',1000);
-        $start = $request->get('start',1);
+        $maxParagraph = WbwTemplate::where('book', $book)->max('paragraph');
+        $pageSize = $request->input('page_size', 1000);
+        $start = $request->input('start', 1);
         $output = array();
-        if($start+$pageSize>$maxParagraph){
-            $endOfPara = $maxParagraph+1;
-        }else{
-            $endOfPara = $start+$pageSize;
+        if ($start + $pageSize > $maxParagraph) {
+            $endOfPara = $maxParagraph + 1;
+        } else {
+            $endOfPara = $start + $pageSize;
         }
 
-        for($iPara=$start; $iPara < $endOfPara; $iPara++){
-            $content = $this->getContent($book,$iPara);
+        for ($iPara = $start; $iPara < $endOfPara; $iPara++) {
+            $content = $this->getContent($book, $iPara);
             //查找黑体字
-            $words = WbwTemplate::where('book',$book)
-                                ->where('paragraph',$iPara)
-                                ->orderBy('wid')->get();
+            $words = WbwTemplate::where('book', $book)
+                ->where('paragraph', $iPara)
+                ->orderBy('wid')->get();
             $bold1 = array();
             $bold2 = array();
             $bold3 = array();
             $currBold = array();
             foreach ($words as $word) {
-                if($word->style==='bld'){
+                if ($word->style === 'bld') {
                     $currBold[] = $word->real;
-                }else{
+                } else {
                     $countBold = count($currBold);
-                    if($countBold === 1){
+                    if ($countBold === 1) {
                         $bold1[] = $currBold[0];
-                    }else if($countBold === 2){
-                        $bold2 = array_merge($bold2,$currBold);
-                    }else if($countBold > 0){
-                        $bold3 = array_merge($bold3,$currBold);
+                    } else if ($countBold === 2) {
+                        $bold2 = array_merge($bold2, $currBold);
+                    } else if ($countBold > 0) {
+                        $bold3 = array_merge($bold3, $currBold);
                     }
                     $currBold = [];
                 }
             }
-            $pcd_book = BookTitle::where('book',$book)
-                    ->where('paragraph','<=',$iPara)
-                    ->orderBy('paragraph','desc')
-                    ->first();
-            if($pcd_book){
+            $pcd_book = BookTitle::where('book', $book)
+                ->where('paragraph', '<=', $iPara)
+                ->orderBy('paragraph', 'desc')
+                ->first();
+            if ($pcd_book) {
                 $pcd_book_id = $pcd_book->sn;
-            }else{
-                $pcd_book_id = BookTitle::where('book',$book)
-                                        ->orderBy('paragraph')
-                                        ->value('sn');
+            } else {
+                $pcd_book_id = BookTitle::where('book', $book)
+                    ->orderBy('paragraph')
+                    ->value('sn');
             }
 
-            $update = ['book'=>$book,
-                        'paragraph' => $iPara,
-                        'bold1' => implode(' ',$bold1),
-                        'bold2' => implode(' ',$bold2),
-                        'bold3' => implode(' ',$bold3),
-                        'content' => $content,
-                        'pcd_book_id' => $pcd_book_id
-                    ];
+            $update = [
+                'book' => $book,
+                'paragraph' => $iPara,
+                'bold1' => implode(' ', $bold1),
+                'bold2' => implode(' ', $bold2),
+                'bold3' => implode(' ', $bold3),
+                'content' => $content,
+                'pcd_book_id' => $pcd_book_id
+            ];
             $output[] = $update;
         }
-        return $this->ok(['rows'=>$output,'count'=>$maxParagraph]);
+        return $this->ok(['rows' => $output, 'count' => $maxParagraph]);
     }
-    private function getContent($book,$para){
-        $words = WbwTemplate::where('book',$book)
-                            ->where('paragraph',$para)
-                            ->where('type',"<>",".ctl.")
-                            ->orderBy('wid')->get();
+    private function getContent($book, $para)
+    {
+        $words = WbwTemplate::where('book', $book)
+            ->where('paragraph', $para)
+            ->where('type', "<>", ".ctl.")
+            ->orderBy('wid')->get();
         $content = '';
         foreach ($words as  $word) {
-            if($word->style === 'bld'){
-                if(strpos($word->word,"{")===FALSE){
+            if ($word->style === 'bld') {
+                if (strpos($word->word, "{") === FALSE) {
                     $content .= "**{$word->word}** ";
-                }else{
-                    $content .= str_replace(['{','}'],['**','** '],$word->word);
+                } else {
+                    $content .= str_replace(['{', '}'], ['**', '** '], $word->word);
                 }
-            }else if($word->style === 'note'){
+            } else if ($word->style === 'note') {
                 $content .= " _{$word->word}_ ";
-            }else{
+            } else {
                 $content .= $word->word . " ";
             }
         }

+ 36 - 35
api-v12/app/Http/Controllers/SearchPaliWbwController.php

@@ -21,78 +21,79 @@ class SearchPaliWbwController extends Controller
         //获取书的范围
         $bookId = [];
         $search = new SearchController;
-        if($request->has('book')){
-            foreach (explode(',',$request->get('book')) as $key => $id) {
+        if ($request->has('book')) {
+            foreach (explode(',', $request->input('book')) as $key => $id) {
                 $bookId[] = (int)$id;
             }
-        }else if($request->has('tags')){
+        } else if ($request->has('tags')) {
             //查询搜索范围
             //查询搜索范围
-            $tagItems = explode(';',$request->get('tags'));
+            $tagItems = explode(';', $request->input('tags'));
 
             foreach ($tagItems as $tagItem) {
-                $bookId = array_merge($bookId,$search->getBookIdByTags(explode(',',$tagItem)));
+                $bookId = array_merge($bookId, $search->getBookIdByTags(explode(',', $tagItem)));
             }
         }
 
-        $keyWords = explode(',',$request->get('key'));
-        $table = WbwTemplate::whereIn('real',$keyWords)
-                            ->groupBy(['book','paragraph'])
-                            ->selectRaw('book,paragraph,sum(weight) as rank');
+        $keyWords = explode(',', $request->input('key'));
+        $table = WbwTemplate::whereIn('real', $keyWords)
+            ->groupBy(['book', 'paragraph'])
+            ->selectRaw('book,paragraph,sum(weight) as rank');
         $whereBold = '';
-        if($request->get('bold')==='on'){
-            $table = $table->where('style','bld');
+        if ($request->input('bold') === 'on') {
+            $table = $table->where('style', 'bld');
             $whereBold = " and style='bld'";
-        }else if($request->get('bold')==='off'){
-            $table = $table->where('style','<>','bld');
+        } else if ($request->input('bold') === 'off') {
+            $table = $table->where('style', '<>', 'bld');
             $whereBold = " and style <> 'bld'";
         }
-        $placeholderWord = implode(",",array_fill(0, count($keyWords), '?')) ;
+        $placeholderWord = implode(",", array_fill(0, count($keyWords), '?'));
         $whereWord = "real in ({$placeholderWord})";
         $whereBookId = '';
-        if(count($bookId)>0){
-            $table =  $table->whereIn('pcd_book_id',$bookId);
-            $placeholderBookId = implode(",",array_fill(0, count($bookId), '?')) ;
+        if (count($bookId) > 0) {
+            $table =  $table->whereIn('pcd_book_id', $bookId);
+            $placeholderBookId = implode(",", array_fill(0, count($bookId), '?'));
             $whereBookId = " and pcd_book_id in ({$placeholderBookId}) ";
         }
         $queryCount = "SELECT count(*) FROM ( SELECT book,paragraph FROM wbw_templates WHERE $whereWord $whereBookId $whereBold  GROUP BY book,paragraph) T;";
-        $count = DB::select($queryCount,array_merge($keyWords,$bookId));
+        $count = DB::select($queryCount, array_merge($keyWords, $bookId));
 
-        $table =  $table->orderBy('rank','desc');
-        $table =  $table->skip($request->get("offset",0))
-                        ->take($request->get('limit',10));
+        $table =  $table->orderBy('rank', 'desc');
+        $table =  $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 10));
 
         $result = $table->get();
         return $this->ok([
-            "rows"=>SearchPaliWbwResource::collection($result),
-            "count"=>$count[0]->count,
-            ]);
+            "rows" => SearchPaliWbwResource::collection($result),
+            "count" => $count[0]->count,
+        ]);
     }
 
-    public function book_list(Request $request){
+    public function book_list(Request $request)
+    {
         //获取书的范围
         $bookId = [];
         $search = new SearchController;
-        if($request->has('tags')){
+        if ($request->has('tags')) {
             //查询搜索范围
             //查询搜索范围
-            $tagItems = explode(';',$request->get('tags'));
+            $tagItems = explode(';', $request->input('tags'));
 
             foreach ($tagItems as $tagItem) {
-                $bookId = array_merge($bookId,$search->getBookIdByTags(explode(',',$tagItem)));
+                $bookId = array_merge($bookId, $search->getBookIdByTags(explode(',', $tagItem)));
             }
         }
-        $keyWords = explode(',',$request->get('key'));
-        $table = WbwTemplate::whereIn('real',$keyWords);
+        $keyWords = explode(',', $request->input('key'));
+        $table = WbwTemplate::whereIn('real', $keyWords);
 
-        if(count($bookId)>0){
-            $table = $table->whereIn('pcd_book_id',$bookId);
+        if (count($bookId) > 0) {
+            $table = $table->whereIn('pcd_book_id', $bookId);
         }
         $table = $table->groupBy('pcd_book_id')
-                       ->selectRaw('pcd_book_id,count(*) as co')
-                       ->orderBy('co','desc');
+            ->selectRaw('pcd_book_id,count(*) as co')
+            ->orderBy('co', 'desc');
         $result = $table->get();
-        return $this->ok(["rows"=>SearchBookResource::collection($result),"count"=>count($result)]);
+        return $this->ok(["rows" => SearchBookResource::collection($result), "count" => count($result)]);
     }
     /**
      * Store a newly created resource in storage.

+ 9 - 9
api-v12/app/Http/Controllers/SearchTitleController.php

@@ -16,19 +16,19 @@ class SearchTitleController extends Controller
     public function index(Request $request)
     {
         //
-        $key = strtolower($request->get('key'));
-        $table = PaliText::where('level','<',8)
-                         ->where(function ($query) use($key){
-                            $query->where('title_en','like',"%{$key}%")
-                                  ->orWhere('title','like',"%{$key}%");
-                        });
+        $key = strtolower($request->input('key'));
+        $table = PaliText::where('level', '<', 8)
+            ->where(function ($query) use ($key) {
+                $query->where('title_en', 'like', "%{$key}%")
+                    ->orWhere('title', 'like', "%{$key}%");
+            });
         $count = $table->count();
         $table = $table->orderBy('title_en');
-        $table = $table->skip($request->get("offset",0))
-                         ->take($request->get('limit',10));
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 10));
 
         $result = $table->get();
-        return $this->ok(["rows"=>SearchTitleIndexResource::collection($result),"count"=>$count]);
+        return $this->ok(["rows" => SearchTitleIndexResource::collection($result), "count" => $count]);
     }
 
     /**

+ 19 - 17
api-v12/app/Http/Controllers/SentHistoryController.php

@@ -19,28 +19,30 @@ class SentHistoryController extends Controller
         //
         switch ($request->view) {
             case 'sentence':
-                $table = SentHistory::where('sent_uid',$request->get('id'));
+                $table = SentHistory::where('sent_uid', $request->input('id'));
                 break;
             default:
                 return $this->error('known view');
                 break;
         }
-        if($request->has('fork')){
+        if ($request->has('fork')) {
             $table = $table->whereNotNull('fork_from');
         }
         $count = $table->count();
-        $table->orderBy($request->get('order','created_at'),
-                        $request->get('dir','desc'));
-        $table->skip($request->get("offset",0))
-              ->take($request->get('limit',100));
+        $table->orderBy(
+            $request->input('order', 'created_at'),
+            $request->input('dir', 'desc')
+        );
+        $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 100));
 
         $result = $table->get();
-		return $this->ok(["rows"=>SentHistoryResource::collection($result),"count"=>$count]);
-
+        return $this->ok(["rows" => SentHistoryResource::collection($result), "count" => $count]);
     }
 
-    public function contribution(Request $request){
-                /**
+    public function contribution(Request $request)
+    {
+        /**
          *  计算用户贡献度
          *  算法:统计句子历史记录里的用户贡献句子的数量
          *  TODO:
@@ -48,18 +50,18 @@ class SentHistoryController extends Controller
          *  只统计一个月内的数值
          */
         $result = SentHistory::select('user_uid')
-                            ->selectRaw('count(*)')
-                            ->groupBy('user_uid')
-                            ->orderBy('count','desc')
-                            ->take(10)
-                            ->get();
+            ->selectRaw('count(*)')
+            ->groupBy('user_uid')
+            ->orderBy('count', 'desc')
+            ->take(10)
+            ->get();
 
 
         foreach ($result as $key => $user) {
             $userInfo = UserApi::getByUuid($user->user_uid);
             $user->username = [
-                'nickname'=>$userInfo['nickName'],
-                'username'=>$userInfo['userName'],
+                'nickname' => $userInfo['nickName'],
+                'username' => $userInfo['userName'],
             ];
         }
         return $this->ok($result);

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff