| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- /**
- * 雅思词汇导入秒过知识库
- * 用法:
- * node ielts/import_ielts.js --dry-run # 预检,不写库
- * node ielts/import_ielts.js --import # 正式导入
- *
- * 数据源: ielts/ielts-xdf-random.json (新东方3050词,有音标/词性/中文释义)
- * ielts/ielts-4000.json (526词,仅英文释义)
- * 合并去重后 3346 词, 分6本书 (BookID 212-217)
- * 4000独有的296词: 优先从现有四六级/高考词库取中文释义+音标, 否则用英文释义
- */
- import { getConnection, query } from '../../../src/util/db.js';
- import fs from 'fs';
- const DRY_RUN = process.argv.includes('--dry-run');
- const IMPORT = process.argv.includes('--import');
- if (!DRY_RUN && !IMPORT) {
- console.error('请指定 --dry-run 或 --import');
- process.exit(1);
- }
- const BOOK_COUNT = 6;
- const WORDS_PER_LESSON = 10;
- const START_BOOK_ID = 212; // WordBooks 新ID (当前最大211)
- const SORT = 14; // 排在六级(13)之后
- const CATEGORY = '雅思英语';
- const CATEGORY2 = '雅思英语';
- const CATEGORY3 = '雅思考试单词';
- const GRADE = '大学';
- const BOOK_IMAGE_NAME = 'ieltsWords';
- const REMARK = '雅思备考核心词汇,提升考试通过率。';
- const TEST_FUNCTION = JSON.stringify([
- { ID: 1, N: "read", N2: "念单词说含义", C: 0, Img: "picJygs_13", Remark: "题目为单词,答出它的发音,配释义等信息。" },
- { ID: 2, N: "write", N2: "听写单词", C: 0, Img: "picJygs_14", Remark: "题目为单词发音和释义,写出单词。" }
- ]);
- const PART_NAMES = ['第一部分', '第二部分', '第三部分', '第四部分', '第五部分', '第六部分'];
- const norm = (w) => String(w || '').trim().toLowerCase();
- function normalizeSoundmark(p) {
- if (!p) return null;
- let s = String(p).trim();
- if (!s) return null;
- // 统一为 [xxx] 格式 (参照四级 "[əˈbændən]")
- if (s.startsWith('/') && s.endsWith('/')) s = s.slice(1, -1);
- else if (s.startsWith('[') && s.endsWith(']')) s = s.slice(1, -1);
- s = s.trim();
- if (!s) return null;
- return '[' + s + ']';
- }
- function buildTranslate(pos, zh) {
- let t = '';
- const p = String(pos || '').trim();
- const z = String(zh || '').trim();
- if (p && z) t = p + z; // 参照四级 "vt.丢弃 放弃 抛弃"
- else t = z || p;
- return t || null;
- }
- async function main() {
- // ---------- 1. 读取并合并数据 ----------
- const xdf = JSON.parse(fs.readFileSync(new URL('./ielts-xdf-random.json', import.meta.url), 'utf8'))
- .filter(x => norm(x.word) !== 'word' && norm(x.word) !== ''); // 去掉表头行
- const awl = JSON.parse(fs.readFileSync(new URL('./ielts-4000.json', import.meta.url), 'utf8'));
- const merged = new Map(); // key: norm word -> record
- for (const item of xdf) {
- const key = norm(item.word);
- if (merged.has(key)) continue;
- merged.set(key, {
- word: String(item.word).trim(),
- soundmark: normalizeSoundmark(item.phonetic),
- translate: buildTranslate(item.pos, item.zh),
- source: 'xdf'
- });
- }
- const xdfCount = merged.size;
- const onlyAwl = [];
- for (const item of awl) {
- const key = norm(item.word);
- if (merged.has(key)) continue;
- onlyAwl.push({ word: String(item.word).trim(), def: String(item.def || '').trim() });
- merged.set(key, {
- word: String(item.word).trim(),
- soundmark: null,
- translate: null,
- def: String(item.def || '').trim(),
- source: 'awl'
- });
- }
- // ---------- 2. 从现有四六级/高考词库补释义和音标 ----------
- const dbRef = await query(
- "SELECT Word, Translate, Soundmark FROM kylx365_db.Words WHERE BookID BETWEEN 169 AND 183 AND LOWER(Word) IN (?)",
- [onlyAwl.map(x => norm(x.word))]
- );
- const refMap = new Map();
- for (const r of dbRef) {
- const k = norm(r.Word);
- if (!refMap.has(k)) refMap.set(k, r);
- }
- let refFilled = 0, defFilled = 0;
- for (const item of onlyAwl) {
- const rec = merged.get(norm(item.word));
- const ref = refMap.get(norm(item.word));
- if (ref) {
- rec.translate = ref.Translate;
- rec.soundmark = ref.Soundmark ? normalizeSoundmark(ref.Soundmark) : null;
- refFilled++;
- } else {
- rec.translate = rec.def; // 英文释义填入
- defFilled++;
- }
- }
- // ---------- 3. 分书 ----------
- const ordered = [...xdf].map(x => merged.get(norm(x.word))).filter(Boolean); // 保持xdf乱序源顺序
- for (const item of onlyAwl) ordered.push(merged.get(norm(item.word))); // 4000独有词按原字母序追加在后
- const total = ordered.length;
- const base = Math.floor(total / BOOK_COUNT); // 557
- const extra = total - base * BOOK_COUNT; // 4
- // 前 extra 本 base+1 词, 其余 base 词 → 558*4 + 557*2 = 3346
- const books = [];
- let idx = 0;
- for (let i = 0; i < BOOK_COUNT; i++) {
- const n = i < extra ? base + 1 : base;
- books.push({ part: i + 1, bookID: START_BOOK_ID + i, words: ordered.slice(idx, idx + n) });
- idx += n;
- }
- console.log(`合并统计: xdf=${xdfCount}, awl独有=${onlyAwl.length}(四六级补释义=${refFilled}, 英文释义=${defFilled}), 总计=${total}`);
- for (const b of books)
- console.log(` BookID=${b.bookID} ${PART_NAMES[b.part - 1]}: ${b.words.length}词, ${Math.ceil(b.words.length / WORDS_PER_LESSON)}个Lesson`);
- // ---------- 4. 校验 ----------
- const [maxWB] = await query("SELECT MAX(ID) m FROM kylx365_db.WordBooks;");
- const [maxMG] = await query("SELECT MAX(ID) m FROM MiaoguoBook;");
- const [exist] = await query("SELECT COUNT(*) c FROM kylx365_db.WordBooks WHERE Category=?;", [CATEGORY]);
- console.log(`当前最大ID: WordBooks=${maxWB.m}, MiaoguoBook=${maxMG.m}; 已存在"${CATEGORY}"记录=${exist.c}`);
- if (maxWB.m >= START_BOOK_ID || exist.c > 0) {
- console.error('中止: ID已被占用或已存在雅思记录, 请人工检查!');
- process.exit(1);
- }
- if (DRY_RUN) {
- console.log('\n=== 预览: 每本书前3词 ===');
- for (const b of books) {
- console.log(`BookID=${b.bookID}:`);
- for (const w of b.words.slice(0, 3))
- console.log(` ${w.word} ${w.soundmark || '(无音标)'} ${w.translate || '(无释义)'}`);
- }
- console.log('\n[dry-run] 校验通过, 未写库.');
- process.exit(0);
- }
- // ---------- 5. 导入 (事务) ----------
- const conn = await getConnection();
- try {
- await conn.beginTransaction();
- // 5.1 WordBooks
- for (const b of books) {
- const lessonNum = Math.ceil(b.words.length / WORDS_PER_LESSON);
- await conn.query(
- `INSERT INTO kylx365_db.WordBooks (ID,Sort,Category,Name,Name2,Total,Image,Grade,Category2,Category3,WordType,Remark,Package,BookImageName,WordNum,Flag)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)`,
- [b.bookID, SORT, CATEGORY, PART_NAMES[b.part - 1], `词汇${b.words.length}`, lessonNum,
- `../images/examine_subject_a00${b.part}.png`, GRADE, CATEGORY2, CATEGORY3, '单词', REMARK,
- PART_NAMES[b.part - 1], BOOK_IMAGE_NAME, b.words.length]
- );
- }
- // 5.2 MiaoguoBook
- for (const b of books) {
- const lessonNum = Math.ceil(b.words.length / WORDS_PER_LESSON);
- await conn.query(
- `INSERT INTO MiaoguoBook (BookIDOld,Category,LibraryName1,LibraryName2,Grade,Category2,BookName,Remark,WordType,UnitNum,StartID,WordNum,BookImageName,KnowledgeImageName,TestFunction,Sort,Flag)
- VALUES (?,?,?,?,?,?,?,?,?,?,0,?,?,?,?,?,0)`,
- [b.bookID, 'English', CATEGORY2, CATEGORY3, GRADE, '课外拓展', PART_NAMES[b.part - 1], REMARK,
- '单词', lessonNum, b.words.length, BOOK_IMAGE_NAME, '', TEST_FUNCTION, SORT]
- );
- }
- // 5.3 Words (批量插入, 每批500条)
- let inserted = 0;
- const BATCH = 500;
- const rows = [];
- for (const b of books) {
- for (let i = 0; i < b.words.length; i++) {
- const w = b.words[i];
- rows.push([w.word, b.bookID, Math.floor(i / WORDS_PER_LESSON) + 1, w.soundmark, w.translate]);
- }
- }
- for (let i = 0; i < rows.length; i += BATCH) {
- const chunk = rows.slice(i, i + BATCH);
- await conn.query(
- `INSERT INTO kylx365_db.Words (Word,BookID,LessonID,Soundmark,Translate,Sort)
- VALUES ${chunk.map(() => '(?,?,?,?,?,0)').join(',')}`,
- [chunk.flat()]
- );
- inserted += chunk.length;
- console.log(` Words 已插入 ${inserted}/${rows.length}`);
- }
- await conn.commit();
- console.log(`\n导入完成: WordBooks 6条, MiaoguoBook 6条, Words ${inserted}条.`);
- } catch (e) {
- await conn.rollback();
- console.error('导入失败, 已回滚:', e.message);
- process.exit(1);
- } finally {
- conn.release();
- }
- // ---------- 6. 验证 ----------
- const wb = await query("SELECT ID,Category,Name,Name2,WordNum,BookImageName,Sort,Flag FROM kylx365_db.WordBooks WHERE ID BETWEEN 212 AND 217;");
- const mg = await query("SELECT ID,BookIDOld,Category,LibraryName1,LibraryName2,BookName,UnitNum,WordNum,Sort,Flag FROM MiaoguoBook WHERE BookIDOld BETWEEN 212 AND 217;");
- const wc = await query("SELECT BookID,COUNT(*) c,COUNT(DISTINCT LOWER(Word)) d FROM kylx365_db.Words WHERE BookID BETWEEN 212 AND 217 GROUP BY BookID;");
- console.log('\n=== 验证 WordBooks ===\n' + JSON.stringify(wb));
- console.log('=== 验证 MiaoguoBook ===\n' + JSON.stringify(mg));
- console.log('=== 验证 Words ===\n' + JSON.stringify(wc));
- process.exit(0);
- }
- main().catch(e => { console.error(e); process.exit(1); });
|