| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- /**
- * 🔧 清理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<boolean> {
- 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<boolean> {
- 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);
- */
|