fix:修复了清除缓存功能的缺失

This commit is contained in:
xuncha
2026-01-17 02:45:10 +08:00
parent afa3e089b1
commit 095c8f0db6
9 changed files with 215 additions and 7 deletions

1
.gitignore vendored
View File

@@ -56,3 +56,4 @@ Thumbs.db
*.aps *.aps
wcdb/ wcdb/
*info

View File

@@ -482,6 +482,40 @@ function registerIpcHandlers() {
return analyticsService.getTimeDistribution() return analyticsService.getTimeDistribution()
}) })
// 缓存管理
ipcMain.handle('cache:clearAnalytics', async () => {
return analyticsService.clearCache()
})
ipcMain.handle('cache:clearImages', async () => {
const imageResult = await imageDecryptService.clearCache()
const emojiResult = chatService.clearCaches({ includeMessages: false, includeContacts: false, includeEmojis: true })
const errors = [imageResult, emojiResult]
.filter((result) => !result.success)
.map((result) => result.error)
.filter(Boolean) as string[]
if (errors.length > 0) {
return { success: false, error: errors.join('; ') }
}
return { success: true }
})
ipcMain.handle('cache:clearAll', async () => {
const [analyticsResult, imageResult] = await Promise.all([
analyticsService.clearCache(),
imageDecryptService.clearCache()
])
const chatResult = chatService.clearCaches()
const errors = [analyticsResult, imageResult, chatResult]
.filter((result) => !result.success)
.map((result) => result.error)
.filter(Boolean) as string[]
if (errors.length > 0) {
return { success: false, error: errors.join('; ') }
}
return { success: true }
})
// 群聊分析相关 // 群聊分析相关
ipcMain.handle('groupAnalytics:getGroupChats', async () => { ipcMain.handle('groupAnalytics:getGroupChats', async () => {
return groupAnalyticsService.getGroupChats() return groupAnalyticsService.getGroupChats()

View File

@@ -140,6 +140,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
} }
}, },
// 缓存管理
cache: {
clearAnalytics: () => ipcRenderer.invoke('cache:clearAnalytics'),
clearImages: () => ipcRenderer.invoke('cache:clearImages'),
clearAll: () => ipcRenderer.invoke('cache:clearAll')
},
// 群聊分析 // 群聊分析
groupAnalytics: { groupAnalytics: {
getGroupChats: () => ipcRenderer.invoke('groupAnalytics:getGroupChats'), getGroupChats: () => ipcRenderer.invoke('groupAnalytics:getGroupChats'),

View File

@@ -1,7 +1,7 @@
import { ConfigService } from './config' import { ConfigService } from './config'
import { wcdbService } from './wcdbService' import { wcdbService } from './wcdbService'
import { join } from 'path' import { join } from 'path'
import { readFile, writeFile } from 'fs/promises' import { readFile, writeFile, rm } from 'fs/promises'
import { app } from 'electron' import { app } from 'electron'
export interface ChatStatistics { export interface ChatStatistics {
@@ -528,6 +528,18 @@ class AnalyticsService {
return { success: false, error: String(e) } return { success: false, error: String(e) }
} }
} }
async clearCache(): Promise<{ success: boolean; error?: string }> {
this.aggregateCache = null
this.fallbackAggregateCache = null
this.aggregatePromise = null
try {
await rm(this.getCacheFilePath(), { force: true })
return { success: true }
} catch (e) {
return { success: false, error: String(e) }
}
}
} }
export const analyticsService = new AnalyticsService() export const analyticsService = new AnalyticsService()

View File

@@ -1,5 +1,5 @@
import { join, dirname } from 'path' import { join, dirname } from 'path'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs'
import { app } from 'electron' import { app } from 'electron'
export interface ContactCacheEntry { export interface ContactCacheEntry {
@@ -72,4 +72,13 @@ export class ContactCacheService {
console.error('ContactCacheService: 保存缓存失败', error) console.error('ContactCacheService: 保存缓存失败', error)
} }
} }
clear(): void {
this.cache = {}
try {
rmSync(this.cacheFilePath, { force: true })
} catch (error) {
console.error('ContactCacheService: 清理缓存失败', error)
}
}
} }

View File

