Skip to content

Commit dd2a056

Browse files
committed
perf(storage): P2-14 采集片段拆分独立表(addSegment 原子追加,旧数据回退兼容)
1 parent 03de962 commit dd2a056

5 files changed

Lines changed: 45 additions & 16 deletions

File tree

‎client/src/lib/storage/captureStore.ts‎

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/**
22
* 采集会话存储
33
*
4-
* @ai-context: segments 以整数组覆盖方式追加(Dexie 不支持数组原子
5-
* push),高频调用 addSegment 存在读改写竞态,调用方需串行化。
4+
* @ai-context: P2-14——segments 拆至独立表 windowCaptureSegments:addSegment
5+
* 由原来的整数组读改写(Dexie 不支持原子 push,O(n)+高频竞态)变为单行原子
6+
* 追加。getSegments 优先读独立表,空则回退旧会话内嵌 segments(向后兼容,
7+
* 无数据迁移)。
68
*/
79
import { db } from './database';
810
import type { WindowCapture, ExtractedSegment } from '@/types/models';
@@ -41,20 +43,13 @@ export const captureStore = {
4143
},
4244

4345
/**
44-
* 添加提取片段到会话
45-
* 将 segment 追加到 segments 数组
46+
* 添加提取片段到会话(P2-14:写入独立表,原子追加,无读改写竞态)
4647
*/
4748
async addSegment(
4849
sessionId: string,
4950
segment: ExtractedSegment
5051
): Promise<void> {
51-
const session = await db.windowCaptures.get(sessionId);
52-
if (!session) {
53-
throw new Error(`[captureStore] Session not found: ${sessionId}`);
54-
}
55-
await db.windowCaptures.update(sessionId, {
56-
segments: [...session.segments, segment],
57-
});
52+
await db.windowCaptureSegments.add({ ...segment, sessionId });
5853
},
5954

6055
/**
@@ -69,17 +64,20 @@ export const captureStore = {
6964
},
7065

7166
/**
72-
* 获取会话的所有片段
67+
* 获取会话的所有片段(P2-14:独立表优先,空则回退旧会话内嵌 segments)
7368
*/
7469
async getSegments(sessionId: string): Promise<ExtractedSegment[]> {
70+
const rows = await db.windowCaptureSegments.where('sessionId').equals(sessionId).toArray();
71+
if (rows.length > 0) return rows;
7572
const session = await db.windowCaptures.get(sessionId);
7673
return session?.segments ?? [];
7774
},
7875

7976
/**
80-
* 删除会话(及其关联的所有片段)
77+
* 删除会话(及其关联的所有片段:独立表 + 内嵌)
8178
*/
8279
async deleteSession(id: string): Promise<void> {
80+
await db.windowCaptureSegments.where('sessionId').equals(id).delete();
8381
await db.windowCaptures.delete(id);
8482
},
8583

‎client/src/lib/storage/database.ts‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import type {
44
FlashcardDeck, Flashcard, FlashcardReview,
55
FeynmanNote, FeynmanSummary, FeynmanWeakPoint,
66
OperationLog, AppSettings, SyncConflict, OfflineQueueItem,
7-
StudyCheckIn, Achievement, PomodoroGoal, WindowCapture,
7+
StudyCheckIn, Achievement, PomodoroGoal, WindowCapture, WindowCaptureSegment,
88
Consent, UserProfile, Inspiration, SearchIndexEntry, RitualRecord
99
} from '@/types/models';
10+
import type { DeepSeaDiscovery, CoralRecord, StreakState } from '@/features/retention/types';
1011
import type { ClassroomNote } from './classroomNoteStore';
1112
import type { CRDTDocRecord, CRDTChangeRecord } from '@/lib/sync/crdtEngine';
1213

