/** * 把旧库数据编排成新库可直接写入的数据集。 * * 逻辑集中在这里,migrate.ts(写库)与 transform.test.ts(校验)共用同一份实现, * 避免两边各写一遍导致漂移。 */ import { asString, type LegacyRows } from './load'; import { HOME_COURSE_TITLE_MAP, OPENID_RE, transformArticle, transformBanner, transformCourse, transformCourseTip, transformEntitlement, transformFreeLesson, transformHomeCourse, transformHomeFreeCourse, transformLesson, transformSetting, transformUser, } from './transform'; export type Dataset = ReturnType; export function buildDataset(db: LegacyRows, staticBase: string) { // 先建课程,得到 code → 自增 id 的映射(新库 id 从 1 开始,与插入顺序一致) const courses = db.课程口令码读取信息.map(transformCourse); const courseIdByCode = new Map(courses.map((c, i) => [c.code, i + 1])); const courseIdByTitle = new Map(courses.map((c, i) => [c.title, i + 1])); // 过滤旧库里混入的攻击载荷:只有符合 openid 形态的才当作真实用户 const skippedUsers: Array<{ openid: string; reason: string }> = []; const users = db.用户信息.flatMap((row) => { const openid = asString(row.OpenID); if (!OPENID_RE.test(openid)) { skippedUsers.push({ openid: openid.slice(0, 60), reason: '非法 openid 形态(疑似攻击载荷)' }); return []; } return [transformUser(row)]; }); const userIdByOpenid = new Map(users.map((u, i) => [u.openid, i + 1])); const skippedLessonOrphans: string[] = []; const lessons = db.视频课程数据.flatMap((row) => { const courseId = courseIdByCode.get(asString(row.课程id)); if (!courseId) { skippedLessonOrphans.push(asString(row.课程id)); return []; } return [transformLesson(row, courseId)]; }); // 权益:按 (user, course) 去重,重复的只保留第一条 const seen = new Set(); const duplicateEntitlements: string[] = []; const droppedEntitlements: string[] = []; const entitlements = db.用户已购课程.flatMap((row) => { const userId = userIdByOpenid.get(asString(row.OpenID)); const courseId = courseIdByCode.get(asString(row.课程id)); if (!userId || !courseId) { droppedEntitlements.push(`${asString(row.OpenID).slice(0, 30)}::${asString(row.课程id).slice(0, 12)}`); return []; } const key = `${userId}::${courseId}`; if (seen.has(key)) { duplicateEntitlements.push(key); return []; } seen.add(key); return [transformEntitlement(row, userId, courseId)]; }); const unmappedHomeCourses: string[] = []; const homeCourses = db.首页课程列表.map((row) => { const courseName = asString(row.课程名称); const title = HOME_COURSE_TITLE_MAP[courseName]; const courseId = title ? courseIdByTitle.get(title) ?? null : null; if (courseId === null) unmappedHomeCourses.push(courseName); return transformHomeCourse(row, courseId, staticBase); }); return { courses, users, lessons, entitlements, homeCourses, freeLessons: db.免费视频课程数据.map(transformFreeLesson), homeFreeCourses: db.首页免费课程列表.map(transformHomeFreeCourse), banners: db.首页横幅图片.map(transformBanner), articles: db.网页列表.map((row) => transformArticle(row, staticBase)), courseTips: db.课程页面Tips.map(transformCourseTip), settings: db.cloudconfig .map(transformSetting) .filter((v): v is NonNullable => v !== null), diagnostics: { skippedLessonOrphans, duplicateEntitlements, unmappedHomeCourses, skippedUsers, droppedEntitlements, }, }; }