cleanup-base64-in-projectfile.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /**
  2. * 🔧 清理ProjectFile表中的Base64数据
  3. *
  4. * 问题:之前的代码将超大Base64字符串保存到数据库,导致查询500错误
  5. * 解决:删除或清空包含Base64的字段
  6. *
  7. * 使用方法:
  8. * 1. 在浏览器控制台运行此脚本
  9. * 2. 或在Parse Cloud Code中运行
  10. */
  11. import * as Parse from 'parse';
  12. /**
  13. * 检测字符串是否为Base64格式
  14. */
  15. function isBase64String(str: string): boolean {
  16. if (!str || typeof str !== 'string') return false;
  17. // 检测data URL格式:data:image/...;base64,...
  18. if (str.startsWith('data:image/') && str.includes('base64')) {
  19. return true;
  20. }
  21. // 检测纯Base64字符串(长度>10KB可能是图片)
  22. if (str.length > 10000 && /^[A-Za-z0-9+/=]+$/.test(str)) {
  23. return true;
  24. }
  25. return false;
  26. }
  27. /**
  28. * 清理单个ProjectFile记录
  29. */
  30. async function cleanupProjectFile(projectFile: Parse.Object): Promise<boolean> {
  31. let needsSave = false;
  32. const fileId = projectFile.id;
  33. const fileName = projectFile.get('fileName') || '未知文件';
  34. console.log(`\n🔍 检查: ${fileName} (${fileId})`);
  35. // 1. 检查 attach 字段(可能是Pointer或Object)
  36. const attach = projectFile.get('attach');
  37. if (attach && typeof attach === 'object') {
  38. // 如果是Object(包含内嵌数据),检查是否有Base64
  39. if (!attach.className && !attach.__type) {
  40. console.log(' 📦 attach字段包含内嵌数据');
  41. // 检查attach中的各个字段
  42. for (const key of Object.keys(attach)) {
  43. const value = attach[key];
  44. if (typeof value === 'string' && isBase64String(value)) {
  45. console.log(` ❌ 发现Base64数据: attach.${key}, 长度: ${value.length} 字符`);
  46. delete attach[key];
  47. needsSave = true;
  48. }
  49. }
  50. if (needsSave) {
  51. projectFile.set('attach', attach);
  52. }
  53. }
  54. }
  55. // 2. 检查 data 字段
  56. const data = projectFile.get('data');
  57. if (data && typeof data === 'object') {
  58. console.log(' 📦 data字段存在');
  59. // 检查preview字段
  60. if (data.preview && typeof data.preview === 'string' && isBase64String(data.preview)) {
  61. console.log(` ❌ 发现Base64数据: data.preview, 长度: ${data.preview.length} 字符`);
  62. delete data.preview;
  63. needsSave = true;
  64. }
  65. // 检查其他可能的Base64字段
  66. for (const key of Object.keys(data)) {
  67. const value = data[key];
  68. if (typeof value === 'string' && isBase64String(value)) {
  69. console.log(` ❌ 发现Base64数据: data.${key}, 长度: ${value.length} 字符`);
  70. delete data[key];
  71. needsSave = true;
  72. }
  73. }
  74. if (needsSave) {
  75. projectFile.set('data', data);
  76. }
  77. }
  78. // 3. 检查 fileUrl 字段(不应该是Base64)
  79. const fileUrl = projectFile.get('fileUrl');
  80. if (fileUrl && typeof fileUrl === 'string' && isBase64String(fileUrl)) {
  81. console.log(` ❌ fileUrl字段包含Base64, 长度: ${fileUrl.length} 字符`);
  82. // 不要删除fileUrl,而是标记为错误
  83. projectFile.set('_hasInvalidUrl', true);
  84. needsSave = true;
  85. }
  86. if (needsSave) {
  87. console.log(` ✅ 清理完成,准备保存...`);
  88. try {
  89. await projectFile.save();
  90. console.log(` 💾 保存成功: ${fileName}`);
  91. return true;
  92. } catch (error: any) {
  93. console.error(` ❌ 保存失败: ${error.message}`);
  94. return false;
  95. }
  96. } else {
  97. console.log(` ✓ 无需清理`);
  98. return false;
  99. }
  100. }
  101. /**
  102. * 批量清理ProjectFile表
  103. */
  104. export async function cleanupAllProjectFiles(
  105. projectId?: string,
  106. stage?: string,
  107. dryRun: boolean = true
  108. ): Promise<{
  109. total: number;
  110. cleaned: number;
  111. errors: number;
  112. skipped: number;
  113. }> {
  114. console.log('🚀 开始清理ProjectFile表中的Base64数据...');
  115. console.log(`模式: ${dryRun ? '🔍 检查模式(不会保存)' : '🔧 清理模式(会保存修改)'}`);
  116. if (projectId) {
  117. console.log(`📁 限制项目: ${projectId}`);
  118. }
  119. if (stage) {
  120. console.log(`📋 限制阶段: ${stage}`);
  121. }
  122. const query = new Parse.Query('ProjectFile');
  123. // 筛选条件
  124. if (projectId) {
  125. const project = new Parse.Object('Project');
  126. project.id = projectId;
  127. query.equalTo('project', project.toPointer());
  128. }
  129. if (stage) {
  130. query.equalTo('stage', stage);
  131. }
  132. // 按创建时间倒序
  133. query.descending('createdAt');
  134. query.limit(1000); // 限制单次处理数量
  135. const stats = {
  136. total: 0,
  137. cleaned: 0,
  138. errors: 0,
  139. skipped: 0
  140. };
  141. try {
  142. const projectFiles = await query.find();
  143. stats.total = projectFiles.length;
  144. console.log(`\n📊 找到 ${stats.total} 条记录\n`);
  145. for (const projectFile of projectFiles) {
  146. try {
  147. if (dryRun) {
  148. // 检查模式:只检测不保存
  149. const needsCleaning = await checkProjectFileNeedsCleaning(projectFile);
  150. if (needsCleaning) {
  151. stats.cleaned++;
  152. } else {
  153. stats.skipped++;
  154. }
  155. } else {
  156. // 清理模式:检测并保存
  157. const wasCleaned = await cleanupProjectFile(projectFile);
  158. if (wasCleaned) {
  159. stats.cleaned++;
  160. } else {
  161. stats.skipped++;
  162. }
  163. }
  164. } catch (error: any) {
  165. console.error(`❌ 处理失败:`, error.message);
  166. stats.errors++;
  167. }
  168. }
  169. console.log('\n✅ 清理完成!');
  170. console.log(`📊 统计:`);
  171. console.log(` - 总记录数: ${stats.total}`);
  172. console.log(` - 需要清理: ${stats.cleaned}`);
  173. console.log(` - 无需处理: ${stats.skipped}`);
  174. console.log(` - 处理错误: ${stats.errors}`);
  175. if (dryRun && stats.cleaned > 0) {
  176. console.log(`\n💡 提示: 这是检查模式,没有实际修改数据`);
  177. console.log(` 要真正清理,请设置 dryRun = false`);
  178. }
  179. } catch (error: any) {
  180. console.error('❌ 查询失败:', error.message);
  181. stats.errors++;
  182. }
  183. return stats;
  184. }
  185. /**
  186. * 检查单个ProjectFile是否需要清理(不保存)
  187. */
  188. async function checkProjectFileNeedsCleaning(projectFile: Parse.Object): Promise<boolean> {
  189. const fileId = projectFile.id;
  190. const fileName = projectFile.get('fileName') || '未知文件';
  191. let needsCleaning = false;
  192. console.log(`🔍 检查: ${fileName} (${fileId})`);
  193. // 检查 attach
  194. const attach = projectFile.get('attach');
  195. if (attach && typeof attach === 'object' && !attach.className && !attach.__type) {
  196. for (const key of Object.keys(attach)) {
  197. const value = attach[key];
  198. if (typeof value === 'string' && isBase64String(value)) {
  199. console.log(` ⚠️ 发现Base64: attach.${key}, ${(value.length / 1024).toFixed(2)} KB`);
  200. needsCleaning = true;
  201. }
  202. }
  203. }
  204. // 检查 data
  205. const data = projectFile.get('data');
  206. if (data && typeof data === 'object') {
  207. for (const key of Object.keys(data)) {
  208. const value = data[key];
  209. if (typeof value === 'string' && isBase64String(value)) {
  210. console.log(` ⚠️ 发现Base64: data.${key}, ${(value.length / 1024).toFixed(2)} KB`);
  211. needsCleaning = true;
  212. }
  213. }
  214. }
  215. // 检查 fileUrl
  216. const fileUrl = projectFile.get('fileUrl');
  217. if (fileUrl && typeof fileUrl === 'string' && isBase64String(fileUrl)) {
  218. console.log(` ⚠️ fileUrl包含Base64, ${(fileUrl.length / 1024).toFixed(2)} KB`);
  219. needsCleaning = true;
  220. }
  221. if (!needsCleaning) {
  222. console.log(` ✓ 正常`);
  223. }
  224. return needsCleaning;
  225. }
  226. // ============ 浏览器控制台使用示例 ============
  227. /*
  228. // 1. 检查模式(不会修改数据)
  229. cleanupAllProjectFiles(undefined, undefined, true);
  230. // 2. 清理特定项目
  231. cleanupAllProjectFiles('项目ID', undefined, false);
  232. // 3. 清理特定阶段
  233. cleanupAllProjectFiles(undefined, 'delivery_execution', false);
  234. // 4. 清理所有(谨慎!)
  235. cleanupAllProjectFiles(undefined, undefined, false);
  236. */