/** * 🔧 清理ProjectFile表中的Base64数据 * * 问题:之前的代码将超大Base64字符串保存到数据库,导致查询500错误 * 解决:删除或清空包含Base64的字段 * * 使用方法: * 1. 在浏览器控制台运行此脚本 * 2. 或在Parse Cloud Code中运行 */ import * as Parse from 'parse'; /** * 检测字符串是否为Base64格式 */ function isBase64String(str: string): boolean { if (!str || typeof str !== 'string') return false; // 检测data URL格式:data:image/...;base64,... if (str.startsWith('data:image/') && str.includes('base64')) { return true; } // 检测纯Base64字符串(长度>10KB可能是图片) if (str.length > 10000 && /^[A-Za-z0-9+/=]+$/.test(str)) { return true; } return false; } /** * 清理单个ProjectFile记录 */ async function cleanupProjectFile(projectFile: Parse.Object): Promise { let needsSave = false; const fileId = projectFile.id; const fileName = projectFile.get('fileName') || '未知文件'; console.log(`\n🔍 检查: ${fileName} (${fileId})`); // 1. 检查 attach 字段(可能是Pointer或Object) const attach = projectFile.get('attach'); if (attach && typeof attach === 'object') { // 如果是Object(包含内嵌数据),检查是否有Base64 if (!attach.className && !attach.__type) { console.log(' 📦 attach字段包含内嵌数据'); // 检查attach中的各个字段 for (const key of Object.keys(attach)) { const value = attach[key]; if (typeof value === 'string' && isBase64String(value)) { console.log(` ❌ 发现Base64数据: attach.${key}, 长度: ${value.length} 字符`); delete attach[key]; needsSave = true; } } if (needsSave) { projectFile.set('attach', attach); } } } // 2. 检查 data 字段 const data = projectFile.get('data'); if (data && typeof data === 'object') { console.log(' 📦 data字段存在'); // 检查preview字段 if (data.preview && typeof data.preview === 'string' && isBase64String(data.preview)) { console.log(` ❌ 发现Base64数据: data.preview, 长度: ${data.preview.length} 字符`); delete data.preview; needsSave = true; } // 检查其他可能的Base64字段 for (const key of Object.keys(data)) { const value = data[key]; if (typeof value === 'string' && isBase64String(value)) { console.log(` ❌ 发现Base64数据: data.${key}, 长度: ${value.length} 字符`); delete data[key]; needsSave = true; } } if (needsSave) { projectFile.set('data', data); } } // 3. 检查 fileUrl 字段(不应该是Base64) const fileUrl = projectFile.get('fileUrl'); if (fileUrl && typeof fileUrl === 'string' && isBase64String(fileUrl)) { console.log(` ❌ fileUrl字段包含Base64, 长度: ${fileUrl.length} 字符`); // 不要删除fileUrl,而是标记为错误 projectFile.set('_hasInvalidUrl', true); needsSave = true; } if (needsSave) { console.log(` ✅ 清理完成,准备保存...`); try { await projectFile.save(); console.log(` 💾 保存成功: ${fileName}`); return true; } catch (error: any) { console.error(` ❌ 保存失败: ${error.message}`); return false; } } else { console.log(` ✓ 无需清理`); return false; } } /** * 批量清理ProjectFile表 */ export async function cleanupAllProjectFiles( projectId?: string, stage?: string, dryRun: boolean = true ): Promise<{ total: number; cleaned: number; errors: number; skipped: number; }> { console.log('🚀 开始清理ProjectFile表中的Base64数据...'); console.log(`模式: ${dryRun ? '🔍 检查模式(不会保存)' : '🔧 清理模式(会保存修改)'}`); if (projectId) { console.log(`📁 限制项目: ${projectId}`); } if (stage) { console.log(`📋 限制阶段: ${stage}`); } const query = new Parse.Query('ProjectFile'); // 筛选条件 if (projectId) { const project = new Parse.Object('Project'); project.id = projectId; query.equalTo('project', project.toPointer()); } if (stage) { query.equalTo('stage', stage); } // 按创建时间倒序 query.descending('createdAt'); query.limit(1000); // 限制单次处理数量 const stats = { total: 0, cleaned: 0, errors: 0, skipped: 0 }; try { const projectFiles = await query.find(); stats.total = projectFiles.length; console.log(`\n📊 找到 ${stats.total} 条记录\n`); for (const projectFile of projectFiles) { try { if (dryRun) { // 检查模式:只检测不保存 const needsCleaning = await checkProjectFileNeedsCleaning(projectFile); if (needsCleaning) { stats.cleaned++; } else { stats.skipped++; } } else { // 清理模式:检测并保存 const wasCleaned = await cleanupProjectFile(projectFile); if (wasCleaned) { stats.cleaned++; } else { stats.skipped++; } } } catch (error: any) { console.error(`❌ 处理失败:`, error.message); stats.errors++; } } console.log('\n✅ 清理完成!'); console.log(`📊 统计:`); console.log(` - 总记录数: ${stats.total}`); console.log(` - 需要清理: ${stats.cleaned}`); console.log(` - 无需处理: ${stats.skipped}`); console.log(` - 处理错误: ${stats.errors}`); if (dryRun && stats.cleaned > 0) { console.log(`\n💡 提示: 这是检查模式,没有实际修改数据`); console.log(` 要真正清理,请设置 dryRun = false`); } } catch (error: any) { console.error('❌ 查询失败:', error.message); stats.errors++; } return stats; } /** * 检查单个ProjectFile是否需要清理(不保存) */ async function checkProjectFileNeedsCleaning(projectFile: Parse.Object): Promise { const fileId = projectFile.id; const fileName = projectFile.get('fileName') || '未知文件'; let needsCleaning = false; console.log(`🔍 检查: ${fileName} (${fileId})`); // 检查 attach const attach = projectFile.get('attach'); if (attach && typeof attach === 'object' && !attach.className && !attach.__type) { for (const key of Object.keys(attach)) { const value = attach[key]; if (typeof value === 'string' && isBase64String(value)) { console.log(` ⚠️ 发现Base64: attach.${key}, ${(value.length / 1024).toFixed(2)} KB`); needsCleaning = true; } } } // 检查 data const data = projectFile.get('data'); if (data && typeof data === 'object') { for (const key of Object.keys(data)) { const value = data[key]; if (typeof value === 'string' && isBase64String(value)) { console.log(` ⚠️ 发现Base64: data.${key}, ${(value.length / 1024).toFixed(2)} KB`); needsCleaning = true; } } } // 检查 fileUrl const fileUrl = projectFile.get('fileUrl'); if (fileUrl && typeof fileUrl === 'string' && isBase64String(fileUrl)) { console.log(` ⚠️ fileUrl包含Base64, ${(fileUrl.length / 1024).toFixed(2)} KB`); needsCleaning = true; } if (!needsCleaning) { console.log(` ✓ 正常`); } return needsCleaning; } // ============ 浏览器控制台使用示例 ============ /* // 1. 检查模式(不会修改数据) cleanupAllProjectFiles(undefined, undefined, true); // 2. 清理特定项目 cleanupAllProjectFiles('项目ID', undefined, false); // 3. 清理特定阶段 cleanupAllProjectFiles(undefined, 'delivery_execution', false); // 4. 清理所有(谨慎!) cleanupAllProjectFiles(undefined, undefined, false); */