WbwLookupController.php 23 KB

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