update_card_examples.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // 为 UserID=2 的雅思卡片补充 ContentType=3 例句
  2. // 逻辑:从 ContentType=2 提取单词 -> 在雅思 Words(BookID 212-217) 取 ExampleSentence JSON -> 写入 ContentType=3
  3. // 用法:
  4. // DRY=1 node update_card_examples.js # 预检,打印匹配情况,不写库
  5. // node update_card_examples.js # 正式写入
  6. import { query } from '../../../src/util/db.js';
  7. const DRY = process.env.DRY === '1';
  8. const USER_ID = 2;
  9. const MIN_CARD_ID = 1671933;
  10. const BOOK_IDS = [212, 213, 214, 215, 216, 217];
  11. function extractWord(content) {
  12. if (!content) return null;
  13. // 1) [读 src='...text=WORD&...'] 中的 text= 参数
  14. const m1 = content.match(/text=([^&\s'"]+)/);
  15. if (m1) {
  16. try {
  17. const w = decodeURIComponent(m1[1]).trim();
  18. if (w) return w;
  19. } catch {
  20. /* ignore */
  21. }
  22. }
  23. // 2) 兜底:行首 "WORD 的基本中文释义"
  24. const m2 = content.match(/^([A-Za-z][A-Za-z'’.\-\s]*?)\s*的基本中文释义/);
  25. if (m2) return m2[1].trim();
  26. return null;
  27. }
  28. async function main() {
  29. const cards = await query(
  30. `select MiaoguoCardID from kylx365_db.MiaoguoCard where UserID=? and MiaoguoCardID>=? order by MiaoguoCardID`,
  31. [USER_ID, MIN_CARD_ID]
  32. );
  33. console.log(`目标卡片数: ${cards.length}`);
  34. if (!cards.length) return;
  35. const cardIds = cards.map((c) => c.MiaoguoCardID);
  36. const inSql = '(' + cardIds.join(',') + ')';
  37. const contents = await query(
  38. `select MiaoguoContentID, MiaoguoCardID, ContentType, Content from kylx365_db.MiaoguoContent where MiaoguoCardID in ${inSql} and ContentType in (2,3) order by MiaoguoCardID, ContentType`
  39. );
  40. // 建立 card -> {2: row, 3: row}
  41. const map = new Map();
  42. for (const r of contents) {
  43. if (!map.has(r.MiaoguoCardID)) map.set(r.MiaoguoCardID, {});
  44. map.get(r.MiaoguoCardID)[r.ContentType] = r;
  45. }
  46. // 收集所有要查的单词
  47. const wordToCards = new Map(); // lowerWord -> [{cardId, content3Id}]
  48. for (const [cardId, rec] of map) {
  49. const c2 = rec[2];
  50. if (!c2) continue;
  51. const w = extractWord(c2.Content);
  52. if (!w) {
  53. console.warn(` ✗ 无法提取单词 card=${cardId}`);
  54. continue;
  55. }
  56. const c3 = rec[3];
  57. if (!c3) {
  58. console.warn(` ✗ 缺少 ContentType=3 行 card=${cardId}`);
  59. continue;
  60. }
  61. const key = w.toLowerCase();
  62. if (!wordToCards.has(key)) wordToCards.set(key, []);
  63. wordToCards.get(key).push({ cardId, content3Id: c3.MiaoguoContentID, word: w });
  64. }
  65. // 一次性查雅思例句
  66. const words = [...wordToCards.keys()];
  67. const examples = new Map();
  68. if (words.length) {
  69. const rows = await query(
  70. `select Word, ExampleSentence from kylx365_db.Words where BookID in (${BOOK_IDS.join(',')}) and LOWER(Word) in (?)`,
  71. [words]
  72. );
  73. for (const r of rows) examples.set(r.Word.toLowerCase(), r.ExampleSentence);
  74. }
  75. let matched = 0,
  76. unmatched = 0,
  77. updated = 0,
  78. skipped = 0;
  79. for (const [, list] of wordToCards) {
  80. for (const item of list) {
  81. const ex = examples.get(item.word.toLowerCase());
  82. if (!ex) {
  83. unmatched++;
  84. console.warn(` ✗ 雅思词库无例句: "${item.word}" (card=${item.cardId})`);
  85. continue;
  86. }
  87. matched++;
  88. if (DRY) {
  89. console.log(` · ${item.word} (card=${item.cardId}) -> 例句 ${ex.length} 字符`);
  90. continue;
  91. }
  92. await query('update kylx365_db.MiaoguoContent set Content=? where MiaoguoContentID=?', [
  93. ex,
  94. item.content3Id,
  95. ]);
  96. updated++;
  97. }
  98. }
  99. console.log(
  100. `\n预检=${DRY ? '是' : '否'} | 卡片派生词 ${wordToCards.size} | 匹配例句 ${matched} | 无例句 ${unmatched} | 已写入 ${updated}`
  101. );
  102. }
  103. main()
  104. .then(() => process.exit(0))
  105. .catch((e) => {
  106. console.error(e);
  107. process.exit(1);
  108. });