数据持久化 #
一、持久化策略 #
1.1 持久化类型 #
| 类型 | 说明 | 存储位置 |
|---|---|---|
| 应用配置 | 应用设置 | app-config 目录 |
| 用户数据 | 用户生成内容 | app-data 目录 |
| 缓存数据 | 临时数据 | app-cache 目录 |
| 日志数据 | 运行日志 | app-log 目录 |
1.2 持久化架构 #
text
┌─────────────────────────────────────────────────────────────┐
│ 持久化架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 应用数据 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ │
│ │ │ 配置数据 │ │ 用户数据 │ │ 缓存数据 ││ │
│ │ │ config/ │ │ data/ │ │ cache/ ││ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘│ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存储方式 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ │
│ │ │ JSON │ │ SQLite │ │ Binary ││ │
│ │ │ Files │ │ Database │ │ Files ││ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘│ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
二、配置持久化 #
2.1 配置管理器 #
typescript
// persistence/configManager.ts
import { Store } from '@tauri-apps/plugin-store';
import { appConfigDir, join } from '@tauri-apps/api/path';
interface AppConfig {
theme: 'light' | 'dark';
language: string;
fontSize: number;
window: {
width: number;
height: number;
x?: number;
y?: number;
maximized: boolean;
};
recentFiles: string[];
}
export class ConfigManager {
private store: Store | null = null;
private configPath: string | null = null;
private defaultConfig: AppConfig = {
theme: 'light',
language: 'en',
fontSize: 14,
window: {
width: 800,
height: 600,
maximized: false,
},
recentFiles: [],
};
async init(): Promise<void> {
const configDir = await appConfigDir();
this.configPath = await join(configDir, 'config.json');
this.store = new Store(this.configPath);
await this.store.load();
}
async get<K extends keyof AppConfig>(key: K): Promise<AppConfig[K]> {
if (!this.store) await this.init();
const value = await this.store!.get<AppConfig[K]>(key);
return value ?? this.defaultConfig[key];
}
async set<K extends keyof AppConfig>(key: K, value: AppConfig[K]): Promise<void> {
if (!this.store) await this.init();
await this.store!.set(key, value);
await this.store!.save();
}
async getAll(): Promise<AppConfig> {
if (!this.store) await this.init();
const config: AppConfig = { ...this.defaultConfig };
for (const key of Object.keys(this.defaultConfig) as (keyof AppConfig)[]) {
const value = await this.store!.get<AppConfig[typeof key]>(key);
if (value !== null) {
config[key] = value;
}
}
return config;
}
async reset(): Promise<void> {
if (!this.store) await this.init();
await this.store!.clear();
await this.store!.save();
}
}
export const configManager = new ConfigManager();
2.2 使用配置管理器 #
typescript
import { configManager } from './persistence/configManager';
// 初始化
await configManager.init();
// 读取配置
const theme = await configManager.get('theme');
const fontSize = await configManager.get('fontSize');
// 写入配置
await configManager.set('theme', 'dark');
await configManager.set('fontSize', 16);
// 获取所有配置
const config = await configManager.getAll();
三、用户数据持久化 #
3.1 用户数据管理器 #
typescript
// persistence/userDataManager.ts
import { writeTextFile, readTextFile, exists, mkdir } from '@tauri-apps/plugin-fs';
import { appDataDir, join } from '@tauri-apps/api/path';
interface UserData {
profile: UserProfile;
preferences: UserPreferences;
history: HistoryItem[];
}
interface UserProfile {
name: string;
avatar?: string;
createdAt: string;
updatedAt: string;
}
interface UserPreferences {
notifications: boolean;
autoSave: boolean;
syncEnabled: boolean;
}
interface HistoryItem {
id: string;
action: string;
timestamp: string;
data: unknown;
}
export class UserDataManager {
private dataPath: string | null = null;
async init(): Promise<void> {
const dataDir = await appDataDir();
this.dataPath = await join(dataDir, 'user-data.json');
if (!(await exists(dataDir))) {
await mkdir(dataDir, { recursive: true });
}
}
async load(): Promise<UserData> {
if (!this.dataPath) await this.init();
if (!(await exists(this.dataPath!))) {
return this.getDefaultData();
}
const content = await readTextFile(this.dataPath!);
return JSON.parse(content);
}
async save(data: UserData): Promise<void> {
if (!this.dataPath) await this.init();
const content = JSON.stringify(data, null, 2);
await writeTextFile(this.dataPath!, content);
}
async update<K extends keyof UserData>(
key: K,
value: UserData[K]
): Promise<void> {
const data = await this.load();
data[key] = value;
await this.save(data);
}
private getDefaultData(): UserData {
return {
profile: {
name: 'User',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
preferences: {
notifications: true,
autoSave: true,
syncEnabled: false,
},
history: [],
};
}
}
export const userDataManager = new UserDataManager();
3.2 历史记录管理 #
typescript
// persistence/historyManager.ts
import { userDataManager } from './userDataManager';
export class HistoryManager {
private maxItems = 100;
async add(action: string, data: unknown): Promise<void> {
const userData = await userDataManager.load();
const item: HistoryItem = {
id: crypto.randomUUID(),
action,
timestamp: new Date().toISOString(),
data,
};
userData.history.unshift(item);
// 限制历史记录数量
if (userData.history.length > this.maxItems) {
userData.history = userData.history.slice(0, this.maxItems);
}
await userDataManager.save(userData);
}
async getAll(): Promise<HistoryItem[]> {
const userData = await userDataManager.load();
return userData.history;
}
async getByAction(action: string): Promise<HistoryItem[]> {
const userData = await userDataManager.load();
return userData.history.filter((item) => item.action === action);
}
async clear(): Promise<void> {
const userData = await userDataManager.load();
userData.history = [];
await userDataManager.save(userData);
}
}
export const historyManager = new HistoryManager();
四、缓存管理 #
4.1 缓存管理器 #
typescript
// persistence/cacheManager.ts
import { writeTextFile, readTextFile, exists, mkdir, remove } from '@tauri-apps/plugin-fs';
import { appCacheDir, join } from '@tauri-apps/api/path';
interface CacheItem<T> {
data: T;
expiresAt: number;
createdAt: number;
}
export class CacheManager {
private cacheDir: string | null = null;
async init(): Promise<void> {
this.cacheDir = await appCacheDir();
if (!(await exists(this.cacheDir))) {
await mkdir(this.cacheDir, { recursive: true });
}
}
async get<T>(key: string): Promise<T | null> {
if (!this.cacheDir) await this.init();
const filePath = await join(this.cacheDir!, `${key}.json`);
if (!(await exists(filePath))) {
return null;
}
const content = await readTextFile(filePath);
const item: CacheItem<T> = JSON.parse(content);
// 检查是否过期
if (item.expiresAt && Date.now() > item.expiresAt) {
await remove(filePath);
return null;
}
return item.data;
}
async set<T>(key: string, data: T, ttlSeconds?: number): Promise<void> {
if (!this.cacheDir) await this.init();
const filePath = await join(this.cacheDir!, `${key}.json`);
const item: CacheItem<T> = {
data,
createdAt: Date.now(),
expiresAt: ttlSeconds ? Date.now() + ttlSeconds * 1000 : 0,
};
await writeTextFile(filePath, JSON.stringify(item));
}
async delete(key: string): Promise<void> {
if (!this.cacheDir) await this.init();
const filePath = await join(this.cacheDir!, `${key}.json`);
if (await exists(filePath)) {
await remove(filePath);
}
}
async clear(): Promise<void> {
if (!this.cacheDir) await this.init();
const { readDir } = await import('@tauri-apps/plugin-fs');
const entries = await readDir(this.cacheDir!);
for (const entry of entries) {
const filePath = await join(this.cacheDir!, entry.name);
await remove(filePath);
}
}
async clearExpired(): Promise<void> {
if (!this.cacheDir) await this.init();
const { readDir } = await import('@tauri-apps/plugin-fs');
const entries = await readDir(this.cacheDir!);
for (const entry of entries) {
const filePath = await join(this.cacheDir!, entry.name);
const content = await readTextFile(filePath);
const item: CacheItem<unknown> = JSON.parse(content);
if (item.expiresAt && Date.now() > item.expiresAt) {
await remove(filePath);
}
}
}
}
export const cacheManager = new CacheManager();
五、自动保存 #
5.1 自动保存管理器 #
typescript
// persistence/autoSave.ts
import { debounce } from 'lodash-es';
export class AutoSaveManager<T> {
private data: T;
private saveFunction: (data: T) => Promise<void>;
private debouncedSave: () => void;
private interval: ReturnType<typeof setInterval> | null = null;
constructor(
initialData: T,
saveFunction: (data: T) => Promise<void>,
delay: number = 1000
) {
this.data = initialData;
this.saveFunction = saveFunction;
this.debouncedSave = debounce(this.save.bind(this), delay);
}
getData(): T {
return this.data;
}
update(updater: (data: T) => void): void {
updater(this.data);
this.debouncedSave();
}
private async save(): Promise<void> {
await this.saveFunction(this.data);
}
startPeriodicSave(intervalMs: number = 60000): void {
this.interval = setInterval(() => {
this.save();
}, intervalMs);
}
stopPeriodicSave(): void {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
async forceSave(): Promise<void> {
this.debouncedSave.cancel();
await this.save();
}
}
5.2 使用自动保存 #
typescript
import { AutoSaveManager } from './persistence/autoSave';
import { userDataManager } from './persistence/userDataManager';
const autoSave = new AutoSaveManager(
await userDataManager.load(),
async (data) => {
await userDataManager.save(data);
},
2000
);
// 启动定期保存
autoSave.startPeriodicSave(60000);
// 更新数据(自动保存)
autoSave.update((data) => {
data.profile.name = 'New Name';
});
// 强制保存
await autoSave.forceSave();
六、数据同步 #
6.1 同步管理器 #
typescript
// persistence/syncManager.ts
import { userDataManager } from './userDataManager';
interface SyncStatus {
lastSyncAt: string | null;
pending: boolean;
error: string | null;
}
export class SyncManager {
private status: SyncStatus = {
lastSyncAt: null,
pending: false,
error: null,
};
async sync(): Promise<void> {
if (this.status.pending) {
return;
}
this.status.pending = true;
this.status.error = null;
try {
const localData = await userDataManager.load();
// 这里实现与服务器的同步逻辑
// const serverData = await fetchServerData();
// const mergedData = mergeData(localData, serverData);
// await pushToServer(mergedData);
// await userDataManager.save(mergedData);
this.status.lastSyncAt = new Date().toISOString();
} catch (error) {
this.status.error = String(error);
throw error;
} finally {
this.status.pending = false;
}
}
getStatus(): SyncStatus {
return { ...this.status };
}
async enableAutoSync(intervalMs: number = 300000): Promise<() => void> {
const interval = setInterval(() => {
this.sync().catch(console.error);
}, intervalMs);
return () => clearInterval(interval);
}
}
export const syncManager = new SyncManager();
七、数据备份 #
7.1 备份管理器 #
typescript
// persistence/backupManager.ts
import { copyFile, mkdir, exists, readDir } from '@tauri-apps/plugin-fs';
import { appDataDir, join } from '@tauri-apps/api/path';
export class BackupManager {
private backupDir: string | null = null;
private maxBackups = 10;
async init(): Promise<void> {
const dataDir = await appDataDir();
this.backupDir = await join(dataDir, 'backups');
if (!(await exists(this.backupDir))) {
await mkdir(this.backupDir, { recursive: true });
}
}
async createBackup(): Promise<string> {
if (!this.backupDir) await this.init();
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = await join(this.backupDir!, `backup-${timestamp}`);
await mkdir(backupPath, { recursive: true });
// 备份数据库
const dbPath = await join(await appDataDir(), 'app.db');
if (await exists(dbPath)) {
await copyFile(dbPath, await join(backupPath, 'app.db'));
}
// 备份配置
const configPath = await join(await appDataDir(), 'config.json');
if (await exists(configPath)) {
await copyFile(configPath, await join(backupPath, 'config.json'));
}
// 清理旧备份
await this.cleanOldBackups();
return backupPath;
}
private async cleanOldBackups(): Promise<void> {
const entries = await readDir(this.backupDir!);
if (entries.length > this.maxBackups) {
const sorted = entries.sort((a, b) => a.name.localeCompare(b.name));
const toDelete = sorted.slice(0, entries.length - this.maxBackups);
for (const entry of toDelete) {
const path = await join(this.backupDir!, entry.name);
await this.removeRecursive(path);
}
}
}
private async removeRecursive(path: string): Promise<void> {
const { remove } = await import('@tauri-apps/plugin-fs');
await remove(path, { recursive: true });
}
async listBackups(): Promise<string[]> {
if (!this.backupDir) await this.init();
const entries = await readDir(this.backupDir!);
return entries
.filter((e) => e.isDirectory)
.map((e) => e.name)
.sort()
.reverse();
}
}
export const backupManager = new BackupManager();
八、最佳实践 #
8.1 数据版本控制 #
typescript
interface VersionedData<T> {
version: number;
data: T;
migrated: boolean;
}
async function migrateData<T>(
data: VersionedData<T>,
migrations: Array<(data: T) => T>,
targetVersion: number
): Promise<VersionedData<T>> {
let currentData = data.data;
for (let i = data.version; i < targetVersion; i++) {
currentData = migrations[i](currentData);
}
return {
version: targetVersion,
data: currentData,
migrated: true,
};
}
8.2 数据验证 #
typescript
import { z } from 'zod';
const ConfigSchema = z.object({
theme: z.enum(['light', 'dark']),
language: z.string(),
fontSize: z.number().min(10).max(24),
});
async function validateConfig(data: unknown) {
return ConfigSchema.parse(data);
}
九、总结 #
9.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 配置持久化 | 使用 tauri-plugin-store |
| 用户数据 | JSON 文件或数据库 |
| 缓存管理 | 带过期时间的缓存 |
| 自动保存 | 防抖 + 定期保存 |
| 数据备份 | 定期备份 + 清理 |
9.2 下一步 #
现在你已经掌握了数据持久化,接下来让我们学习 系统对话框,了解如何使用原生对话框!
最后更新:2026-03-28