/** * 探查数据库表结构 + 数据量 + 示例数据 * * 用法: cd backend && npx tsx scripts/discover-db.ts */ import 'dotenv/config'; import Parse from '../src/db/parse-client.js'; async function countRows(className: string): Promise { try { const n = await new Parse.Query(className).count({ useMasterKey: true }); return n; } catch { return -1; } } async function showSample(className: string, fields: string[], limit = 2): Promise { try { const q = new Parse.Query(className); q.limit(limit); q.descending('updatedAt'); const rows = await q.find({ useMasterKey: true }) as any[]; if (rows.length === 0) { console.log(' (无数据)'); return; } for (const row of rows) { const vals: string[] = []; for (const f of fields) { const v = row.get(f); if (v === undefined || v === null) { vals.push(`${f}=null`); } else if (typeof v === 'object' && v.className) { vals.push(`${f}=Pointer(${v.className}:${v.id})`); } else if (typeof v === 'object') { vals.push(`${f}=${JSON.stringify(v).substring(0, 80)}`); } else { const s = String(v); vals.push(`${f}=${s.length > 60 ? s.substring(0, 60) + '...' : s}`); } } console.log(` ${vals.join(' | ')}`); } } catch (err: any) { console.log(` 查询失败: ${err.message}`); } } async function main(): Promise { console.log('=== 数据库探查 ===\n'); console.log(`Server: ${Parse.serverURL}`); console.log(`App ID: ${Parse.applicationId}\n`); // 测试连接 try { const n = await new Parse.Query('_User').count({ useMasterKey: true }); console.log(`✅ Parse 连接正常 (_User: ${n} 条)\n`); } catch (err: any) { console.error(`❌ Parse 连接失败: ${err.message}`); process.exit(1); } // 目标表 const targets = ['GroupChat', 'GroupMember', 'Message', 'Community', 'Store', 'ResponsibilityAssignment']; const schemas = await Parse.Schema.all({ useMasterKey: true }); const schemaMap = new Map(schemas.map(s => [s.className, s])); for (const className of targets) { const schema = schemaMap.get(className); const count = await countRows(className); console.log(`\n${'═'.repeat(80)}`); console.log(`📦 ${className} — ${count} 行`); console.log(`${'─'.repeat(80)}`); if (schema) { const fields = schema.fields as Record; const entries = Object.entries(fields) .filter(([name]) => !name.startsWith('_')) .sort(([a], [b]) => a.localeCompare(b)); const fieldNames: string[] = []; for (const [name, def] of entries) { const typeStr = def.type === 'Pointer' ? `Pointer→${def.targetClass || '?'}` : def.type; console.log(` ${name}: ${typeStr}`); fieldNames.push(name); } // 示例数据 console.log(`\n 示例数据:`); const keyFields = fieldNames.filter(f => ['roomId', 'roomName', 'name', 'status', 'nickname', 'content', 'senderName', 'msgType', 'lifecyclePhase', 'ownerName', 'scopeType', 'userName', 'memberCount', 'messageCountToday', 'messageCountTotal', 'groupCount', 'community', 'store', 'deliveryDate'].includes(f) ); await showSample(className, keyFields.length > 0 ? keyFields : fieldNames.slice(0, 8)); } else { console.log(' (Schema 未找到,可能表不存在)'); } } // 检查 GroupChat 是否还有 memberCount/messageCountToday/messageCountTotal 字段 console.log(`\n${'═'.repeat(80)}`); console.log('🔍 GroupChat 旧预计算字段检查'); console.log(`${'─'.repeat(80)}`); const gcSchema = schemaMap.get('GroupChat'); if (gcSchema) { const fields = gcSchema.fields as Record; for (const name of ['memberCount', 'messageCountToday', 'messageCountTotal', 'ownerName', 'lastActivityAt', 'avatarUrl']) { console.log(` ${name}: ${fields[name] ? '存在 (' + fields[name].type + ')' : '❌ 已删除'}`); } } // 今日消息量 console.log(`\n${'═'.repeat(80)}`); console.log('🔍 今日消息量'); console.log(`${'─'.repeat(80)}`); const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); const todayMsgQ = new Parse.Query('Message'); todayMsgQ.greaterThanOrEqualTo('timestamp', todayStart); todayMsgQ.containedIn('msgType', [0, 2]); todayMsgQ.notEqualTo('content', ''); const todayMsgCount = await todayMsgQ.count({ useMasterKey: true }); console.log(` 今日文本/富文本消息: ${todayMsgCount} 条`); console.log('\n完成!\n'); } main().catch((err) => { console.error('失败:', err.message); process.exit(1); });