mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
增加一个导出的缓存
This commit is contained in:
@@ -1596,6 +1596,7 @@ function ExportPage() {
|
|||||||
const sessionMutualFriendsRunIdRef = useRef(0)
|
const sessionMutualFriendsRunIdRef = useRef(0)
|
||||||
const sessionMutualFriendsWorkerRunningRef = useRef(false)
|
const sessionMutualFriendsWorkerRunningRef = useRef(false)
|
||||||
const sessionMutualFriendsBackgroundFeedTimerRef = useRef<number | null>(null)
|
const sessionMutualFriendsBackgroundFeedTimerRef = useRef<number | null>(null)
|
||||||
|
const sessionMutualFriendsPersistTimerRef = useRef<number | null>(null)
|
||||||
const sessionMutualFriendsVisibleRangeRef = useRef<{ startIndex: number; endIndex: number }>({
|
const sessionMutualFriendsVisibleRangeRef = useRef<{ startIndex: number; endIndex: number }>({
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
endIndex: -1
|
endIndex: -1
|
||||||
@@ -2748,8 +2749,32 @@ function ExportPage() {
|
|||||||
window.clearTimeout(sessionMutualFriendsBackgroundFeedTimerRef.current)
|
window.clearTimeout(sessionMutualFriendsBackgroundFeedTimerRef.current)
|
||||||
sessionMutualFriendsBackgroundFeedTimerRef.current = null
|
sessionMutualFriendsBackgroundFeedTimerRef.current = null
|
||||||
}
|
}
|
||||||
|
if (sessionMutualFriendsPersistTimerRef.current) {
|
||||||
|
window.clearTimeout(sessionMutualFriendsPersistTimerRef.current)
|
||||||
|
sessionMutualFriendsPersistTimerRef.current = null
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const flushSessionMutualFriendsCache = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const scopeKey = await ensureExportCacheScope()
|
||||||
|
await configService.setExportSessionMutualFriendsCache(
|
||||||
|
scopeKey,
|
||||||
|
sessionMutualFriendsDirectMetricsRef.current
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('写入导出页共同好友缓存失败:', error)
|
||||||
|
}
|
||||||
|
}, [ensureExportCacheScope])
|
||||||
|
|
||||||
|
const scheduleFlushSessionMutualFriendsCache = useCallback(() => {
|
||||||
|
if (sessionMutualFriendsPersistTimerRef.current) return
|
||||||
|
sessionMutualFriendsPersistTimerRef.current = window.setTimeout(() => {
|
||||||
|
sessionMutualFriendsPersistTimerRef.current = null
|
||||||
|
void flushSessionMutualFriendsCache()
|
||||||
|
}, SESSION_MEDIA_METRIC_CACHE_FLUSH_DELAY_MS)
|
||||||
|
}, [flushSessionMutualFriendsCache])
|
||||||
|
|
||||||
const isSessionMutualFriendsReady = useCallback((sessionId: string): boolean => {
|
const isSessionMutualFriendsReady = useCallback((sessionId: string): boolean => {
|
||||||
if (!sessionId) return true
|
if (!sessionId) return true
|
||||||
if (sessionMutualFriendsReadySetRef.current.has(sessionId)) return true
|
if (sessionMutualFriendsReadySetRef.current.has(sessionId)) return true
|
||||||
@@ -2879,10 +2904,35 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
}, [getSessionMutualFriendProfile])
|
}, [getSessionMutualFriendProfile])
|
||||||
|
|
||||||
|
const rebuildSessionMutualFriendsStateFromDirectMetrics = useCallback((sessionIds?: string[]) => {
|
||||||
|
const targets = Array.isArray(sessionIds) && sessionIds.length > 0
|
||||||
|
? sessionIds
|
||||||
|
: Object.keys(sessionMutualFriendsDirectMetricsRef.current)
|
||||||
|
const nextMetrics: Record<string, SessionMutualFriendsMetric> = {}
|
||||||
|
const readyIds: string[] = []
|
||||||
|
for (const sessionIdRaw of targets) {
|
||||||
|
const sessionId = String(sessionIdRaw || '').trim()
|
||||||
|
if (!sessionId) continue
|
||||||
|
const rebuilt = rebuildSessionMutualFriendsMetric(sessionId)
|
||||||
|
if (!rebuilt) continue
|
||||||
|
nextMetrics[sessionId] = rebuilt
|
||||||
|
readyIds.push(sessionId)
|
||||||
|
}
|
||||||
|
sessionMutualFriendsMetricsRef.current = nextMetrics
|
||||||
|
setSessionMutualFriendsMetrics(nextMetrics)
|
||||||
|
if (readyIds.length > 0) {
|
||||||
|
for (const sessionId of readyIds) {
|
||||||
|
sessionMutualFriendsReadySetRef.current.add(sessionId)
|
||||||
|
}
|
||||||
|
patchSessionLoadTraceStage(readyIds, 'mutualFriends', 'done')
|
||||||
|
}
|
||||||
|
}, [patchSessionLoadTraceStage, rebuildSessionMutualFriendsMetric])
|
||||||
|
|
||||||
const applySessionMutualFriendsMetric = useCallback((sessionId: string, directMetric: SessionMutualFriendsMetric) => {
|
const applySessionMutualFriendsMetric = useCallback((sessionId: string, directMetric: SessionMutualFriendsMetric) => {
|
||||||
const normalizedSessionId = String(sessionId || '').trim()
|
const normalizedSessionId = String(sessionId || '').trim()
|
||||||
if (!normalizedSessionId) return
|
if (!normalizedSessionId) return
|
||||||
sessionMutualFriendsDirectMetricsRef.current[normalizedSessionId] = directMetric
|
sessionMutualFriendsDirectMetricsRef.current[normalizedSessionId] = directMetric
|
||||||
|
scheduleFlushSessionMutualFriendsCache()
|
||||||
|
|
||||||
const impactedSessionIds = new Set<string>([normalizedSessionId])
|
const impactedSessionIds = new Set<string>([normalizedSessionId])
|
||||||
const allSessionIds = sessionsRef.current
|
const allSessionIds = sessionsRef.current
|
||||||
@@ -2912,7 +2962,7 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
return changed ? next : prev
|
return changed ? next : prev
|
||||||
})
|
})
|
||||||
}, [getSessionMutualFriendProfile, rebuildSessionMutualFriendsMetric])
|
}, [getSessionMutualFriendProfile, rebuildSessionMutualFriendsMetric, scheduleFlushSessionMutualFriendsCache])
|
||||||
|
|
||||||
const isSessionMediaMetricReady = useCallback((sessionId: string): boolean => {
|
const isSessionMediaMetricReady = useCallback((sessionId: string): boolean => {
|
||||||
if (!sessionId) return true
|
if (!sessionId) return true
|
||||||
@@ -3339,11 +3389,13 @@ function ExportPage() {
|
|||||||
const [
|
const [
|
||||||
cachedContactsPayload,
|
cachedContactsPayload,
|
||||||
cachedMessageCountsPayload,
|
cachedMessageCountsPayload,
|
||||||
cachedContentMetricsPayload
|
cachedContentMetricsPayload,
|
||||||
|
cachedMutualFriendsPayload
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
loadContactsCaches(scopeKey),
|
loadContactsCaches(scopeKey),
|
||||||
configService.getExportSessionMessageCountCache(scopeKey),
|
configService.getExportSessionMessageCountCache(scopeKey),
|
||||||
configService.getExportSessionContentMetricCache(scopeKey)
|
configService.getExportSessionContentMetricCache(scopeKey),
|
||||||
|
configService.getExportSessionMutualFriendsCache(scopeKey)
|
||||||
])
|
])
|
||||||
if (isStale()) return
|
if (isStale()) return
|
||||||
|
|
||||||
@@ -3411,6 +3463,15 @@ function ExportPage() {
|
|||||||
if (cachedContentMetricReadySessionIds.length > 0) {
|
if (cachedContentMetricReadySessionIds.length > 0) {
|
||||||
patchSessionLoadTraceStage(cachedContentMetricReadySessionIds, 'mediaMetrics', 'done')
|
patchSessionLoadTraceStage(cachedContentMetricReadySessionIds, 'mediaMetrics', 'done')
|
||||||
}
|
}
|
||||||
|
const cachedMutualFriendDirectMetrics = Object.entries(cachedMutualFriendsPayload?.metrics || {}).reduce<Record<string, SessionMutualFriendsMetric>>((acc, [sessionIdRaw, metricRaw]) => {
|
||||||
|
const sessionId = String(sessionIdRaw || '').trim()
|
||||||
|
if (!exportableSessionIdSet.has(sessionId) || !isSingleContactSession(sessionId)) return acc
|
||||||
|
const metric = metricRaw as SessionMutualFriendsMetric | undefined
|
||||||
|
if (!metric || !Array.isArray(metric.items) || !Number.isFinite(metric.count)) return acc
|
||||||
|
acc[sessionId] = metric
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
const cachedMutualFriendSessionIds = Object.keys(cachedMutualFriendDirectMetrics)
|
||||||
|
|
||||||
if (isStale()) return
|
if (isStale()) return
|
||||||
if (Object.keys(cachedMessageCounts).length > 0) {
|
if (Object.keys(cachedMessageCounts).length > 0) {
|
||||||
@@ -3422,6 +3483,13 @@ function ExportPage() {
|
|||||||
if (Object.keys(cachedContentMetrics).length > 0) {
|
if (Object.keys(cachedContentMetrics).length > 0) {
|
||||||
mergeSessionContentMetrics(cachedContentMetrics)
|
mergeSessionContentMetrics(cachedContentMetrics)
|
||||||
}
|
}
|
||||||
|
if (cachedMutualFriendSessionIds.length > 0) {
|
||||||
|
sessionMutualFriendsDirectMetricsRef.current = cachedMutualFriendDirectMetrics
|
||||||
|
rebuildSessionMutualFriendsStateFromDirectMetrics(cachedMutualFriendSessionIds)
|
||||||
|
} else {
|
||||||
|
sessionMutualFriendsMetricsRef.current = {}
|
||||||
|
setSessionMutualFriendsMetrics({})
|
||||||
|
}
|
||||||
setSessions(baseSessions)
|
setSessions(baseSessions)
|
||||||
sessionsHydratedAtRef.current = Date.now()
|
sessionsHydratedAtRef.current = Date.now()
|
||||||
void (async () => {
|
void (async () => {
|
||||||
@@ -3622,7 +3690,7 @@ function ExportPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
if (!isStale()) setIsLoading(false)
|
if (!isStale()) setIsLoading(false)
|
||||||
}
|
}
|
||||||
}, [ensureExportCacheScope, loadContactsCaches, loadSessionMessageCounts, mergeSessionContentMetrics, patchSessionLoadTraceStage, resetSessionMediaMetricLoader, resetSessionMutualFriendsLoader, syncContactTypeCounts])
|
}, [ensureExportCacheScope, loadContactsCaches, loadSessionMessageCounts, mergeSessionContentMetrics, patchSessionLoadTraceStage, rebuildSessionMutualFriendsStateFromDirectMetrics, resetSessionMediaMetricLoader, resetSessionMutualFriendsLoader, syncContactTypeCounts])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isExportRoute) return
|
if (!isExportRoute) return
|
||||||
@@ -3630,10 +3698,7 @@ function ExportPage() {
|
|||||||
const hasFreshSessionSnapshot = hasBaseConfigReadyRef.current &&
|
const hasFreshSessionSnapshot = hasBaseConfigReadyRef.current &&
|
||||||
sessionsRef.current.length > 0 &&
|
sessionsRef.current.length > 0 &&
|
||||||
now - sessionsHydratedAtRef.current <= EXPORT_REENTER_SESSION_SOFT_REFRESH_MS
|
now - sessionsHydratedAtRef.current <= EXPORT_REENTER_SESSION_SOFT_REFRESH_MS
|
||||||
const hasFreshSnsSnapshot = hasSeededSnsStatsRef.current &&
|
const baseConfigPromise = loadBaseConfig()
|
||||||
now - snsStatsHydratedAtRef.current <= EXPORT_REENTER_SNS_SOFT_REFRESH_MS
|
|
||||||
|
|
||||||
void loadBaseConfig()
|
|
||||||
void ensureSharedTabCountsLoaded()
|
void ensureSharedTabCountsLoaded()
|
||||||
if (!hasFreshSessionSnapshot) {
|
if (!hasFreshSessionSnapshot) {
|
||||||
void loadSessions()
|
void loadSessions()
|
||||||
@@ -3641,9 +3706,14 @@ function ExportPage() {
|
|||||||
|
|
||||||
// 朋友圈统计延后一点加载,避免与首屏会话初始化抢占。
|
// 朋友圈统计延后一点加载,避免与首屏会话初始化抢占。
|
||||||
const timer = window.setTimeout(() => {
|
const timer = window.setTimeout(() => {
|
||||||
if (!hasFreshSnsSnapshot) {
|
void (async () => {
|
||||||
void loadSnsStats({ full: true })
|
await baseConfigPromise
|
||||||
}
|
const hasFreshSnsSnapshot = hasSeededSnsStatsRef.current &&
|
||||||
|
Date.now() - snsStatsHydratedAtRef.current <= EXPORT_REENTER_SNS_SOFT_REFRESH_MS
|
||||||
|
if (!hasFreshSnsSnapshot) {
|
||||||
|
void loadSnsStats({ full: true })
|
||||||
|
}
|
||||||
|
})()
|
||||||
}, 120)
|
}, 120)
|
||||||
|
|
||||||
return () => window.clearTimeout(timer)
|
return () => window.clearTimeout(timer)
|
||||||
@@ -4988,9 +5058,14 @@ function ExportPage() {
|
|||||||
window.clearTimeout(sessionMutualFriendsBackgroundFeedTimerRef.current)
|
window.clearTimeout(sessionMutualFriendsBackgroundFeedTimerRef.current)
|
||||||
sessionMutualFriendsBackgroundFeedTimerRef.current = null
|
sessionMutualFriendsBackgroundFeedTimerRef.current = null
|
||||||
}
|
}
|
||||||
|
if (sessionMutualFriendsPersistTimerRef.current) {
|
||||||
|
window.clearTimeout(sessionMutualFriendsPersistTimerRef.current)
|
||||||
|
sessionMutualFriendsPersistTimerRef.current = null
|
||||||
|
}
|
||||||
void flushSessionMediaMetricCache()
|
void flushSessionMediaMetricCache()
|
||||||
|
void flushSessionMutualFriendsCache()
|
||||||
}
|
}
|
||||||
}, [flushSessionMediaMetricCache])
|
}, [flushSessionMediaMetricCache, flushSessionMutualFriendsCache])
|
||||||
|
|
||||||
const contactByUsername = useMemo(() => {
|
const contactByUsername = useMemo(() => {
|
||||||
const map = new Map<string, ContactInfo>()
|
const map = new Map<string, ContactInfo>()
|
||||||
@@ -5254,6 +5329,23 @@ function ExportPage() {
|
|||||||
console.error('导出页读取会话统计缓存失败:', error)
|
console.error('导出页读取会话统计缓存失败:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const relationCacheResult = await window.electronAPI.chat.getExportSessionStats(
|
||||||
|
[normalizedSessionId],
|
||||||
|
{ includeRelations: true, allowStaleCache: true, cacheOnly: true }
|
||||||
|
)
|
||||||
|
if (requestSeq !== detailRequestSeqRef.current) return
|
||||||
|
if (relationCacheResult.success && relationCacheResult.data) {
|
||||||
|
const relationMetric = relationCacheResult.data[normalizedSessionId] as SessionExportMetric | undefined
|
||||||
|
const relationCacheMeta = relationCacheResult.cache?.[normalizedSessionId] as SessionExportCacheMeta | undefined
|
||||||
|
if (relationMetric) {
|
||||||
|
applySessionDetailStats(normalizedSessionId, relationMetric, relationCacheMeta, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出页读取会话关系缓存失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
const lastPreciseAt = sessionPreciseRefreshAtRef.current[preciseCacheKey] || 0
|
const lastPreciseAt = sessionPreciseRefreshAtRef.current[preciseCacheKey] || 0
|
||||||
const hasRecentPrecise = Date.now() - lastPreciseAt <= DETAIL_PRECISE_REFRESH_COOLDOWN_MS
|
const hasRecentPrecise = Date.now() - lastPreciseAt <= DETAIL_PRECISE_REFRESH_COOLDOWN_MS
|
||||||
const shouldRunPreciseRefresh = !hasRecentPrecise && (!quickMetric || Boolean(quickCacheMeta?.stale))
|
const shouldRunPreciseRefresh = !hasRecentPrecise && (!quickMetric || Boolean(quickCacheMeta?.stale))
|
||||||
@@ -5302,16 +5394,36 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
}, [applySessionDetailStats, contactByUsername, mergeSessionContentMetrics, sessionContentMetrics, sessionMessageCounts, sessionRowByUsername])
|
}, [applySessionDetailStats, contactByUsername, mergeSessionContentMetrics, sessionContentMetrics, sessionMessageCounts, sessionRowByUsername])
|
||||||
|
|
||||||
const loadSessionRelationStats = useCallback(async () => {
|
const loadSessionRelationStats = useCallback(async (options?: { forceRefresh?: boolean }) => {
|
||||||
const normalizedSessionId = String(sessionDetail?.wxid || '').trim()
|
const normalizedSessionId = String(sessionDetail?.wxid || '').trim()
|
||||||
if (!normalizedSessionId || isLoadingSessionRelationStats) return
|
if (!normalizedSessionId || isLoadingSessionRelationStats) return
|
||||||
|
|
||||||
const requestSeq = detailRequestSeqRef.current
|
const requestSeq = detailRequestSeqRef.current
|
||||||
|
const forceRefresh = options?.forceRefresh === true
|
||||||
setIsLoadingSessionRelationStats(true)
|
setIsLoadingSessionRelationStats(true)
|
||||||
try {
|
try {
|
||||||
|
if (!forceRefresh) {
|
||||||
|
const relationCacheResult = await window.electronAPI.chat.getExportSessionStats(
|
||||||
|
[normalizedSessionId],
|
||||||
|
{ includeRelations: true, allowStaleCache: true, cacheOnly: true }
|
||||||
|
)
|
||||||
|
if (requestSeq !== detailRequestSeqRef.current) return
|
||||||
|
|
||||||
|
const relationMetric = relationCacheResult.success && relationCacheResult.data
|
||||||
|
? relationCacheResult.data[normalizedSessionId] as SessionExportMetric | undefined
|
||||||
|
: undefined
|
||||||
|
const relationCacheMeta = relationCacheResult.success
|
||||||
|
? relationCacheResult.cache?.[normalizedSessionId] as SessionExportCacheMeta | undefined
|
||||||
|
: undefined
|
||||||
|
if (relationMetric) {
|
||||||
|
applySessionDetailStats(normalizedSessionId, relationMetric, relationCacheMeta, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const relationResult = await window.electronAPI.chat.getExportSessionStats(
|
const relationResult = await window.electronAPI.chat.getExportSessionStats(
|
||||||
[normalizedSessionId],
|
[normalizedSessionId],
|
||||||
{ includeRelations: true, forceRefresh: true, preferAccurateSpecialTypes: true }
|
{ includeRelations: true, forceRefresh, preferAccurateSpecialTypes: true }
|
||||||
)
|
)
|
||||||
if (requestSeq !== detailRequestSeqRef.current) return
|
if (requestSeq !== detailRequestSeqRef.current) return
|
||||||
|
|
||||||
@@ -5333,6 +5445,60 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
}, [applySessionDetailStats, isLoadingSessionRelationStats, sessionDetail?.wxid])
|
}, [applySessionDetailStats, isLoadingSessionRelationStats, sessionDetail?.wxid])
|
||||||
|
|
||||||
|
const handleRefreshTableData = useCallback(async () => {
|
||||||
|
const scopeKey = await ensureExportCacheScope()
|
||||||
|
|
||||||
|
resetSessionMutualFriendsLoader()
|
||||||
|
sessionMutualFriendsMetricsRef.current = {}
|
||||||
|
setSessionMutualFriendsMetrics({})
|
||||||
|
closeSessionMutualFriendsDialog()
|
||||||
|
try {
|
||||||
|
await configService.clearExportSessionMutualFriendsCache(scopeKey)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清理导出页共同好友缓存失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSessionCountStageReady) {
|
||||||
|
const visibleTargetIds = collectVisibleSessionMutualFriendsTargets(filteredContacts)
|
||||||
|
const visibleTargetSet = new Set(visibleTargetIds)
|
||||||
|
const remainingTargetIds = sessionsRef.current
|
||||||
|
.filter((session) => session.hasSession && isSingleContactSession(session.username) && !visibleTargetSet.has(session.username))
|
||||||
|
.map((session) => session.username)
|
||||||
|
|
||||||
|
if (visibleTargetIds.length > 0) {
|
||||||
|
enqueueSessionMutualFriendsRequests(visibleTargetIds, { front: true })
|
||||||
|
}
|
||||||
|
if (remainingTargetIds.length > 0) {
|
||||||
|
enqueueSessionMutualFriendsRequests(remainingTargetIds)
|
||||||
|
}
|
||||||
|
scheduleSessionMutualFriendsWorker()
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
loadContactsList({ scopeKey }),
|
||||||
|
loadSnsStats({ full: true }),
|
||||||
|
loadSnsUserPostCounts({ force: true })
|
||||||
|
])
|
||||||
|
|
||||||
|
if (String(sessionDetail?.wxid || '').trim()) {
|
||||||
|
void loadSessionRelationStats({ forceRefresh: true })
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
closeSessionMutualFriendsDialog,
|
||||||
|
collectVisibleSessionMutualFriendsTargets,
|
||||||
|
enqueueSessionMutualFriendsRequests,
|
||||||
|
ensureExportCacheScope,
|
||||||
|
filteredContacts,
|
||||||
|
isSessionCountStageReady,
|
||||||
|
loadContactsList,
|
||||||
|
loadSessionRelationStats,
|
||||||
|
loadSnsStats,
|
||||||
|
loadSnsUserPostCounts,
|
||||||
|
resetSessionMutualFriendsLoader,
|
||||||
|
scheduleSessionMutualFriendsWorker,
|
||||||
|
sessionDetail?.wxid
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showSessionDetailPanel || !sessionDetailSupportsSnsTimeline) return
|
if (!showSessionDetailPanel || !sessionDetailSupportsSnsTimeline) return
|
||||||
if (snsUserPostCountsStatus === 'idle') {
|
if (snsUserPostCountsStatus === 'idle') {
|
||||||
@@ -6371,7 +6537,7 @@ function ExportPage() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button className="secondary-btn" onClick={() => void loadContactsList()} disabled={isContactsListLoading}>
|
<button className="secondary-btn" onClick={() => void handleRefreshTableData()} disabled={isContactsListLoading}>
|
||||||
<RefreshCw size={14} className={isContactsListLoading ? 'spin' : ''} />
|
<RefreshCw size={14} className={isContactsListLoading ? 'spin' : ''} />
|
||||||
刷新
|
刷新
|
||||||
</button>
|
</button>
|
||||||
@@ -6468,7 +6634,7 @@ function ExportPage() {
|
|||||||
<li>可能原因3:数据库连接状态异常或 IPC 调用卡住。</li>
|
<li>可能原因3:数据库连接状态异常或 IPC 调用卡住。</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="issue-actions">
|
<div className="issue-actions">
|
||||||
<button className="issue-btn primary" onClick={() => void loadContactsList()}>
|
<button className="issue-btn primary" onClick={() => void handleRefreshTableData()}>
|
||||||
<RefreshCw size={14} />
|
<RefreshCw size={14} />
|
||||||
<span>重试加载</span>
|
<span>重试加载</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const CONFIG_KEYS = {
|
|||||||
EXPORT_SESSION_CONTENT_METRIC_CACHE_MAP: 'exportSessionContentMetricCacheMap',
|
EXPORT_SESSION_CONTENT_METRIC_CACHE_MAP: 'exportSessionContentMetricCacheMap',
|
||||||
EXPORT_SNS_STATS_CACHE_MAP: 'exportSnsStatsCacheMap',
|
EXPORT_SNS_STATS_CACHE_MAP: 'exportSnsStatsCacheMap',
|
||||||
EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP: 'exportSnsUserPostCountsCacheMap',
|
EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP: 'exportSnsUserPostCountsCacheMap',
|
||||||
|
EXPORT_SESSION_MUTUAL_FRIENDS_CACHE_MAP: 'exportSessionMutualFriendsCacheMap',
|
||||||
SNS_PAGE_CACHE_MAP: 'snsPageCacheMap',
|
SNS_PAGE_CACHE_MAP: 'snsPageCacheMap',
|
||||||
CONTACTS_LOAD_TIMEOUT_MS: 'contactsLoadTimeoutMs',
|
CONTACTS_LOAD_TIMEOUT_MS: 'contactsLoadTimeoutMs',
|
||||||
CONTACTS_LIST_CACHE_MAP: 'contactsListCacheMap',
|
CONTACTS_LIST_CACHE_MAP: 'contactsListCacheMap',
|
||||||
@@ -596,6 +597,34 @@ export interface ExportSnsUserPostCountsCacheItem {
|
|||||||
counts: Record<string, number>
|
counts: Record<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ExportSessionMutualFriendDirection = 'incoming' | 'outgoing' | 'bidirectional'
|
||||||
|
export type ExportSessionMutualFriendBehavior = 'likes' | 'comments' | 'both'
|
||||||
|
|
||||||
|
export interface ExportSessionMutualFriendCacheItem {
|
||||||
|
name: string
|
||||||
|
incomingLikeCount: number
|
||||||
|
incomingCommentCount: number
|
||||||
|
outgoingLikeCount: number
|
||||||
|
outgoingCommentCount: number
|
||||||
|
totalCount: number
|
||||||
|
latestTime: number
|
||||||
|
direction: ExportSessionMutualFriendDirection
|
||||||
|
behavior: ExportSessionMutualFriendBehavior
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportSessionMutualFriendsCacheEntry {
|
||||||
|
count: number
|
||||||
|
items: ExportSessionMutualFriendCacheItem[]
|
||||||
|
loadedPosts: number
|
||||||
|
totalPosts: number | null
|
||||||
|
computedAt: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportSessionMutualFriendsCacheItem {
|
||||||
|
updatedAt: number
|
||||||
|
metrics: Record<string, ExportSessionMutualFriendsCacheEntry>
|
||||||
|
}
|
||||||
|
|
||||||
export interface SnsPageOverviewCache {
|
export interface SnsPageOverviewCache {
|
||||||
totalPosts: number
|
totalPosts: number
|
||||||
totalFriends: number
|
totalFriends: number
|
||||||
@@ -855,6 +884,148 @@ export async function setExportSnsUserPostCountsCache(
|
|||||||
await config.set(CONFIG_KEYS.EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP, map)
|
await config.set(CONFIG_KEYS.EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeMutualFriendDirection = (value: unknown): ExportSessionMutualFriendDirection | null => {
|
||||||
|
if (value === 'incoming' || value === 'outgoing' || value === 'bidirectional') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeMutualFriendBehavior = (value: unknown): ExportSessionMutualFriendBehavior | null => {
|
||||||
|
if (value === 'likes' || value === 'comments' || value === 'both') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeExportSessionMutualFriendsCacheEntry = (raw: unknown): ExportSessionMutualFriendsCacheEntry | null => {
|
||||||
|
if (!raw || typeof raw !== 'object') return null
|
||||||
|
const source = raw as Record<string, unknown>
|
||||||
|
const count = Number(source.count)
|
||||||
|
const loadedPosts = Number(source.loadedPosts)
|
||||||
|
const computedAt = Number(source.computedAt)
|
||||||
|
const itemsRaw = Array.isArray(source.items) ? source.items : []
|
||||||
|
const totalPostsRaw = source.totalPosts
|
||||||
|
const totalPosts = totalPostsRaw === null || totalPostsRaw === undefined
|
||||||
|
? null
|
||||||
|
: Number(totalPostsRaw)
|
||||||
|
|
||||||
|
if (!Number.isFinite(count) || count < 0 || !Number.isFinite(loadedPosts) || loadedPosts < 0 || !Number.isFinite(computedAt) || computedAt < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: ExportSessionMutualFriendCacheItem[] = []
|
||||||
|
for (const itemRaw of itemsRaw) {
|
||||||
|
if (!itemRaw || typeof itemRaw !== 'object') continue
|
||||||
|
const item = itemRaw as Record<string, unknown>
|
||||||
|
const name = String(item.name || '').trim()
|
||||||
|
const direction = normalizeMutualFriendDirection(item.direction)
|
||||||
|
const behavior = normalizeMutualFriendBehavior(item.behavior)
|
||||||
|
const incomingLikeCount = Number(item.incomingLikeCount)
|
||||||
|
const incomingCommentCount = Number(item.incomingCommentCount)
|
||||||
|
const outgoingLikeCount = Number(item.outgoingLikeCount)
|
||||||
|
const outgoingCommentCount = Number(item.outgoingCommentCount)
|
||||||
|
const totalCount = Number(item.totalCount)
|
||||||
|
const latestTime = Number(item.latestTime)
|
||||||
|
if (!name || !direction || !behavior) continue
|
||||||
|
if (
|
||||||
|
!Number.isFinite(incomingLikeCount) || incomingLikeCount < 0 ||
|
||||||
|
!Number.isFinite(incomingCommentCount) || incomingCommentCount < 0 ||
|
||||||
|
!Number.isFinite(outgoingLikeCount) || outgoingLikeCount < 0 ||
|
||||||
|
!Number.isFinite(outgoingCommentCount) || outgoingCommentCount < 0 ||
|
||||||
|
!Number.isFinite(totalCount) || totalCount < 0 ||
|
||||||
|
!Number.isFinite(latestTime) || latestTime < 0
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items.push({
|
||||||
|
name,
|
||||||
|
incomingLikeCount: Math.floor(incomingLikeCount),
|
||||||
|
incomingCommentCount: Math.floor(incomingCommentCount),
|
||||||
|
outgoingLikeCount: Math.floor(outgoingLikeCount),
|
||||||
|
outgoingCommentCount: Math.floor(outgoingCommentCount),
|
||||||
|
totalCount: Math.floor(totalCount),
|
||||||
|
latestTime: Math.floor(latestTime),
|
||||||
|
direction,
|
||||||
|
behavior
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
count: Math.floor(count),
|
||||||
|
items,
|
||||||
|
loadedPosts: Math.floor(loadedPosts),
|
||||||
|
totalPosts: totalPosts === null
|
||||||
|
? null
|
||||||
|
: (Number.isFinite(totalPosts) && totalPosts >= 0 ? Math.floor(totalPosts) : null),
|
||||||
|
computedAt: Math.floor(computedAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getExportSessionMutualFriendsCache(scopeKey: string): Promise<ExportSessionMutualFriendsCacheItem | null> {
|
||||||
|
if (!scopeKey) return null
|
||||||
|
const value = await config.get(CONFIG_KEYS.EXPORT_SESSION_MUTUAL_FRIENDS_CACHE_MAP)
|
||||||
|
if (!value || typeof value !== 'object') return null
|
||||||
|
const rawMap = value as Record<string, unknown>
|
||||||
|
const rawItem = rawMap[scopeKey]
|
||||||
|
if (!rawItem || typeof rawItem !== 'object') return null
|
||||||
|
|
||||||
|
const rawUpdatedAt = (rawItem as Record<string, unknown>).updatedAt
|
||||||
|
const rawMetrics = (rawItem as Record<string, unknown>).metrics
|
||||||
|
if (!rawMetrics || typeof rawMetrics !== 'object') return null
|
||||||
|
|
||||||
|
const metrics: Record<string, ExportSessionMutualFriendsCacheEntry> = {}
|
||||||
|
for (const [sessionIdRaw, metricRaw] of Object.entries(rawMetrics as Record<string, unknown>)) {
|
||||||
|
const sessionId = String(sessionIdRaw || '').trim()
|
||||||
|
if (!sessionId) continue
|
||||||
|
const metric = normalizeExportSessionMutualFriendsCacheEntry(metricRaw)
|
||||||
|
if (!metric) continue
|
||||||
|
metrics[sessionId] = metric
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updatedAt: typeof rawUpdatedAt === 'number' && Number.isFinite(rawUpdatedAt) ? rawUpdatedAt : 0,
|
||||||
|
metrics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setExportSessionMutualFriendsCache(
|
||||||
|
scopeKey: string,
|
||||||
|
metrics: Record<string, ExportSessionMutualFriendsCacheEntry>
|
||||||
|
): Promise<void> {
|
||||||
|
if (!scopeKey) return
|
||||||
|
const current = await config.get(CONFIG_KEYS.EXPORT_SESSION_MUTUAL_FRIENDS_CACHE_MAP)
|
||||||
|
const map = current && typeof current === 'object'
|
||||||
|
? { ...(current as Record<string, unknown>) }
|
||||||
|
: {}
|
||||||
|
|
||||||
|
const normalized: Record<string, ExportSessionMutualFriendsCacheEntry> = {}
|
||||||
|
for (const [sessionIdRaw, metricRaw] of Object.entries(metrics || {})) {
|
||||||
|
const sessionId = String(sessionIdRaw || '').trim()
|
||||||
|
if (!sessionId) continue
|
||||||
|
const metric = normalizeExportSessionMutualFriendsCacheEntry(metricRaw)
|
||||||
|
if (!metric) continue
|
||||||
|
normalized[sessionId] = metric
|
||||||
|
}
|
||||||
|
|
||||||
|
map[scopeKey] = {
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
metrics: normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
await config.set(CONFIG_KEYS.EXPORT_SESSION_MUTUAL_FRIENDS_CACHE_MAP, map)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clearExportSessionMutualFriendsCache(scopeKey: string): Promise<void> {
|
||||||
|
if (!scopeKey) return
|
||||||
|
const current = await config.get(CONFIG_KEYS.EXPORT_SESSION_MUTUAL_FRIENDS_CACHE_MAP)
|
||||||
|
if (!current || typeof current !== 'object') return
|
||||||
|
const map = { ...(current as Record<string, unknown>) }
|
||||||
|
if (!(scopeKey in map)) return
|
||||||
|
delete map[scopeKey]
|
||||||
|
await config.set(CONFIG_KEYS.EXPORT_SESSION_MUTUAL_FRIENDS_CACHE_MAP, map)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSnsPageCache(scopeKey: string): Promise<SnsPageCacheItem | null> {
|
export async function getSnsPageCache(scopeKey: string): Promise<SnsPageCacheItem | null> {
|
||||||
if (!scopeKey) return null
|
if (!scopeKey) return null
|
||||||
const value = await config.get(CONFIG_KEYS.SNS_PAGE_CACHE_MAP)
|
const value = await config.get(CONFIG_KEYS.SNS_PAGE_CACHE_MAP)
|
||||||
|
|||||||
Reference in New Issue
Block a user