TermIndexService.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. <?php
  2. namespace App\Services;
  3. class TermIndexService
  4. {
  5. public function __construct(
  6. protected OpenSearchService $openSearchService,
  7. protected TermService $termService,
  8. ) {}
  9. /**
  10. * 构建单条词条文档并写入 OpenSearch
  11. *
  12. * @param string $guid DhammaTerm 的 guid
  13. */
  14. public function index(string $guid): void
  15. {
  16. $document = $this->buildDocument($guid);
  17. $this->openSearchService->create($document['id'], $document);
  18. }
  19. /**
  20. * 构建单条词条的 OpenSearch 文档(不写入)
  21. *
  22. * 文档结构遵循新版 mapping:
  23. * title.text.pali / title.text.zh → 全文检索
  24. * title.suggest.pali / title.suggest.zh → 自动建议
  25. * content.text.pali / content.text.zh → 正文内容
  26. *
  27. * @param string $guid DhammaTerm 的 guid
  28. * @return array<string, mixed>
  29. */
  30. public function buildDocument(string $guid): array
  31. {
  32. $termData = $this->termService->find($guid, 'text');
  33. $channelName = $termData['channel']['name'] ?? '';
  34. $content = $termData['html'] ?? $termData['meaning'];
  35. $categories = $this->extractCategories($termData['note'] ?? '');
  36. $quality = $this->extractFirstQuality($termData['note'] ?? '');
  37. $tags = [];
  38. foreach ($categories as $category) {
  39. $tags[] = "category:{$category}";
  40. }
  41. if (! empty($quality)) {
  42. $tags[] = "quality:{$quality}";
  43. }
  44. $document = [
  45. 'id' => "term_{$guid}",
  46. 'resource_id' => $guid,
  47. 'resource_type' => 'term',
  48. 'title' => [
  49. 'text' => [
  50. 'pali' => $termData['word'],
  51. 'zh' => $termData['meaning'],
  52. ],
  53. 'suggest' => [
  54. 'pali' => [$termData['word']],
  55. 'zh' => [$termData['meaning']],
  56. ],
  57. ],
  58. 'summary' => [
  59. 'text' => $termData['summary'] ?? '',
  60. ],
  61. 'content' => [],
  62. 'bold_single' => [$termData['meaning'], $termData['word']],
  63. 'related_id' => $termData['word'],
  64. 'category' => null,
  65. 'tags' => $tags,
  66. 'language' => $termData['language'],
  67. 'updated_at' => now()->toIso8601String(),
  68. 'path' => $termData['studio']['realName']."/{$channelName}",
  69. 'metadata' => ['channel' => $termData['channel_id']],
  70. ];
  71. // TODO: 补充语言判断,将内容放入对应的 text.pali 或 text.zh 字段
  72. $plainText = strip_tags($content);
  73. $document['content']['text']['zh'] = $plainText;
  74. $document['content']['display'] = $content; // 展示
  75. return $document;
  76. }
  77. /**
  78. * 提取 Markdown 中的 {{category|...}} 分类标签
  79. *
  80. * @return array<int, string>
  81. */
  82. private function extractCategories(string $content): array
  83. {
  84. if (empty($content)) {
  85. return [];
  86. }
  87. preg_match_all('/\{\{category\|([^}]+)\}\}/u', $content, $matches);
  88. return array_values(array_filter(array_map(
  89. fn ($item) => trim($item),
  90. $matches[1] ?? []
  91. )));
  92. }
  93. /**
  94. * 提取 Markdown 中第一个 {{quality|...}} 标签内的内容
  95. */
  96. private function extractFirstQuality(string $content): string
  97. {
  98. if (empty($content)) {
  99. return '';
  100. }
  101. preg_match('/\{\{quality\|([^}]+)\}\}/u', $content, $matches);
  102. return isset($matches[1]) ? trim($matches[1]) : '';
  103. }
  104. }