discover-db.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * 探查数据库表结构 + 数据量 + 示例数据
  3. *
  4. * 用法: cd backend && npx tsx scripts/discover-db.ts
  5. */
  6. import 'dotenv/config';
  7. import Parse from '../src/db/parse-client.js';
  8. async function countRows(className: string): Promise<number> {
  9. try {
  10. const n = await new Parse.Query(className).count({ useMasterKey: true });
  11. return n;
  12. } catch {
  13. return -1;
  14. }
  15. }
  16. async function showSample(className: string, fields: string[], limit = 2): Promise<void> {
  17. try {
  18. const q = new Parse.Query(className);
  19. q.limit(limit);
  20. q.descending('updatedAt');
  21. const rows = await q.find({ useMasterKey: true }) as any[];
  22. if (rows.length === 0) {
  23. console.log(' (无数据)');
  24. return;
  25. }
  26. for (const row of rows) {
  27. const vals: string[] = [];
  28. for (const f of fields) {
  29. const v = row.get(f);
  30. if (v === undefined || v === null) {
  31. vals.push(`${f}=null`);
  32. } else if (typeof v === 'object' && v.className) {
  33. vals.push(`${f}=Pointer(${v.className}:${v.id})`);
  34. } else if (typeof v === 'object') {
  35. vals.push(`${f}=${JSON.stringify(v).substring(0, 80)}`);
  36. } else {
  37. const s = String(v);
  38. vals.push(`${f}=${s.length > 60 ? s.substring(0, 60) + '...' : s}`);
  39. }
  40. }
  41. console.log(` ${vals.join(' | ')}`);
  42. }
  43. } catch (err: any) {
  44. console.log(` 查询失败: ${err.message}`);
  45. }
  46. }
  47. async function main(): Promise<void> {
  48. console.log('=== 数据库探查 ===\n');
  49. console.log(`Server: ${Parse.serverURL}`);
  50. console.log(`App ID: ${Parse.applicationId}\n`);
  51. // 测试连接
  52. try {
  53. const n = await new Parse.Query('_User').count({ useMasterKey: true });
  54. console.log(`✅ Parse 连接正常 (_User: ${n} 条)\n`);
  55. } catch (err: any) {
  56. console.error(`❌ Parse 连接失败: ${err.message}`);
  57. process.exit(1);
  58. }
  59. // 目标表
  60. const targets = ['GroupChat', 'GroupMember', 'Message', 'Community', 'Store', 'ResponsibilityAssignment'];
  61. const schemas = await Parse.Schema.all({ useMasterKey: true });
  62. const schemaMap = new Map(schemas.map(s => [s.className, s]));
  63. for (const className of targets) {
  64. const schema = schemaMap.get(className);
  65. const count = await countRows(className);
  66. console.log(`\n${'═'.repeat(80)}`);
  67. console.log(`📦 ${className} — ${count} 行`);
  68. console.log(`${'─'.repeat(80)}`);
  69. if (schema) {
  70. const fields = schema.fields as Record<string, { type: string; targetClass?: string }>;
  71. const entries = Object.entries(fields)
  72. .filter(([name]) => !name.startsWith('_'))
  73. .sort(([a], [b]) => a.localeCompare(b));
  74. const fieldNames: string[] = [];
  75. for (const [name, def] of entries) {
  76. const typeStr = def.type === 'Pointer' ? `Pointer→${def.targetClass || '?'}` : def.type;
  77. console.log(` ${name}: ${typeStr}`);
  78. fieldNames.push(name);
  79. }
  80. // 示例数据
  81. console.log(`\n 示例数据:`);
  82. const keyFields = fieldNames.filter(f =>
  83. ['roomId', 'roomName', 'name', 'status', 'nickname', 'content', 'senderName',
  84. 'msgType', 'lifecyclePhase', 'ownerName', 'scopeType', 'userName',
  85. 'memberCount', 'messageCountToday', 'messageCountTotal', 'groupCount',
  86. 'community', 'store', 'deliveryDate'].includes(f)
  87. );
  88. await showSample(className, keyFields.length > 0 ? keyFields : fieldNames.slice(0, 8));
  89. } else {
  90. console.log(' (Schema 未找到,可能表不存在)');
  91. }
  92. }
  93. // 检查 GroupChat 是否还有 memberCount/messageCountToday/messageCountTotal 字段
  94. console.log(`\n${'═'.repeat(80)}`);
  95. console.log('🔍 GroupChat 旧预计算字段检查');
  96. console.log(`${'─'.repeat(80)}`);
  97. const gcSchema = schemaMap.get('GroupChat');
  98. if (gcSchema) {
  99. const fields = gcSchema.fields as Record<string, { type: string }>;
  100. for (const name of ['memberCount', 'messageCountToday', 'messageCountTotal', 'ownerName', 'lastActivityAt', 'avatarUrl']) {
  101. console.log(` ${name}: ${fields[name] ? '存在 (' + fields[name].type + ')' : '❌ 已删除'}`);
  102. }
  103. }
  104. // 今日消息量
  105. console.log(`\n${'═'.repeat(80)}`);
  106. console.log('🔍 今日消息量');
  107. console.log(`${'─'.repeat(80)}`);
  108. const todayStart = new Date();
  109. todayStart.setHours(0, 0, 0, 0);
  110. const todayMsgQ = new Parse.Query('Message');
  111. todayMsgQ.greaterThanOrEqualTo('timestamp', todayStart);
  112. todayMsgQ.containedIn('msgType', [0, 2]);
  113. todayMsgQ.notEqualTo('content', '');
  114. const todayMsgCount = await todayMsgQ.count({ useMasterKey: true });
  115. console.log(` 今日文本/富文本消息: ${todayMsgCount} 条`);
  116. console.log('\n完成!\n');
  117. }
  118. main().catch((err) => {
  119. console.error('失败:', err.message);
  120. process.exit(1);
  121. });