修复内存溢出问题

This commit is contained in:
2025-09-19 01:25:30 +08:00
parent 803cc100be
commit 9674740c0d
13 changed files with 1085 additions and 337 deletions

View File

@@ -127,8 +127,14 @@ interface AppState {
// Blob URL清理操作
revokeBlobUrls: (urls: string[]) => void;
cleanupAllBlobUrls: () => void;
// 定期清理Blob URL
scheduleBlobCleanup: () => void;
}
// 限制历史记录数量
const MAX_HISTORY_ITEMS = 50;
export const useAppStore = create<AppState>()(
devtools(
persist(
@@ -250,7 +256,19 @@ export const useAppStore = create<AppState>()(
checksum: asset.checksum,
blobUrl
};
} else if (asset.url.startsWith('blob:')) {
// 如果已经是Blob URL直接使用
return {
id: asset.id,
type: asset.type,
mime: asset.mime,
width: asset.width,
height: asset.height,
checksum: asset.checksum,
blobUrl: asset.url
};
}
// 对于其他URL类型创建一个新的Blob URL
return {
id: asset.id,
type: asset.type,
@@ -285,7 +303,11 @@ export const useAppStore = create<AppState>()(
});
return blobUrl;
} else if (asset.url.startsWith('blob:')) {
// 如果已经是Blob URL直接使用
return asset.url;
}
// 对于其他URL类型直接使用
return asset.url;
});
@@ -316,11 +338,11 @@ export const useAppStore = create<AppState>()(
updatedAt: Date.now()
};
// 清理旧记录以保持在限制内现在限制为1000条
if (updatedProject.generations.length > 1000) {
// 清理旧记录以保持在限制内
if (updatedProject.generations.length > MAX_HISTORY_ITEMS) {
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
const generationsToRemove = updatedProject.generations.slice(0, updatedProject.generations.length - 1000);
const generationsToRemove = updatedProject.generations.slice(0, updatedProject.generations.length - MAX_HISTORY_ITEMS);
generationsToRemove.forEach(gen => {
gen.sourceAssets.forEach(asset => {
if (asset.blobUrl.startsWith('blob:')) {
@@ -348,9 +370,9 @@ export const useAppStore = create<AppState>()(
}
// 清理数组
updatedProject.generations.splice(0, updatedProject.generations.length - 1000);
updatedProject.generations.splice(0, updatedProject.generations.length - MAX_HISTORY_ITEMS);
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(1000).catch(err => {
indexedDBService.cleanupOldRecords(MAX_HISTORY_ITEMS).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
}
@@ -415,7 +437,11 @@ export const useAppStore = create<AppState>()(
});
return blobUrl;
} else if (asset.url.startsWith('blob:')) {
// 如果已经是Blob URL直接使用
return asset.url;
}
// 对于其他URL类型直接使用
return asset.url;
});
@@ -441,11 +467,11 @@ export const useAppStore = create<AppState>()(
updatedAt: Date.now()
};
// 清理旧记录以保持在限制内现在限制为1000条
if (updatedProject.edits.length > 1000) {
// 清理旧记录以保持在限制内
if (updatedProject.edits.length > MAX_HISTORY_ITEMS) {
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
const editsToRemove = updatedProject.edits.slice(0, updatedProject.edits.length - 1000);
const editsToRemove = updatedProject.edits.slice(0, updatedProject.edits.length - MAX_HISTORY_ITEMS);
editsToRemove.forEach(edit => {
if (edit.maskReferenceAssetBlobUrl && edit.maskReferenceAssetBlobUrl.startsWith('blob:')) {
urlsToRevoke.push(edit.maskReferenceAssetBlobUrl);
@@ -471,9 +497,9 @@ export const useAppStore = create<AppState>()(
}
// 清理数组
updatedProject.edits.splice(0, updatedProject.edits.length - 1000);
updatedProject.edits.splice(0, updatedProject.edits.length - MAX_HISTORY_ITEMS);
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(1000).catch(err => {
indexedDBService.cleanupOldRecords(MAX_HISTORY_ITEMS).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
}
@@ -492,7 +518,7 @@ export const useAppStore = create<AppState>()(
setSelectedTool: (tool) => set({ selectedTool: tool }),
// 清理旧的历史记录保留最多1000条
// 清理旧的历史记录
cleanupOldHistory: () => set((state) => {
if (!state.currentProject) return {};
@@ -502,9 +528,9 @@ export const useAppStore = create<AppState>()(
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
// 如果生成记录超过1000条,只保留最新的1000条
if (generations.length > 1000) {
const generationsToRemove = generations.slice(0, generations.length - 1000);
// 如果生成记录超过限制,只保留最新的记录
if (generations.length > MAX_HISTORY_ITEMS) {
const generationsToRemove = generations.slice(0, generations.length - MAX_HISTORY_ITEMS);
generationsToRemove.forEach(gen => {
gen.sourceAssets.forEach(asset => {
if (asset.blobUrl.startsWith('blob:')) {
@@ -517,12 +543,12 @@ export const useAppStore = create<AppState>()(
}
});
});
generations.splice(0, generations.length - 1000);
generations.splice(0, generations.length - MAX_HISTORY_ITEMS);
}
// 如果编辑记录超过1000条,只保留最新的1000条
if (edits.length > 1000) {
const editsToRemove = edits.slice(0, edits.length - 1000);
// 如果编辑记录超过限制,只保留最新的记录
if (edits.length > MAX_HISTORY_ITEMS) {
const editsToRemove = edits.slice(0, edits.length - MAX_HISTORY_ITEMS);
editsToRemove.forEach(edit => {
if (edit.maskReferenceAssetBlobUrl && edit.maskReferenceAssetBlobUrl.startsWith('blob:')) {
urlsToRevoke.push(edit.maskReferenceAssetBlobUrl);
@@ -533,7 +559,7 @@ export const useAppStore = create<AppState>()(
}
});
});
edits.splice(0, edits.length - 1000);
edits.splice(0, edits.length - MAX_HISTORY_ITEMS);
}
// 释放Blob URLs
@@ -550,7 +576,7 @@ export const useAppStore = create<AppState>()(
}
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(1000).catch(err => {
indexedDBService.cleanupOldRecords(MAX_HISTORY_ITEMS).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
@@ -583,7 +609,42 @@ export const useAppStore = create<AppState>()(
URL.revokeObjectURL(url);
});
return { ...state, blobStore: new Map() };
})
}),
// 定期清理Blob URL
scheduleBlobCleanup: () => {
// 清理超过10分钟未使用的Blob
const state = get();
const now = Date.now();
// 这里我们简单地清理所有Blob因为在实际应用中很难跟踪哪些Blob正在使用
// 在生产环境中,您可能需要更复杂的跟踪机制
state.blobStore.forEach((blob, url) => {
// 检查URL是否仍在使用中
const isUsedInProject = state.currentProject && (
state.currentProject.generations.some(gen =>
gen.sourceAssets.some(asset => asset.blobUrl === url) ||
gen.outputAssetsBlobUrls.some(outputUrl => outputUrl === url)
) ||
state.currentProject.edits.some(edit =>
(edit.maskReferenceAssetBlobUrl === url) ||
edit.outputAssetsBlobUrls.some(outputUrl => outputUrl === url)
)
);
const isUsedInCanvas = state.canvasImage === url;
const isUsedInUploads = state.uploadedImages.includes(url);
const isUsedInEdits = state.editReferenceImages.includes(url);
// 如果Blob没有被使用则清理它
if (!isUsedInProject && !isUsedInCanvas && !isUsedInUploads && !isUsedInEdits) {
URL.revokeObjectURL(url);
const newBlobStore = new Map(state.blobStore);
newBlobStore.delete(url);
set({ blobStore: newBlobStore });
}
});
}
}),
{
name: 'nano-banana-store',