| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- /**
- * 手动脚本:从 Message 表的 senderName 回填 GroupMember 缺失的昵称
- *
- * 用法: cd backend && npx tsx scripts/backfill-member-nicknames.ts
- *
- * 逻辑:
- * 1. 查找 nickname 为空或 '' 的 GroupMember
- * 2. 跨所有房间,从 Message 表查找同一 userId 的 senderName
- * 3. 批量写入 GroupMember.nickname
- */
- import 'dotenv/config';
- import Parse from '../src/db/parse-client.js';
- const GUID = process.env.QIWE_GUID || '';
- const CONTAINED_IN_BATCH = 500; // containedIn 单次最多 500 个 userId
- async function main(): Promise<void> {
- console.log('=== GroupMember 昵称回填 ===');
- console.log(`Server: ${Parse.serverURL}`);
- console.log(`GUID: ${GUID}\n`);
- // ── 1. 查找缺少昵称的成员 ──
- console.log('[1/3] 查找缺失昵称的成员...');
- const memberQuery = new Parse.Query('GroupMember');
- memberQuery.equalTo('guid', GUID);
- memberQuery.equalTo('status', 'active');
- memberQuery.limit(10000);
- const allMembers = await memberQuery.find({ useMasterKey: true });
- const noNick: Parse.Object[] = [];
- let hasNick = 0;
- for (const m of allMembers) {
- const nick = m.get('nickname');
- if (!nick || (typeof nick === 'string' && nick.trim() === '')) {
- noNick.push(m);
- } else {
- hasNick++;
- }
- }
- console.log(` 总数: ${allMembers.length} | 有昵称: ${hasNick} | 缺昵称: ${noNick.length}`);
- if (noNick.length === 0) {
- console.log('\n✅ 所有成员已有昵称,无需回填。');
- return;
- }
- // ── 2. 跨房间从 Message 查找 senderName ──
- // 收集所有无昵称成员的唯一 userId(去重)
- const noNickUserIds = [...new Set(noNick.map((m) => String(m.get('userId') || '')).filter(Boolean))];
- console.log(` 去重后唯一 userId: ${noNickUserIds.length} 个`);
- // 分批用 containedIn 查询消息(避免单次参数过多)
- const userIdToName = new Map<string, string>();
- const batches: string[][] = [];
- for (let i = 0; i < noNickUserIds.length; i += CONTAINED_IN_BATCH) {
- batches.push(noNickUserIds.slice(i, i + CONTAINED_IN_BATCH));
- }
- console.log(` 分 ${batches.length} 批查询消息...`);
- for (let bi = 0; bi < batches.length; bi++) {
- const batch = batches[bi];
- try {
- const msgQuery = new Parse.Query('Message');
- msgQuery.equalTo('guid', GUID);
- msgQuery.equalTo('isGroupChat', 1);
- msgQuery.containedIn('senderId', batch);
- msgQuery.exists('senderName');
- msgQuery.notEqualTo('senderName', '');
- msgQuery.limit(5000);
- const messages = await msgQuery.find({ useMasterKey: true });
- for (const msg of messages) {
- const sid = msg.get('senderId') as string;
- const sname = msg.get('senderName') as string;
- if (sid && sname && !userIdToName.has(sid)) {
- userIdToName.set(sid, sname);
- }
- }
- console.log(` 批次 ${bi + 1}/${batches.length}: ${messages.length} 条消息, 已累积 ${userIdToName.size} 个 senderName`);
- } catch (err: any) {
- console.warn(` ⚠ 批次 ${bi + 1} 查询失败: ${err.message}`);
- }
- }
- // ── 3. 回填 ──
- console.log(`\n[3/3] 回填 GroupMember.nickname...`);
- let filled = 0;
- let skipped = 0;
- const toSave: Parse.Object[] = [];
- for (const m of noNick) {
- const uid = String(m.get('userId') || '');
- const senderName = userIdToName.get(uid);
- if (senderName) {
- m.set('nickname', senderName);
- toSave.push(m);
- filled++;
- } else {
- skipped++;
- }
- }
- // 分批写入
- for (let i = 0; i < toSave.length; i += 100) {
- await Parse.Object.saveAll(toSave.slice(i, i + 100), { useMasterKey: true });
- }
- console.log(`\n✅ 完成 — 回填: ${filled} | 找不到消息: ${skipped} | 总计无昵称: ${noNick.length}`);
- }
- main().catch((err) => {
- console.error('回填失败:', err.message);
- process.exit(1);
- });
|