/** * 回填作者名:把误存成邮箱的 authorName 改回真实用户名 * * 成因:任务-详情上传入口曾读取 session.user.username,而登录态从不写入该字段, * 一路兜底存成了邮箱。入口已修复,本脚本清理存量。 * * 只改 authorName(署名展示用),不动 userId(归属)。 * 取值口径与其它上传入口一致:使用 users.username。 * * 默认预演,不写任何数据。加 --apply 才实际执行。 * * 用法: * npx tsx scripts/backfill-email-author-names.ts # 预演 * npx tsx scripts/backfill-email-author-names.ts --apply # 实际执行 */ import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient({ log: [] }) async function main() { const apply = process.argv.includes('--apply') console.log('\n回填被误存为邮箱的作者名') console.log('模式:' + (apply ? '⚠️ 实际执行' : '预演,不写入')) const targets = await prisma.material.findMany({ where: { deletedAt: null, authorName: { contains: '@' } }, select: { id: true, authorName: true, materialType: true, user: { select: { username: true, realName: true, archivedAt: true } }, }, }) console.log('\n 作者名含邮箱的素材 ' + targets.length + ' 条') const fixable = targets.filter((m) => m.user?.username && m.user.username.trim() !== '') const unfixable = targets.filter((m) => !m.user?.username || m.user.username.trim() === '') console.log(' 可按归属账号回填 ' + fixable.length + ' 条') console.log(' 无法回填(账号缺失) ' + unfixable.length + ' 条') const byType = new Map() fixable.forEach((m) => { const k = m.materialType || '(无类型)' byType.set(k, (byType.get(k) || 0) + 1) }) console.log('\n 按素材类型分布:') Array.from(byType.entries()) .sort((a, b) => b[1] - a[1]) .forEach(([t, c]) => console.log(' ' + t.padEnd(20, '.') + c)) const samples = fixable.slice(0, 8) console.log('\n 回填样本(前 8 条):') samples.forEach((m) => console.log( ' ' + (m.authorName ?? '').padEnd(34) + ' → ' + m.user!.username + (m.user!.archivedAt ? '(已离职)' : ''), ), ) if (!apply) { console.log('\n(预演结束,未写入任何数据。加 --apply 实际执行)') await prisma.$disconnect() return } // 按目标用户名分组批量更新,避免逐条往返 const groups = new Map() fixable.forEach((m) => { const name = m.user!.username const list = groups.get(name) ?? [] list.push(m.id) groups.set(name, list) }) let updated = 0 for (const [username, ids] of groups) { const result = await prisma.material.updateMany({ where: { id: { in: ids } }, data: { authorName: username }, }) updated += result.count } console.log('\n 已回填 ' + updated + ' 条') const remaining = await prisma.material.count({ where: { deletedAt: null, authorName: { contains: '@' } }, }) console.log(' 剩余作者名含邮箱的 ' + remaining + ' 条' + (remaining === unfixable.length ? '(均为无法回填的)' : '')) await prisma.$disconnect() } main().catch(async (e) => { console.error('\n执行失败:', e) await prisma.$disconnect() process.exitCode = 1 })