ParaInfoController.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Models\PaliText;
  4. use App\Models\RelatedParagraph;
  5. use Illuminate\Http\JsonResponse;
  6. use Illuminate\Http\Request;
  7. use Illuminate\Http\Response;
  8. use Illuminate\Support\Collection;
  9. class ParaInfoController extends Controller
  10. {
  11. /**
  12. * 取一个章节内 pali_texts 的全部段落记录。
  13. *
  14. * 以 (book, para) 定位章节起始段,再按 chapter_len 取出该章节覆盖的整段区间。
  15. * 每条记录附带 related_paragraphs 里的 CS6 锚点(book_name + cs_para),
  16. * 没有锚点的段落这两个字段为 null——约 2% 的段落没有锚点,属正常情况。
  17. *
  18. * @param int $book 书号
  19. * @param int $para 章节起始段落号
  20. * @return JsonResponse
  21. */
  22. public function index(Request $request)
  23. {
  24. $validated = $request->validate([
  25. 'book' => ['required', 'integer'],
  26. 'para' => ['required', 'integer'],
  27. ]);
  28. $root = PaliText::where('book', $validated['book'])
  29. ->where('paragraph', $validated['para'])
  30. ->first();
  31. if (! $root) {
  32. return $this->error('no paragraph');
  33. }
  34. $paraFrom = $root->paragraph;
  35. $paraTo = $root->paragraph + $root->chapter_len - 1;
  36. $chapters = PaliText::where('book', $root->book)
  37. ->whereBetween('paragraph', [$paraFrom, $paraTo])
  38. ->select(['book', 'paragraph', 'toc', 'level', 'lenght', 'chapter_len'])
  39. ->orderBy('paragraph', 'asc')
  40. ->get();
  41. $anchors = $this->relatedAnchors($root->book, $paraFrom, $paraTo);
  42. $rows = [];
  43. foreach ($chapters as $chapter) {
  44. $anchor = $anchors[$chapter->paragraph] ?? null;
  45. $rows[] = [
  46. 'book' => $chapter->book,
  47. 'paragraph' => $chapter->paragraph,
  48. 'toc' => $chapter->toc,
  49. 'level' => $chapter->level,
  50. // pali_texts 的列名拼错成 lenght,输出用正确拼写
  51. 'length' => $chapter->lenght,
  52. 'chapter_len' => $chapter->chapter_len,
  53. 'book_name' => $anchor->book_name ?? null,
  54. 'cs_para' => $anchor->cs_para ?? null,
  55. ];
  56. }
  57. return $this->ok(['rows' => $rows, 'count' => count($rows)]);
  58. }
  59. /**
  60. * 批量取段落区间的 CS6 锚点,按 paragraph 索引。
  61. *
  62. * 一次聚合查询取完,不能按段落逐条查——chapter_len 最大到 15943,
  63. * 逐条查会打出上万次 SQL。
  64. *
  65. * 一个段落可能对应多个 cs_para(约 1.6% 的段落如此,通常是连续区间),
  66. * 取最小值作为该段的起始锚点;book_name 在同一 (book, para) 内唯一,
  67. * min() 只是为了配合 GROUP BY。cs_para = 0 表示没有锚点,直接排除。
  68. *
  69. * @return Collection<int, object{book_name: string, cs_para: int}>
  70. */
  71. private function relatedAnchors(int $book, int $paraFrom, int $paraTo)
  72. {
  73. return RelatedParagraph::where('book', $book)
  74. ->whereBetween('para', [$paraFrom, $paraTo])
  75. ->where('cs_para', '>', 0)
  76. ->groupBy('para')
  77. ->selectRaw('para, min(book_name) as book_name, min(cs_para) as cs_para')
  78. ->get()
  79. ->keyBy('para');
  80. }
  81. /**
  82. * Store a newly created resource in storage.
  83. *
  84. * @return Response
  85. */
  86. public function store(Request $request)
  87. {
  88. //
  89. }
  90. /**
  91. * Display the specified resource.
  92. *
  93. * @return Response
  94. */
  95. public function show(string $id)
  96. {
  97. //
  98. }
  99. /**
  100. * Update the specified resource in storage.
  101. *
  102. * @return Response
  103. */
  104. public function update(Request $request, string $id)
  105. {
  106. //
  107. }
  108. /**
  109. * Remove the specified resource from storage.
  110. *
  111. * @return Response
  112. */
  113. public function destroy(string $id)
  114. {
  115. //
  116. }
  117. }