小程序修正

This commit is contained in:
tianmo committed 2026-09-29 20:51:39 +08:00
1 parent 5a92bab835
commit 6ae4c109ce
12 files changed
+232 -62

No files matched your search

+50 -20
View File
@@ -28,23 +28,26 @@ const prisma = new PrismaClient();
const flag = (name: string): boolean => process.argv.includes(`--${name}`);
/** 清空顺序:先子后父。 */
const CLEAR_ORDER = [
'paymentNotifyLog',
'order',
'entitlement',
'lesson',
'homeCourse',
'freeLesson',
'homeFreeCourse',
'banner',
'article',
'courseTip',
'setting',
'product',
'course',
'user',
] as const;
/**
* 清空顺序:先子后父(有外键,顺序不能乱)。
* 同时给出真实表名,用于重置自增。
*/
const CLEAR_ORDER: Array<{ model: string; table: string }> = [
{ model: 'paymentNotifyLog', table: 'payment_notify_log' },
{ model: 'order', table: 'orders' },
{ model: 'entitlement', table: 'entitlements' },
{ model: 'lesson', table: 'lessons' },
{ model: 'homeCourse', table: 'home_courses' },
{ model: 'freeLesson', table: 'free_lessons' },
{ model: 'homeFreeCourse', table: 'home_free_courses' },
{ model: 'banner', table: 'banners' },
{ model: 'article', table: 'articles' },
{ model: 'courseTip', table: 'course_tips' },
{ model: 'setting', table: 'settings' },
{ model: 'product', table: 'products' },
{ model: 'course', table: 'courses' },
{ model: 'user', table: 'users' },
];
async function main(): Promise<void> {
const dryRun = flag('dry-run');
@@ -108,14 +111,41 @@ async function main(): Promise<void> {
// ── 写库 ───────────────────────────────────────────────────────────────
console.log('\n── 写入新库 ────────────────────────────');
type Delegates = Record<string, { deleteMany: () => Promise<unknown> }>;
const delegates = prisma as unknown as Delegates;
// 清空与重置自增必须放在事务外:MySQL 的 ALTER TABLE 会隐式提交事务。
for (const { model } of CLEAR_ORDER) await delegates[model]!.deleteMany();
// 关键:deleteMany 不会重置自增计数器。
// 上一次失败的迁移虽然回滚了数据,但计数器已经前进,
// 导致再次插入时 id 不是从 1 开始,与 buildDataset 里按顺序推导的映射对不上,
// 进而触发外键约束失败。
for (const { table } of CLEAR_ORDER) {
await prisma.$executeRawUnsafe(`ALTER TABLE \`${table}\` AUTO_INCREMENT = 1`);
}
await prisma.$transaction(
async (tx) => {
type Delegates = Record<string, { deleteMany: () => Promise<unknown> }>;
const delegates = tx as unknown as Delegates;
for (const model of CLEAR_ORDER) await delegates[model]!.deleteMany();
await tx.course.createMany({ data: ds.courses });
await tx.user.createMany({ data: ds.users });
// 校验实际自增 id 与推导出的映射一致。
// 若不一致,lessons / entitlements 会引用到错误的课程——这类错误外键查不出来,
// 但会让用户点开 A 课程看到 B 课程的内容,所以必须显式拦住。
const createdCourses = await tx.course.findMany({ select: { id: true, code: true } });
const idByCode = new Map(createdCourses.map((c) => [c.code, c.id]));
ds.courses.forEach((course, index) => {
const actual = idByCode.get(course.code);
if (actual !== index + 1) {
throw new Error(
`课程 id 与预期不符:${course.title} 期望 id=${index + 1},实际 id=${actual ?? '缺失'}。` +
'为避免课程数据错位,迁移已中止。请清空新库后重跑。',
);
}
});
await tx.lesson.createMany({ data: ds.lessons });
await tx.entitlement.createMany({ data: ds.entitlements });
await tx.homeCourse.createMany({ data: ds.homeCourses });