数据持久化 #

一、持久化策略 #

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