| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- /**
- * GroupChat 计数器回填脚本
- *
- * 用法: cd qiwe-common && npx tsx scripts/backfill-group-counters.ts
- *
- * 回填内容:
- * 1. messageCountTotal: 每个群的历史消息总数
- * 2. messageCountToday: 每个群的今日消息数
- * 3. memberCount: 每个群的活跃成员数
- * 4. lastActivityAt: 每个群的最后消息时间
- *
- * 执行时机: 上线计数器维护后,运行一次。
- * 幂等性: 可安全多次执行,只更新与现有值不同的记录。
- */
- import 'dotenv/config';
- import Parse from '../src/config/parse-client.js';
- const GUID = process.env.QIWE_GUID || '';
- async function backfillMessageCounts(): Promise<number> {
- console.log('\n═══ 1/4 messageCountTotal + messageCountToday 回填 ═══');
- // 获取所有群
- const gq = new Parse.Query('GroupChat');
- gq.exists('roomId');
- gq.limit(500);
- const groups = await gq.find({ useMasterKey: true });
- console.log(`共 ${groups.length} 个群`);
- // 预计算今日起始时间戳
- const today = new Date();
- today.setHours(0, 0, 0, 0);
- let updated = 0;
- for (const group of groups) {
- const roomId = group.get('roomId') as string;
- if (!roomId) continue;
- let changed = false;
- // 回填 messageCountTotal(所有消息)
- try {
- const mq = new Parse.Query('Message');
- mq.equalTo('roomId', roomId);
- mq.equalTo('guid', GUID);
- mq.containedIn('msgType', [0, 2]); // 仅文本 + 混合消息
- mq.exists('content');
- mq.notEqualTo('content', '');
- mq.limit(0); // 只取 count
- const totalCount = await mq.count({ useMasterKey: true });
- if (totalCount !== (group.get('messageCountTotal') ?? 0)) {
- group.set('messageCountTotal', totalCount);
- changed = true;
- }
- // 回填 messageCountToday(今日消息)
- const tmq = new Parse.Query('Message');
- tmq.equalTo('roomId', roomId);
- tmq.equalTo('guid', GUID);
- tmq.containedIn('msgType', [0, 2]);
- tmq.exists('content');
- tmq.notEqualTo('content', '');
- tmq.greaterThanOrEqualTo('timestamp', today);
- tmq.limit(0);
- const todayCount = await tmq.count({ useMasterKey: true });
- if (todayCount !== (group.get('messageCountToday') ?? 0)) {
- group.set('messageCountToday', todayCount);
- changed = true;
- }
- } catch (e: any) {
- console.warn(`roomId=${roomId} 消息计数查询失败: ${e.message}`);
- }
- if (changed) {
- await group.save(null, { useMasterKey: true });
- updated++;
- }
- }
- console.log(`messageCountTotal/messageCountToday 回填完成: ${updated} 个群已更新`);
- return updated;
- }
- async function backfillMemberCount(): Promise<number> {
- console.log('\n═══ 2/4 memberCount 回填 ═══');
- const gq = new Parse.Query('GroupChat');
- gq.exists('roomId');
- gq.limit(500);
- const groups = await gq.find({ useMasterKey: true });
- let updated = 0;
- for (const group of groups) {
- const roomId = group.get('roomId') as string;
- if (!roomId) continue;
- try {
- const mmq = new Parse.Query('GroupMember');
- mmq.equalTo('roomId', roomId);
- mmq.equalTo('guid', GUID);
- mmq.equalTo('status', 'active');
- mmq.limit(0);
- const memberCount = await mmq.count({ useMasterKey: true });
- if (memberCount !== (group.get('memberCount') ?? 0)) {
- group.set('memberCount', memberCount);
- await group.save(null, { useMasterKey: true });
- updated++;
- }
- } catch (e: any) {
- console.warn(`roomId=${roomId} memberCount 查询失败: ${e.message}`);
- }
- }
- console.log(`memberCount 回填完成: ${updated} 个群已更新`);
- return updated;
- }
- async function backfillLastActivityAt(): Promise<number> {
- console.log('\n═══ 3/4 lastActivityAt 回填 ═══');
- const gq = new Parse.Query('GroupChat');
- gq.exists('roomId');
- gq.limit(500);
- const groups = await gq.find({ useMasterKey: true });
- let updated = 0;
- for (const group of groups) {
- const roomId = group.get('roomId') as string;
- if (!roomId) continue;
- try {
- const mq = new Parse.Query('Message');
- mq.equalTo('roomId', roomId);
- mq.equalTo('guid', GUID);
- mq.containedIn('msgType', [0, 2]);
- mq.exists('content');
- mq.notEqualTo('content', '');
- mq.descending('timestamp');
- mq.limit(1);
- const latestMsg = await mq.first({ useMasterKey: true });
- if (latestMsg) {
- const ts = latestMsg.get('timestamp') as Date;
- group.set('lastActivityAt', ts);
- await group.save(null, { useMasterKey: true });
- updated++;
- }
- } catch (e: any) {
- console.warn(`roomId=${roomId} lastActivityAt 查询失败: ${e.message}`);
- }
- }
- console.log(`lastActivityAt 回填完成: ${updated} 个群已更新`);
- return updated;
- }
- async function verifyCounters(): Promise<void> {
- console.log('\n═══ 4/4 验证回填结果 ═══');
- const gq = new Parse.Query('GroupChat');
- gq.exists('roomId');
- gq.limit(500);
- const groups = await gq.find({ useMasterKey: true });
- let withTotal = 0;
- let withToday = 0;
- let withMember = 0;
- let withLastActivity = 0;
- let totalMessages = 0;
- let totalMembers = 0;
- for (const group of groups) {
- const tc = group.get('messageCountTotal') ?? 0;
- const td = group.get('messageCountToday') ?? 0;
- const mc = group.get('memberCount') ?? 0;
- const la = group.get('lastActivityAt');
- if (tc > 0) withTotal++;
- if (td > 0) withToday++;
- if (mc > 0) withMember++;
- if (la) withLastActivity++;
- totalMessages += tc;
- totalMembers += mc;
- }
- console.log(`总群数: ${groups.length}`);
- console.log(`有 messageCountTotal (>0): ${withTotal}/${groups.length}`);
- console.log(`有 messageCountToday (>0): ${withToday}/${groups.length}`);
- console.log(`有 memberCount (>0): ${withMember}/${groups.length}`);
- console.log(`有 lastActivityAt: ${withLastActivity}/${groups.length}`);
- console.log(`累计消息: ${totalMessages}`);
- console.log(`累计成员: ${totalMembers}`);
- }
- async function main(): Promise<void> {
- if (!GUID) {
- console.error('❌ QIWE_GUID 未配置,请在 .env 中设置');
- process.exit(1);
- }
- console.log('🔧 GroupChat 计数器回填');
- console.log(`GUID: ${GUID}`);
- console.log(`开始时间: ${new Date().toISOString()}`);
- const t0 = Date.now();
- await backfillMessageCounts();
- await backfillMemberCount();
- await backfillLastActivityAt();
- await verifyCounters();
- const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
- console.log(`\n✅ 回填完成,耗时 ${elapsed}s`);
- }
- main().catch((err) => {
- console.error('❌ 回填失败:', err);
- process.exit(1);
- });
|