@@ -49,6 +50,10 @@ export class EntropyDecreaseDatabase extends Dexie {
4950
crdtChanges!: Table<CRDTChangeRecord, number>;
5051
ritualRecords!: Table<RitualRecord, string>;
5152
pomodoroPresets!: Table<PomodoroPreset, string>;
53+
deepSeaDiscoveries!: Table<DeepSeaDiscovery, string>;
54+
coralEcosystem!: Table<CoralRecord, string>;
55+
streakState!: Table<StreakState, string>;
56+
windowCaptureSegments!: Table<WindowCaptureSegment, string>;
5257

5358
constructor() {
5459
// 数据库名 'keban' 不可修改(存量用户数据),见文件头 @ai-context
@@ -277,6 +282,20 @@ export class EntropyDecreaseDatabase extends Dexie {
277282
this.version(17).stores({
278283
pomodoroPresets: 'id, sortOrder, builtin, createdAt',
279284
});
285+
286+
// v0.29.0: 留存机制 — 深海发现 / 珊瑚生态 / 防断裂 streak
287+
this.version(18).stores({
288+
deepSeaDiscoveries: 'id, type, rarity, discoveredAt, sourceType',
289+
coralEcosystem: 'id, type, health, plantedAt, depth',
290+
streakState: 'id, lastActiveDate',
291+
});
292+
293+
// P2-14: 采集片段独立表 — 将 segments 从 windowCaptures 内嵌数组拆出,
294+
// addSegment 由全量读改写(O(n)+竞态)变为原子追加;旧会话内嵌 segments
295+
// 仍可读(captureStore.getSegments 回退),无需数据迁移。
296+
this.version(19).stores({
297+
windowCaptureSegments: 'id, sessionId, timestamp',
298+
});
280299
}
281300
}
282301

‎client/src/lib/storage/fullExport.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
PomodoroSession, PomodoroSettings, Note, NoteFolder,
55
FlashcardReview, FeynmanNote, FeynmanSummary, FeynmanWeakPoint,
66
OperationLog, AppSettings, StudyCheckIn, Achievement,
7-
PomodoroGoal, SyncConflict, OfflineQueueItem, WindowCapture,
7+
PomodoroGoal, SyncConflict, OfflineQueueItem, WindowCapture, WindowCaptureSegment,
88
RitualRecord,
99
} from '@/types/models';
1010

@@ -42,6 +42,8 @@ export interface ExportData {
4242
syncConflicts: SyncConflict[];
4343
offlineQueue: OfflineQueueItem[];
4444
windowCaptures: WindowCapture[];
45+
// P2-14: 采集片段独立表(旧备份无此字段时导入安全跳过)
46+
windowCaptureSegments: WindowCaptureSegment[];
4547
// v0.26.0 A1 补全:
4648
ritualRecords: RitualRecord[];
4749
};
@@ -54,6 +56,7 @@ const EXPORT_TABLES = [
5456
'feynmanNotes', 'feynmanSummaries', 'feynmanWeakPoints',
5557
'operationLog', 'appSettings', 'studyCheckIns', 'achievements',
5658
'pomodoroGoals', 'syncConflicts', 'offlineQueue', 'windowCaptures',
59+
'windowCaptureSegments',
5760
'ritualRecords',
5861
] as const;
5962

‎client/src/pages/settings/hooks/useClearData.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function useClearData() {
4444
db.operationLog, db.appSettings,
4545
db.studyCheckIns, db.achievements,
4646
db.pomodoroGoals, db.syncConflicts, db.offlineQueue,
47-
db.windowCaptures,
47+
db.windowCaptures, db.windowCaptureSegments,
4848
], async () => {
4949
await db.pomodoroSessions.clear();
5050
await db.pomodoroSettings.clear();
@@ -64,6 +64,7 @@ export function useClearData() {
6464
await db.syncConflicts.clear();
6565
await db.offlineQueue.clear();
6666
await db.windowCaptures.clear();
67+
await db.windowCaptureSegments.clear();
6768
});
6869
localStorage.clear();
6970
toast({ type: 'success', message: '所有数据已清除,即将刷新' });

‎client/src/types/capture.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ export interface ExtractedSegment {
3131
metadata?: SegmentMetadata; // 附加元数据
3232
}
3333

34+
/**
35+
* 采集片段独立表行(P2-14)——在 ExtractedSegment 基础上绑定所属会话 ID,
36+
* 存储于 windowCaptureSegments 表('id, sessionId, timestamp')。
37+
*/
38+
export interface WindowCaptureSegment extends ExtractedSegment {
39+
sessionId: string; // 所属采集会话 ID
40+
}
41+
3442
// 片段元数据
3543
export interface SegmentMetadata {
3644
startTime?: number; // 音频/视频起始时间(秒)

0 commit comments

Comments
 (0)