WbwLookupController.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Api\DictApi;
  4. use App\Models\Channel;
  5. use App\Models\DictInfo;
  6. use App\Models\UserDict;
  7. use App\Models\WbwTemplate;
  8. use App\Services\AuthService;
  9. use App\Tools\CaseMan;
  10. use Illuminate\Http\Request;
  11. use Illuminate\Http\Response;
  12. use Illuminate\Support\Facades\Cache;
  13. class WbwLookupController extends Controller
  14. {
  15. private $dictList = [
  16. '85dcc61c-c9e1-4ae0-9b44-cd6d9d9f0d01', // 社区汇总
  17. '4d3a0d92-0adc-4052-80f5-512a2603d0e8', // system irregular
  18. '8359757e-9575-455b-a772-cc6f036caea0', // system sandhi
  19. '61f23efb-b526-4a8e-999e-076965034e60', // pali myanmar grammar
  20. 'eae9fd6f-7bac-4940-b80d-ad6cd6f433bf', // Concise P-E Dict
  21. '2f93d0fe-3d68-46ee-a80b-11fa445a29c6', // unity
  22. 'beb45062-7c20-4047-bcd4-1f636ba443d1', // U Hau Sein
  23. '8833de18-0978-434c-b281-a2e7387f69be', // 巴汉增订
  24. '3acf0c0f-59a7-4d25-a3d9-bf394a266ebd', // 汉译パーリ语辞典-黃秉榮
  25. '9ce6a53b-e28f-4fb7-b69d-b35fd5d76a24', // 缅英字典
  26. ];
  27. /**
  28. * Create a new command instance.
  29. *
  30. * @return void
  31. */
  32. private function initSysDict()
  33. {
  34. // system regular
  35. $this->dictList[] = DictApi::getSysDict('system_regular');
  36. $this->dictList[] = DictApi::getSysDict('robot_compound');
  37. $this->dictList[] = DictApi::getSysDict('community');
  38. $this->dictList[] = DictApi::getSysDict('community_extract');
  39. }
  40. /**
  41. * Display a listing of the resource.
  42. *
  43. * @return Response
  44. */
  45. public function index(Request $request)
  46. {
  47. //
  48. $startAt = microtime(true) * 1000;
  49. $this->initSysDict();
  50. $words = \explode(',', $request->input('word'));
  51. $bases = \explode(',', $request->input('base'));
  52. // 查询深度
  53. $deep = $request->input('deep', 2);
  54. $result = $this->lookup($words, $bases, $deep);
  55. $endAt = microtime(true) * 1000;
  56. return $this->ok([
  57. 'rows' => $result,
  58. 'count' => count($result),
  59. 'time' => (int) ($endAt - $startAt),
  60. ]);
  61. }
  62. // 查用户字典获取全部结果
  63. public function lookup($words, $bases, $deep)
  64. {
  65. $wordPool = [];
  66. $output = [];
  67. foreach ($words as $word) {
  68. $wordPool[$word] = ['base' => false, 'done' => false, 'apply' => false];
  69. }
  70. foreach ($bases as $base) {
  71. $wordPool[$base] = ['base' => true, 'done' => false, 'apply' => false];
  72. }
  73. /**
  74. * 先查询字典名称
  75. */
  76. $dict_info = DictInfo::whereIn('id', $this->dictList)->select('id', 'shortname')->get();
  77. $dict_name = [];
  78. foreach ($dict_info as $key => $value) {
  79. // code...
  80. $dict_name[$value->id] = $value->shortname;
  81. }
  82. $caseman = new CaseMan;
  83. for ($i = 0; $i < $deep; $i++) {
  84. $newBase = [];
  85. $newWords = [];
  86. foreach ($wordPool as $word => $info) {
  87. // code...
  88. if ($info['done'] === false) {
  89. $newWords[] = $word;
  90. $wordPool[$word]['done'] = true;
  91. }
  92. }
  93. $data = UserDict::whereIn('word', $newWords)
  94. ->whereIn('dict_id', $this->dictList)
  95. ->leftJoin('dict_infos', 'user_dicts.dict_id', '=', 'dict_infos.id')
  96. ->orderBy('confidence', 'desc')
  97. ->get();
  98. foreach ($data as $row) {
  99. // code...
  100. array_push($output, $row);
  101. if (! empty($row->parent) && ! isset($wordPool[$row->parent])) {
  102. // 将parent 插入待查询列表
  103. $wordPool[$row->parent] = ['base' => true, 'done' => false, 'apply' => false];
  104. }
  105. }
  106. // 处理查询结果中的拆分信息
  107. $newWordPart = [];
  108. foreach ($wordPool as $word => $info) {
  109. if (! empty($info['factors'])) {
  110. $factors = \explode('+', $info['factors']);
  111. foreach ($factors as $factor) {
  112. // 将没有的拆分放入单词查询列表
  113. if (! isset($wordPool[$factor])) {
  114. $wordPool[$factor] = ['base' => true, 'done' => false, 'apply' => false];
  115. }
  116. }
  117. }
  118. }
  119. }
  120. return $output;
  121. }
  122. private function langCheck($query, $lang)
  123. {
  124. if ($query === []) {
  125. return true;
  126. } else {
  127. if (in_array(strtolower($lang), $query)) {
  128. return true;
  129. } else {
  130. $langFamily = explode('-', $lang)[0];
  131. foreach ($query as $value) {
  132. if (strpos($value, $langFamily) !== false) {
  133. return true;
  134. }
  135. }
  136. }
  137. }
  138. return false;
  139. }
  140. private function wbwPreference($word, $field, $userId)
  141. {
  142. $prefix = 'wbw-preference';
  143. $fieldMap = [
  144. 'type' => 1,
  145. 'grammar' => 2,
  146. 'meaning' => 3,
  147. 'factors' => 4,
  148. 'factorMeaning' => 5,
  149. 'parent' => 6,
  150. 'part' => 7,
  151. 'case' => 8,
  152. ];
  153. $fieldId = $fieldMap[$field];
  154. $myPreference = Cache::get("{$prefix}/{$word}/{$fieldId}/{$userId}");
  155. if (! empty($myPreference)) {
  156. return ['value' => $myPreference, 'status' => 5];
  157. } else {
  158. $myPreference = Cache::get("{$prefix}/{$word}/3/0");
  159. if (! empty($myPreference)) {
  160. return ['value' => $myPreference, 'status' => 5];
  161. }
  162. }
  163. return false;
  164. }
  165. /**
  166. * 自动查词
  167. *
  168. * @return Response
  169. */
  170. public function store(Request $request)
  171. {
  172. //
  173. $user = AuthService::current($request);
  174. if (! $user) {
  175. // 未登录用户
  176. return $this->error(__('auth.failed'), 401, 401);
  177. }
  178. $startAt = microtime(true) * 1000;
  179. // system regular
  180. $this->initSysDict();
  181. $channel = Channel::find($request->input('channel_id'));
  182. $orgData = $request->input('data');
  183. $lang = [];
  184. foreach ($request->input('lang', []) as $value) {
  185. $lang[] = strtolower($value);
  186. }
  187. // 句子中的单词
  188. $words = [];
  189. foreach ($orgData as $word) {
  190. // code...
  191. if (isset($word['type']) && $word['type']['value'] === '.ctl.') {
  192. continue;
  193. }
  194. if (! empty($word['real']['value'])) {
  195. $words[] = $word['real']['value'];
  196. }
  197. }
  198. $result = $this->lookup($words, [], 2);
  199. $indexed = $this->toIndexed($result);
  200. foreach ($orgData as $key => $word) {
  201. if (isset($word['type']) && $word['type']['value'] === '.ctl.') {
  202. continue;
  203. }
  204. if (empty($word['real']['value'])) {
  205. continue;
  206. }
  207. $data = $word;
  208. $preference = $this->wbwPreference($word['real']['value'], 'meaning', $user['user_id']);
  209. if ($preference !== false) {
  210. $data['meaning'] = $preference;
  211. }
  212. $preference = $this->wbwPreference($word['real']['value'], 'factors', $user['user_id']);
  213. if ($preference !== false) {
  214. $data['factors'] = $preference;
  215. }
  216. $preference = $this->wbwPreference($word['real']['value'], 'factorMeaning', $user['user_id']);
  217. if ($preference !== false) {
  218. $data['factorMeaning'] = $preference;
  219. }
  220. $preference = $this->wbwPreference($word['real']['value'], 'case', $user['user_id']);
  221. if ($preference !== false) {
  222. $data['case'] = $preference;
  223. }
  224. $preference = $this->wbwPreference($word['real']['value'], 'parent', $user['user_id']);
  225. if ($preference !== false) {
  226. $data['parent'] = $preference;
  227. }
  228. if (isset($indexed[$word['real']['value']])) {
  229. // parent
  230. $case = [];
  231. $parent = [];
  232. $factors = [];
  233. $factorMeaning = [];
  234. $meaning = [];
  235. $parent2 = [];
  236. $case2 = [];
  237. foreach ($indexed[$word['real']['value']] as $value) {
  238. // 非base优先
  239. if (strstr($value->type, 'base') === false) {
  240. $increment = 10;
  241. } else {
  242. $increment = 1;
  243. }
  244. // 将全部结果加上得分放入数组
  245. if ($value->type !== '.cp.') {
  246. $parent = $this->insertValue([$value->parent], $parent, $increment);
  247. }
  248. if (isset($data['case']) && $data['case']['status'] < 5) {
  249. if (! empty($value->type) && $value->type !== '.cp.') {
  250. $case = $this->insertValue([$value->type.'#'.$value->grammar], $case, $increment);
  251. }
  252. }
  253. if ($data['factors']['status'] < 50) {
  254. $factors = $this->insertValue([$value->factors], $factors, $increment);
  255. }
  256. if (isset($data['factorMeaning']) && $data['factorMeaning']['status'] < 50) {
  257. $factorMeaning = $this->insertValue([$value->factormean], $factorMeaning, $increment, false);
  258. }
  259. if ($data['meaning']['status'] < 50) {
  260. if ($this->langCheck($lang, $value->language)) {
  261. $meaning = $this->insertValue(explode('$', $value->mean), $meaning, $increment, false);
  262. }
  263. }
  264. }
  265. if (count($case) > 0) {
  266. arsort($case);
  267. $first = array_keys($case)[0];
  268. $data['case'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  269. }
  270. if (count($parent) > 0) {
  271. arsort($parent);
  272. $first = array_keys($parent)[0];
  273. $data['parent'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  274. } else {
  275. $data['parent'] = ['value' => '', 'status' => 3];
  276. }
  277. if (count($factors) > 0 && empty($data['factors']['value'])) {
  278. arsort($factors);
  279. $first = array_keys($factors)[0];
  280. $data['factors'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  281. }
  282. if (count($factorMeaning) > 0) {
  283. arsort($factorMeaning);
  284. $first = array_keys($factorMeaning)[0];
  285. $data['factorMeaning'] = ['value' => $first === '_null' ? '' : $first, 'status' => 5];
  286. }
  287. if (isset($data['factorMeaning']) && $data['factorMeaning']['status'] < 5) {
  288. $wbwFactorMeaning = [];
  289. if (! empty($data['factors']['value'])) {
  290. foreach (explode('+', $data['factors']['value']) as $factor) {
  291. $preference = $this->wbwPreference($factor, 'meaning', $user['user_id']);
  292. if ($preference !== false) {
  293. $wbwFactorMeaning[] = $preference['value'];
  294. } else {
  295. $wbwFactorMeaning[] = $factor;
  296. }
  297. }
  298. }
  299. $data['factorMeaning'] = ['value' => implode('+', $wbwFactorMeaning), 'status' => 3];
  300. }
  301. if (empty($data['meaning']['value']) && ! empty($data['parent']['value'])) {
  302. if (isset($indexed[$data['parent']['value']])) {
  303. foreach ($indexed[$data['parent']['value']] as $value) {
  304. // 根据base 查找词意
  305. // 非base优先
  306. $increment = 10;
  307. if ($this->langCheck($lang, $value->language)) {
  308. $meaning = $this->insertValue(explode('$', $value->mean), $meaning, $increment, false);
  309. }
  310. // 查找词源
  311. if (! empty($value->parent) && $value->parent !== $value->word && strstr($value->type, 'base') !== false) {
  312. $parent2 = $this->insertValue([$value->grammar.'$'.$value->parent], $parent2, 1, false);
  313. }
  314. }
  315. }
  316. }
  317. if (count($meaning) > 0) {
  318. arsort($meaning);
  319. $first = array_keys($meaning)[0];
  320. $data['meaning'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  321. }
  322. if (count($parent2) > 0) {
  323. arsort($parent2);
  324. $first = explode('$', array_keys($parent2)[0]);
  325. $data['parent2'] = ['value' => $first[1], 'status' => 3];
  326. $data['grammar2'] = ['value' => $first[0], 'status' => 3];
  327. }
  328. }
  329. if (
  330. ! isset($data['factorMeaning']['value']) ||
  331. $this->fmEmpty($data['factorMeaning']['value'])
  332. ) {
  333. $factorMeaning = [];
  334. // 生成自动的拆分意思
  335. $autoMeaning = '';
  336. $currFactors = explode('+', $data['factors']['value']);
  337. $autoFM = [];
  338. foreach ($currFactors as $factor) {
  339. $subFactors = explode('-', $factor);
  340. $autoSubFM = [];
  341. foreach ($subFactors as $subFactor) {
  342. $preference = $this->wbwPreference($subFactor, 'factorMeaning', $user['user_id']);
  343. if ($preference !== false) {
  344. $autoSubFM[] = $preference['value'];
  345. } else {
  346. $preference = $this->wbwPreference($subFactor, 'meaning', $user['user_id']);
  347. if ($preference !== false) {
  348. $autoSubFM[] = $preference['value'];
  349. } else {
  350. $autoSubFM[] = '';
  351. }
  352. }
  353. }
  354. $autoFM[] = implode('-', $autoSubFM);
  355. $autoMeaning .= implode('', $autoSubFM);
  356. }
  357. $autoMeaning .= implode('', $autoFM);
  358. if (count($autoFM) > 0) {
  359. $data['factorMeaning'] = ['value' => implode('+', $autoFM), 'status' => 3];
  360. if (empty($data['meaning']['value']) && ! empty($autoMeaning)) {
  361. $data['meaning'] = ['value' => $autoMeaning, 'status' => 3];
  362. }
  363. }
  364. }
  365. $orgData[$key] = $data;
  366. }
  367. return $this->ok($orgData);
  368. }
  369. private function fmEmpty($value)
  370. {
  371. if (str_replace(['+', '-', ' '], '', $value) === '') {
  372. return true;
  373. } else {
  374. return false;
  375. }
  376. }
  377. /**
  378. * 自动查词
  379. *
  380. * @return Response
  381. */
  382. public function show(Request $request, string $sentId)
  383. {
  384. $startAt = microtime(true) * 1000;
  385. $channel = Channel::find($request->input('channel_id'));
  386. // 查询句子中的单词
  387. $sent = \explode('-', $sentId);
  388. $wbw = WbwTemplate::where('book', $sent[0])
  389. ->where('paragraph', $sent[1])
  390. ->whereBetween('wid', [$sent[2], $sent[3]])
  391. ->orderBy('wid')
  392. ->get();
  393. $words = [];
  394. foreach ($wbw as $row) {
  395. if ($row->type !== '.ctl.' && ! empty($row->real)) {
  396. $words[] = $row->real;
  397. }
  398. }
  399. $result = $this->lookup($words, [], 2);
  400. $indexed = $this->toIndexed($result);
  401. // 生成自动填充结果
  402. $wbwContent = [];
  403. foreach ($wbw as $row) {
  404. $type = $row->type == '?' ? '' : $row->type;
  405. $grammar = $row->gramma == '?' ? '' : $row->gramma;
  406. $part = $row->part == '?' ? '' : $row->part;
  407. if (! empty($type) || ! empty($grammar)) {
  408. $case = "{$type}#$grammar";
  409. } else {
  410. $case = '';
  411. }
  412. $data = [
  413. 'sn' => [$row->wid],
  414. 'word' => ['value' => $row->word, 'status' => 3],
  415. 'real' => ['value' => $row->real, 'status' => 3],
  416. 'meaning' => ['value' => [], 'status' => 3],
  417. 'type' => ['value' => $type, 'status' => 3],
  418. 'grammar' => ['value' => $grammar, 'status' => 3],
  419. 'case' => ['value' => $case, 'status' => 3],
  420. 'style' => ['value' => $row->style, 'status' => 3],
  421. 'factors' => ['value' => $part, 'status' => 3],
  422. 'factorMeaning' => ['value' => '', 'status' => 3],
  423. 'confidence' => 0.5,
  424. ];
  425. if ($row->type !== '.ctl.' && ! empty($row->real)) {
  426. if (isset($indexed[$row->real])) {
  427. // parent
  428. $case = [];
  429. $parent = [];
  430. $factors = [];
  431. $factorMeaning = [];
  432. $meaning = [];
  433. $parent2 = [];
  434. $case2 = [];
  435. foreach ($indexed[$row->real] as $value) {
  436. // 非base优先
  437. if (strstr($value->type, 'base') === false) {
  438. $increment = 10;
  439. } else {
  440. $increment = 1;
  441. }
  442. // 将全部结果加上得分放入数组
  443. $parent = $this->insertValue([$value->parent], $parent, $increment);
  444. $case = $this->insertValue([$value->type.'#'.$value->grammar], $case, $increment);
  445. $factors = $this->insertValue([$value->factors], $factors, $increment);
  446. $factorMeaning = $this->insertValue([$value->factormean], $factorMeaning, $increment);
  447. $meaning = $this->insertValue(explode('$', $value->mean), $meaning, $increment, false);
  448. }
  449. if (count($case) > 0) {
  450. arsort($case);
  451. $first = array_keys($case)[0];
  452. $data['case'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  453. }
  454. if (count($parent) > 0) {
  455. arsort($parent);
  456. $first = array_keys($parent)[0];
  457. $data['parent'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  458. }
  459. if (count($factors) > 0) {
  460. arsort($factors);
  461. $first = array_keys($factors)[0];
  462. $data['factors'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  463. }
  464. if (count($factorMeaning) > 0) {
  465. arsort($factorMeaning);
  466. $first = array_keys($factorMeaning)[0];
  467. $data['factorMeaning'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  468. }
  469. // 根据base 查找词意
  470. if (! empty($data['parent'])) {
  471. if (isset($indexed[$data['parent']['value']])) {
  472. foreach ($indexed[$data['parent']['value']] as $value) {
  473. // 非base优先
  474. $increment = 10;
  475. $meaning = $this->insertValue(explode('$', $value->mean), $meaning, $increment, false);
  476. }
  477. } else {
  478. // Log::error("no set parent".$data['parent']['value']);
  479. }
  480. }
  481. if (count($meaning) > 0) {
  482. arsort($meaning);
  483. $first = array_keys($meaning)[0];
  484. $data['meaning'] = ['value' => $first === '_null' ? '' : $first, 'status' => 3];
  485. }
  486. }
  487. }
  488. $wbwContent[] = $data;
  489. }
  490. $endAt = microtime(true) * 1000;
  491. return $this->ok([
  492. 'rows' => $wbwContent,
  493. 'count' => count($wbwContent),
  494. 'time' => (int) ($endAt - $startAt),
  495. ]);
  496. }
  497. private function toIndexed($words)
  498. {
  499. // 转成索引数组
  500. $indexed = [];
  501. foreach ($words as $key => $value) {
  502. // code...
  503. $indexed[$value->word][] = $value;
  504. }
  505. return $indexed;
  506. }
  507. /**
  508. * $empty:是否允许空值
  509. */
  510. private function insertValue($value, $container, $increment, $empty = true)
  511. {
  512. foreach ($value as $one) {
  513. if ($empty === false) {
  514. if ($this->fmEmpty($one)) {
  515. break;
  516. }
  517. }
  518. $one = trim($one);
  519. $key = $one;
  520. if (empty($key)) {
  521. $key = '_null';
  522. }
  523. if (isset($container[$key])) {
  524. $container[$key] += $increment;
  525. } else {
  526. $container[$key] = $increment;
  527. }
  528. }
  529. return $container;
  530. }
  531. /**
  532. * Update the specified resource in storage.
  533. *
  534. * @return Response
  535. */
  536. public function update(Request $request, UserDict $userDict)
  537. {
  538. //
  539. }
  540. /**
  541. * Remove the specified resource from storage.
  542. *
  543. * @return Response
  544. */
  545. public function destroy(UserDict $userDict)
  546. {
  547. //
  548. }
  549. }