@@ -2,7 +2,7 @@ import { app, BrowserWindow } from 'electron'
import { basename, dirname, extname, join } from 'path' import { basename, dirname, extname, join } from 'path'
import { pathToFileURL } from 'url' import { pathToFileURL } from 'url'
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, appendFileSync } from 'fs' import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, appendFileSync } from 'fs'
import { writeFile } from 'fs/promises' import { writeFile, rm, readdir } from 'fs/promises'
import crypto from 'crypto' import crypto from 'crypto'
import { Worker } from 'worker_threads' import { Worker } from 'worker_threads'
import { ConfigService } from './config' import { ConfigService } from './config'
@@ -1646,6 +1646,71 @@ export class ImageDecryptService {
await writeFile(outputPath, decrypted) await writeFile(outputPath, decrypted)
} }
async clearCache(): Promise<{ success: boolean; error?: string }> {
this.resolvedCache.clear()
this.hardlinkCache.clear()
this.pending.clear()
this.updateFlags.clear()
this.cacheIndexed = false
this.cacheIndexing = null
const configured = this.configService.get('cachePath')
const root = configured
? join(configured, 'Images')
: join(app.getPath('documents'), 'WeFlow', 'Images')
try {
if (!existsSync(root)) {
return { success: true }
}
const monthPattern = /^\d{4}-\d{2}$/
const clearFilesInDir = async (dirPath: string): Promise<void> => {
let entries: Array<{ name: string; isDirectory: () => boolean }>
try {
entries = await readdir(dirPath, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
const fullPath = join(dirPath, entry.name)
if (entry.isDirectory()) {
await clearFilesInDir(fullPath)
continue
}
try {
await rm(fullPath, { force: true })
} catch { }
}
}
const traverse = async (dirPath: string): Promise<void> => {
let entries: Array<{ name: string; isDirectory: () => boolean }>
try {
entries = await readdir(dirPath, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
const fullPath = join(dirPath, entry.name)
if (entry.isDirectory()) {
if (monthPattern.test(entry.name)) {
await clearFilesInDir(fullPath)
} else {
await traverse(fullPath)
}
continue
}
try {
await rm(fullPath, { force: true })
} catch { }
}
}
await traverse(root)
return { success: true }
} catch (e) {
return { success: false, error: String(e) }
}
}
} }
export const imageDecryptService = new ImageDecryptService() export const imageDecryptService = new ImageDecryptService()

View File

