HitItemDTO.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. <?php
  2. namespace App\DTO\Search;
  3. class HitItemDTO
  4. {
  5. public function __construct(
  6. public string $id,
  7. public string $resId,
  8. public float $score,
  9. public string $content,
  10. public string $title,
  11. public string $path,
  12. public array $category,
  13. public string $highlight,
  14. public string $updated,
  15. public string $type,
  16. public string $language,
  17. public ?string $display = null,
  18. ) {}
  19. /**
  20. * 从 OpenSearch hit 数组构建 DTO
  21. *
  22. * title 和 content 的拼接策略:
  23. * 因为一条记录通常只有一种语言被赋值(pali 或 zh),
  24. * 所以直接将 text.* 下所有非空值拼接输出,无需判断语言类型。
  25. *
  26. * title: 取 title.text.pali + title.text.zh(跳过空值)
  27. * content: 取 content.text.pali + content.text.zh(跳过空值)
  28. * display: 取 content.display,列表页查询时因被 sourceExcludes 排除
  29. * 通常为 null;详情页通过 get() 获取完整文档时才有值
  30. *
  31. * @param array $data OpenSearch 单条 hit 原始数据
  32. * @return static
  33. */
  34. public static function fromArray(array $data): self
  35. {
  36. $source = $data['_source'];
  37. // ---------- highlight ----------
  38. $highlightArray = [];
  39. if (!empty($data['highlight']) && is_array($data['highlight'])) {
  40. foreach ($data['highlight'] as $fragments) {
  41. $highlightArray = array_merge($highlightArray, $fragments);
  42. }
  43. }
  44. // ---------- title ----------
  45. // 取 title.text 下所有非空语言值拼接
  46. $titleText = $source['title']['text'] ?? [];
  47. $title = implode('', array_filter(
  48. is_array($titleText) ? array_values($titleText) : [],
  49. fn($v) => is_string($v) && $v !== ''
  50. ));
  51. // ---------- content ----------
  52. // 取 content.text 下所有非空语言值拼接
  53. $contentText = $source['content']['text'] ?? [];
  54. $content = implode('', array_filter(
  55. is_array($contentText) ? array_values($contentText) : [],
  56. fn($v) => is_string($v) && $v !== ''
  57. ));
  58. // ---------- display ----------
  59. // 列表页查询时被 sourceExcludes 排除,值为 null
  60. // 详情页通过 get() 取完整文档时才有值
  61. $display = $source['content']['display'] ?? null;
  62. // ---------- category ----------
  63. $rawCategory = $source['category'] ?? [];
  64. $category = is_array($rawCategory) ? $rawCategory : [$rawCategory];
  65. return new self(
  66. id: $source['id'],
  67. score: $data['_score'] ?? 0,
  68. title: $title,
  69. content: $content,
  70. path: $source['path'] ?? '',
  71. category: $category,
  72. highlight: implode('', $highlightArray),
  73. updated: $source['updated_at'],
  74. type: $source['resource_type'],
  75. language: $source['language'],
  76. resId: $source['resource_id'],
  77. display: $display,
  78. );
  79. }
  80. /**
  81. * 将 DTO 转为数组(用于 API 响应输出)
  82. *
  83. * display 字段仅在有值时输出,避免列表页响应体携带 null 键。
  84. *
  85. * @return array
  86. */
  87. public function toArray(): array
  88. {
  89. $data = [
  90. 'id' => $this->id,
  91. 'res_id' => $this->resId,
  92. 'score' => $this->score,
  93. 'title' => $this->title,
  94. 'content' => $this->content,
  95. 'path' => $this->path,
  96. 'category' => $this->category,
  97. 'highlight' => $this->highlight,
  98. 'updated' => $this->updated,
  99. 'type' => $this->type,
  100. 'language' => $this->language,
  101. 'para_id' => $this->getParaId(),
  102. 'para_link' => $this->getParaLink(),
  103. ];
  104. if ($this->display !== null) {
  105. $data['display'] = $this->display;
  106. }
  107. return $data;
  108. }
  109. /**
  110. * 提取 para 引用 ID
  111. *
  112. * 匹配文档 ID 格式 "pali_para_{bookId}_{paraId}",
  113. * 返回 "{bookId}-{paraId}" 格式字符串。
  114. *
  115. * @return string|null
  116. */
  117. public function getParaId(): ?string
  118. {
  119. if (preg_match('/pali_para_(\d+)_(\d+)/', $this->id, $matches)) {
  120. return "{$matches[1]}-{$matches[2]}";
  121. }
  122. return null;
  123. }
  124. /**
  125. * 生成 para wiki 模板引用字符串
  126. *
  127. * @return string|null 例如:{{para|id=1-23|title=1-23|style=reference}}
  128. */
  129. public function getParaLink(): ?string
  130. {
  131. $id = $this->getParaId();
  132. if (!$id) return null;
  133. return "{{para|id={$id}|title={$id}|style=reference}}";
  134. }
  135. }