const d = require('../questions.json'); let total = 0, bad = []; const setIds = new Set(), qIds = new Set(), setNames = new Set(); let maxSetId = 0, maxQId = 0; if (!d || !Array.isArray(d.sets)) { console.error('questions.json 根节点必须包含 sets 数组'); process.exit(1); } for (const set of d.sets) { if (!Number.isInteger(set.id) || set.id < 1 || setIds.has(set.id)) bad.push(`[套题] ID 非法或重复: ${set.id}`); else { setIds.add(set.id); maxSetId = Math.max(maxSetId, set.id); } const normalizedName = typeof set.name === 'string' ? set.name.trim().toLowerCase() : ''; if (!normalizedName || setNames.has(normalizedName)) bad.push(`[套题 #${set.id}] 名称为空或重复`); else setNames.add(normalizedName); if (typeof set.name === 'string' && set.name.trim().length > 80) bad.push(`[套题 #${set.id}] 名称超过 80 个字符`); if (set.description !== undefined && (typeof set.description !== 'string' || set.description.trim().length > 240)) { bad.push(`[${set.name || set.id}] description 必须为不超过 240 字的字符串`); } if (set.published !== undefined && typeof set.published !== 'boolean') bad.push(`[${set.name || set.id}] published 必须为布尔值`); if (!Array.isArray(set.questions)) { bad.push(`[${set.name || set.id}] questions 不是数组`); continue; } if (set.lvNames != null && (!Array.isArray(set.lvNames) || set.lvNames.length !== 6 || set.lvNames.some(n => typeof n !== 'string' || !n.trim()))) { bad.push(`[${set.name}] lvNames 必须为 6 个非空名称`); } for (const q of set.questions) { total++; if (!Number.isInteger(q.id) || q.id < 1 || qIds.has(q.id)) { bad.push(`[${set.name}] 题目 ID 非法或重复: ${q.id}`); continue; } qIds.add(q.id); maxQId = Math.max(maxQId, q.id); if (typeof q.q !== 'string' || !q.q.trim()) { bad.push(`[${set.name} #${q.id}] 题干必须为非空字符串`); continue; } if (!Array.isArray(q.opts) || q.opts.length < 2 || q.opts.some(o => typeof o !== 'string' || !o.trim())) { bad.push(`[${set.name} #${q.id}] 至少需要 2 个非空字符串选项`); continue; } if (!Number.isInteger(q.ans) || q.ans < 0 || q.ans >= q.opts.length) { bad.push(`[${set.name} #${q.id}] ans 越界`); continue; } if (typeof q.exp !== 'string' || !q.exp.trim()) { bad.push(`[${set.name} #${q.id}] 解析必须为非空字符串`); continue; } if (!Number.isInteger(q.lv) || q.lv < 1 || q.lv > 6) { bad.push(`[${set.name} #${q.id}] lv 非法`); continue; } const normalizedOpts = q.opts.map(o => o.trim()); const dup = normalizedOpts.findIndex((o, i) => normalizedOpts.indexOf(o) !== i); if (dup !== -1) { bad.push(`[${set.name} #${q.id}] 选项重复: "${q.opts[dup]}"`); } } } if (!Number.isInteger(d.nextSetId) || d.nextSetId <= maxSetId) bad.push(`nextSetId 应大于 ${maxSetId}`); if (!Number.isInteger(d.nextQId) || d.nextQId <= maxQId) bad.push(`nextQId 应大于 ${maxQId}`); console.log('总题数:', total); if (bad.length) { console.log('问题:'); bad.forEach(b => console.log(' -', b)); process.exitCode = 1; } else console.log('结构校验全部通过'); // 抽查逻辑已移除,因为选项顺序已被随机打乱