@@ -1,5 +1,5 @@
import { join, dirname } from 'path' import { join, dirname } from 'path'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs'
import { app } from 'electron' import { app } from 'electron'
export interface SessionMessageCacheEntry { export interface SessionMessageCacheEntry {
@@ -65,4 +65,13 @@ export class MessageCacheService {
console.error('MessageCacheService: 保存缓存失败', error) console.error('MessageCacheService: 保存缓存失败', error)
} }
} }
clear(): void {
this.cache = {}
try {
rmSync(this.cacheFilePath, { force: true })
} catch (error) {
console.error('MessageCacheService: 清理缓存失败', error)
}
}
} }

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef } from 'react'
import { useAppStore } from '../stores/appStore' import { useAppStore } from '../stores/appStore'
import { useThemeStore, themes } from '../stores/themeStore' import { useThemeStore, themes } from '../stores/themeStore'
import { useAnalyticsStore } from '../stores/analyticsStore'
import { dialog } from '../services/ipc' import { dialog } from '../services/ipc'
import * as configService from '../services/config' import * as configService from '../services/config'
import { import {
@@ -27,6 +28,7 @@ interface WxidOption {
function SettingsPage() { function SettingsPage() {
const { setDbConnected, setLoading, reset } = useAppStore() const { setDbConnected, setLoading, reset } = useAppStore()
const { currentTheme, themeMode, setTheme, setThemeMode } = useThemeStore() const { currentTheme, themeMode, setTheme, setThemeMode } = useThemeStore()
const clearAnalyticsStoreCache = useAnalyticsStore((state) => state.clearCache)
const [activeTab, setActiveTab] = useState<SettingsTab>('appearance') const [activeTab, setActiveTab] = useState<SettingsTab>('appearance')
const [decryptKey, setDecryptKey] = useState('') const [decryptKey, setDecryptKey] = useState('')
@@ -55,6 +57,11 @@ function SettingsPage() {
const [dbKeyStatus, setDbKeyStatus] = useState('') const [dbKeyStatus, setDbKeyStatus] = useState('')
const [imageKeyStatus, setImageKeyStatus] = useState('') const [imageKeyStatus, setImageKeyStatus] = useState('')
const [isManualStartPrompt, setIsManualStartPrompt] = useState(false) const [isManualStartPrompt, setIsManualStartPrompt] = useState(false)
const [isClearingAnalyticsCache, setIsClearingAnalyticsCache] = useState(false)
const [isClearingImageCache, setIsClearingImageCache] = useState(false)
const [isClearingAllCache, setIsClearingAllCache] = useState(false)
const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache
useEffect(() => { useEffect(() => {
loadConfig() loadConfig()
@@ -428,6 +435,59 @@ function SettingsPage() {
} }
} }
const handleClearAnalyticsCache = async () => {
if (isClearingCache) return
setIsClearingAnalyticsCache(true)
try {
const result = await window.electronAPI.cache.clearAnalytics()
if (result.success) {
clearAnalyticsStoreCache()
showMessage('已清除分析缓存', true)
} else {
showMessage(`清除分析缓存失败: ${result.error || '未知错误'}`, false)
}
} catch (e) {
showMessage(`清除分析缓存失败: ${e}`, false)
} finally {
setIsClearingAnalyticsCache(false)
}
}
const handleClearImageCache = async () => {
if (isClearingCache) return
setIsClearingImageCache(true)
try {
const result = await window.electronAPI.cache.clearImages()
if (result.success) {
showMessage('已清除图片缓存', true)
} else {
showMessage(`清除图片缓存失败: ${result.error || '未知错误'}`, false)
}
} catch (e) {
showMessage(`清除图片缓存失败: ${e}`, false)
} finally {
setIsClearingImageCache(false)
}
}
const handleClearAllCache = async () => {
if (isClearingCache) return
setIsClearingAllCache(true)
try {
const result = await window.electronAPI.cache.clearAll()
if (result.success) {
clearAnalyticsStoreCache()
showMessage('已清除所有缓存', true)
} else {
showMessage(`清除所有缓存失败: ${result.error || '未知错误'}`, false)
}
} catch (e) {
showMessage(`清除所有缓存失败: ${e}`, false)
} finally {
setIsClearingAllCache(false)
}
}
const renderAppearanceTab = () => ( const renderAppearanceTab = () => (
<div className="tab-content"> <div className="tab-content">
<div className="theme-mode-toggle"> <div className="theme-mode-toggle">
@@ -597,9 +657,15 @@ function SettingsPage() {
<div className="tab-content"> <div className="tab-content">
<p className="section-desc"></p> <p className="section-desc"></p>
<div className="btn-row"> <div className="btn-row">
<button className="btn btn-secondary"><Trash2 size={16} /> </button> <button className="btn btn-secondary" onClick={handleClearAnalyticsCache} disabled={isClearingCache}>
<button className="btn btn-secondary"><Trash2 size={16} /> </button> <Trash2 size={16} />
<button className="btn btn-danger"><Trash2 size={16} /> </button> </button>
<button className="btn btn-secondary" onClick={handleClearImageCache} disabled={isClearingCache}>
<Trash2 size={16} />
</button>
<button className="btn btn-danger" onClick={handleClearAllCache} disabled={isClearingCache}>
<Trash2 size={16} />
</button>
</div> </div>
<div className="divider" /> <div className="divider" />
<p className="section-desc"></p> <p className="section-desc"></p>

View File

@@ -148,6 +148,11 @@ export interface ElectronAPI {
}> }>
onProgress: (callback: (payload: { status: string; progress: number }) => void) => () => void onProgress: (callback: (payload: { status: string; progress: number }) => void) => () => void
} }
cache: {
clearAnalytics: () => Promise<{ success: boolean; error?: string }>
clearImages: () => Promise<{ success: boolean; error?: string }>
clearAll: () => Promise<{ success: boolean; error?: string }>
}
groupAnalytics: { groupAnalytics: {
getGroupChats: () => Promise<{ getGroupChats: () => Promise<{
success: boolean success: boolean