TermService.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <?php
  2. namespace App\Services;
  3. use App\Http\Api\ChannelApi;
  4. use App\Http\Resources\TermResource;
  5. use App\Models\DhammaTerm;
  6. use App\Models\UserDict;
  7. use App\Tools\Tools;
  8. use Illuminate\Http\Request;
  9. use Illuminate\Support\Str;
  10. class TermService
  11. {
  12. // 术语表额外纳入的 user_dicts 词典 id(其 word 去除连字符与空格后加入)
  13. private const USER_DICT_IDS = [
  14. 'b089de57-f146-4095-b886-057863728c43',
  15. '0bfd87ec-f3ac-49a2-985e-28388779078d',
  16. '7c7ee287-35ba-4cf3-b87b-30f1fa6e57c9',
  17. '4ac8a0d5-9c6f-4b9f-983d-84288d47f993',
  18. ];
  19. public function attachLocalName(array $categoryData, string $lang): array
  20. {
  21. $allNames = [];
  22. // 收集所有 name
  23. foreach ($categoryData as $item) {
  24. if (! empty($item['category']['name'])) {
  25. $allNames[] = $item['category']['name'];
  26. }
  27. foreach ($item['children'] as $child) {
  28. if (! empty($child['name'])) {
  29. $allNames[] = $child['name'];
  30. }
  31. }
  32. }
  33. // 去重
  34. $allNames = array_values(array_unique($allNames));
  35. // 查词典
  36. $terms = $this->glossaryByLemma($allNames, $lang);
  37. // 构建映射
  38. $termMap = [];
  39. if ($terms) {
  40. foreach ($terms as $term) {
  41. $termMap[$term->word] = $term->meaning;
  42. }
  43. }
  44. // 回填
  45. foreach ($categoryData as &$item) {
  46. $name = $item['category']['name'] ?? null;
  47. $item['category']['local_name'] = $termMap[$name] ?? $name;
  48. foreach ($item['children'] as &$child) {
  49. $childName = $child['name'] ?? null;
  50. $child['local_name'] = $termMap[$childName] ?? $childName;
  51. }
  52. }
  53. unset($item, $child);
  54. return $categoryData;
  55. }
  56. public function glossaryByLemma(array $words, string $lang)
  57. {
  58. $localTermChannel = ChannelApi::getSysChannel(
  59. '_community_term_' . strtolower($lang) . '_'
  60. );
  61. if (! $localTermChannel) {
  62. return null;
  63. }
  64. $result = DhammaTerm::select(['guid', 'word', 'tag', 'meaning', 'other_meaning'])
  65. ->whereIn('word', $words)
  66. ->where('channal', $localTermChannel)
  67. ->get();
  68. return $result;
  69. }
  70. public function getCommunityGlossary(string $lang)
  71. {
  72. $localTermChannel = ChannelApi::getSysChannel(
  73. '_community_term_' . strtolower($lang) . '_',
  74. '_community_term_en_'
  75. );
  76. $result = DhammaTerm::select([
  77. 'guid',
  78. 'word',
  79. 'tag',
  80. 'meaning',
  81. 'other_meaning'
  82. ])
  83. ->where('channal', $localTermChannel)
  84. ->get();
  85. return ['items' => $result, 'total' => count($result)];
  86. }
  87. public function getChannelGlossary(string $channelId)
  88. {
  89. $terms = DhammaTerm::whereNotNull('word')
  90. ->where('word', '!=', '')
  91. ->whereNotNull('word')
  92. ->where('channal', $channelId)
  93. ->select(['word', 'meaning', 'tag'])
  94. ->get();
  95. return $terms->toArray();
  96. }
  97. public function getGlossary()
  98. {
  99. // 1) dhamma_terms 全表 distinct 词条原形(不限 channel),排除 word 含空格的短语条目
  100. $dhammaWords = DhammaTerm::query()
  101. ->whereNotNull('word')
  102. ->where('word', '!=', '')
  103. ->where('word', 'not like', '% %')
  104. ->distinct()
  105. ->pluck('word')
  106. ->all();
  107. // 2) user_dicts 指定词典的词,去除连字符与空格后加入
  108. $userWords = UserDict::query()
  109. ->whereIn('dict_id', self::USER_DICT_IDS)
  110. ->whereNotNull('word')
  111. ->where('word', '!=', '')
  112. ->pluck('word')
  113. ->map(fn($w) => str_replace(['-', ' '], '', (string) $w))
  114. ->all();
  115. // 合并 + NFC 归一(避免巴利文变音符 NFC/NFD 不一致)+ 去空 + 去重
  116. $terms = array_map(fn($w) => $this->toNfc(trim((string) $w)), array_merge($dhammaWords, $userWords));
  117. $terms = array_values(array_unique(array_filter($terms, fn($w) => $w !== '')));
  118. return $terms;
  119. }
  120. /**
  121. * Unicode 规范化为 NFC(巴利文变音符匹配需统一 NFC/NFD)。intl 不可用时原样返回。
  122. */
  123. private function toNfc(string $s): string
  124. {
  125. if (class_exists(\Normalizer::class)) {
  126. $normalized = \Normalizer::normalize($s, \Normalizer::FORM_C);
  127. if ($normalized !== false) {
  128. return $normalized;
  129. }
  130. }
  131. return $s;
  132. }
  133. public function getGrammarGlossary(string $lang)
  134. {
  135. $localTermChannel = ChannelApi::getSysChannel(
  136. '_System_Grammar_Term_' . strtolower($lang) . '_',
  137. '_System_Grammar_Term_en_'
  138. );
  139. $result = DhammaTerm::select(['guid', 'word', 'tag', 'meaning', 'other_meaning'])
  140. ->where('channal', $localTermChannel)
  141. ->get();
  142. return ['items' => $result, 'total' => count($result)];
  143. }
  144. public function getRaw(string $id)
  145. {
  146. $result = DhammaTerm::find($id);
  147. return $result;
  148. }
  149. public function isCommunity(?string $channelId)
  150. {
  151. $channel = ChannelApi::getById($channelId);
  152. if (! $channel) {
  153. return false;
  154. }
  155. if (strpos($channel['name'], '_community_term_') === false) {
  156. return false;
  157. } else {
  158. return true;
  159. }
  160. }
  161. public function communityTerm(string $word, string $lang, string $format)
  162. {
  163. $localTermChannel = ChannelApi::getSysChannel(
  164. '_community_term_' . strtolower($lang) . '_'
  165. );
  166. $result = DhammaTerm::where('word', $word)
  167. ->where('channal', $localTermChannel)
  168. ->first();
  169. if ($result) {
  170. $resource = new TermResource($result);
  171. $urlParam = ['format' => $format];
  172. $fakeRequest = Request::create('', 'GET', $urlParam);
  173. $termArray = $resource->toArray($fakeRequest);
  174. if ($result) {
  175. return $termArray;
  176. } else {
  177. return null;
  178. }
  179. } else {
  180. return null;
  181. }
  182. }
  183. public function communityWiki(string $word, string $lang, string $format)
  184. {
  185. $localTermChannel = ChannelApi::getSysChannel(
  186. '_community_translation_' . strtolower($lang) . '_'
  187. );
  188. $result = DhammaTerm::where('word', $word)
  189. ->where('channal', $localTermChannel)
  190. ->first();
  191. if ($result) {
  192. $resource = new TermResource($result);
  193. $urlParam = ['format' => $format];
  194. $fakeRequest = Request::create('', 'GET', $urlParam);
  195. $termArray = $resource->toArray($fakeRequest);
  196. if ($result) {
  197. return $termArray;
  198. } else {
  199. return null;
  200. }
  201. } else {
  202. return null;
  203. }
  204. }
  205. public function communityTerms(string $lang)
  206. {
  207. $localTermChannel = ChannelApi::getSysChannel(
  208. '_community_term_' . strtolower($lang) . '_'
  209. );
  210. $result = DhammaTerm::where('channal', $localTermChannel)
  211. ->whereNotNull('note')
  212. ->where('note', '<>', '')
  213. ->take(10)
  214. ->orderBy('updated_at', 'desc')
  215. ->get();
  216. return [
  217. 'data' => TermResource::collection($result),
  218. 'count' => 10,
  219. ];
  220. }
  221. public function find(string $id, string $format): ?array
  222. {
  223. $result = DhammaTerm::find($id);
  224. $resource = new TermResource($result);
  225. $urlParam = ['format' => $format];
  226. $fakeRequest = Request::create('', 'GET', $urlParam);
  227. $termArray = $resource->toArray($fakeRequest);
  228. if ($result) {
  229. return $termArray;
  230. } else {
  231. return null;
  232. }
  233. }
  234. public function update(string $id, array $data)
  235. {
  236. DhammaTerm::where('guid', $id)->update($data);
  237. }
  238. /**
  239. * @param array{
  240. * word: string,
  241. * tag: string,
  242. * channal: string,
  243. * meaning: string,
  244. * other_meaning: string|null,
  245. * note: string|null,
  246. * editor_id: int,
  247. * } $data
  248. * @return string 返回记录的 id
  249. */
  250. public function updateOrCreateByWord(array $data): string
  251. {
  252. $now = time();
  253. $channelInfo = ChannelApi::getById($data['channel_id']);
  254. // 先查询是否存在
  255. $term = DhammaTerm::where('word', $data['word'])
  256. ->where('tag', $data['tag'] ?? null)
  257. ->where('channal', $data['channel_id'])
  258. ->first();
  259. if ($term) {
  260. // 已存在,直接更新
  261. $term->update([
  262. 'meaning' => $data['meaning'],
  263. 'other_meaning' => $data['other_meaning'] ?? null,
  264. 'note' => $data['note'] ?? null,
  265. 'redirect' => $data['redirect'] ?? null,
  266. 'editor_id' => $data['editor_id'],
  267. 'modify_time' => $now,
  268. ]);
  269. } else {
  270. // 不存在,新建(一次性写入所有字段)
  271. $term = new DhammaTerm;
  272. $term->id = app('snowflake')->id();
  273. $term->guid = (string) Str::uuid();
  274. $term->word = $data['word'];
  275. $term->tag = $data['tag'] ?? null;
  276. $term->channal = $data['channel_id'];
  277. $term->meaning = $data['meaning'];
  278. $term->other_meaning = $data['other_meaning'] ?? null;
  279. $term->note = $data['note'] ?? null;
  280. $term->redirect = $data['redirect'] ?? null;
  281. $term->editor_id = $data['editor_id']; // 注意:需传入 int 类型的 editor id
  282. $term->owner = $channelInfo['studio_id'];
  283. $term->word_en = Tools::getWordEn($data['word']);
  284. $term->language = $channelInfo['lang'] ?? 'zh-Hans';
  285. $term->create_time = $now;
  286. $term->modify_time = $now;
  287. $term->save();
  288. }
  289. return $term->guid;
  290. }
  291. /**
  292. * 按 channel + word 查找术语;不存在则新建一条空白词条(释义留待后续填充)。
  293. *
  294. * @return DhammaTerm 既有或新建的术语记录
  295. */
  296. public function findOrCreate(string $channelId, string $word): DhammaTerm
  297. {
  298. $term = DhammaTerm::where('word', $word)
  299. ->where('channal', $channelId)
  300. ->first();
  301. if ($term) {
  302. return $term;
  303. }
  304. $channelInfo = ChannelApi::getById($channelId);
  305. $now = time();
  306. $term = new DhammaTerm;
  307. $term->id = app('snowflake')->id();
  308. $term->guid = (string) Str::uuid();
  309. $term->word = $word;
  310. $term->meaning = $word;
  311. $term->channal = $channelId;
  312. $term->word_en = Tools::getWordEn($word);
  313. $term->owner = $channelInfo['studio_id'] ?? null;
  314. $term->language = $channelInfo['lang'] ?? 'en';
  315. $term->editor_id = 0;
  316. $term->create_time = $now;
  317. $term->modify_time = $now;
  318. $term->save();
  319. return $term;
  320. }
  321. }