本文字数:约3200字 | 预计阅读时间:13分钟

前置知识:建议先学习本系列前五篇,特别是网络请求与数据处理

实战价值:掌握本地存储,让应用具备离线能力,提升用户体验

系列导航:本文是《鸿蒙开发系列》第6篇,下篇将讲解服务卡片开发实战

一、本地存储概述

在移动应用开发中,本地数据存储是必不可少的功能。它可以让应用:

  1. 实现离线使用:在没有网络的情况下正常使用

  2. 提升性能:减少网络请求,快速加载数据

  3. 保存用户偏好:记住用户的设置和偏好

  4. 缓存数据:临时存储网络数据,减少流量消耗

鸿蒙提供了多种本地存储方案,每种方案都有其适用的场景。

二、Preferences:轻量级键值存储

Preferences是鸿蒙提供的轻量级存储方案,适合存储简单的键值对数据,如用户设置、登录状态等。

2.1 基本使用

typescript

import dataPreferences from '@ohos.data.preferences';

class PreferencesManager {
  private preferences: dataPreferences.Preferences | null = null;
  
  // 初始化Preferences
  async init(context: any, name: string = 'myPreferences') {
    try {
      this.preferences = await dataPreferences.getPreferences(context, name);
      console.log('Preferences初始化成功');
    } catch (error) {
      console.error('Preferences初始化失败:', error);
    }
  }
  
  // 保存数据
  async set(key: string, value: dataPreferences.ValueType) {
    if (!this.preferences) {
      throw new Error('Preferences未初始化');
    }
    
    try {
      await this.preferences.put(key, value);
      await this.preferences.flush(); // 持久化到磁盘
      return true;
    } catch (error) {
      console.error('保存数据失败:', error);
      return false;
    }
  }
  
  // 获取数据
  async get(key: string, defaultValue: dataPreferences.ValueType = '') {
    if (!this.preferences) {
      throw new Error('Preferences未初始化');
    }
    
    try {
      const value = await this.preferences.get(key, defaultValue);
      return value;
    } catch (error) {
      console.error('获取数据失败:', error);
      return defaultValue;
    }
  }
  
  // 删除数据
  async delete(key: string) {
    if (!this.preferences) {
      throw new Error('Preferences未初始化');
    }
    
    try {
      await this.preferences.delete(key);
      await this.preferences.flush();
      return true;
    } catch (error) {
      console.error('删除数据失败:', error);
      return false;
    }
  }
  
  // 清空所有数据
  async clear() {
    if (!this.preferences) {
      throw new Error('Preferences未初始化');
    }
    
    try {
      await this.preferences.clear();
      await this.preferences.flush();
      return true;
    } catch (error) {
      console.error('清空数据失败:', error);
      return false;
    }
  }
}

// 全局实例
export const preferencesManager = new PreferencesManager();

2.2 在应用中使用

typescript

@Entry
@Component
struct SettingsPage {
  @State username: string = '';
  @State rememberMe: boolean = false;
  @State fontSize: number = 16;
  
  async aboutToAppear() {
    // 初始化Preferences
    await preferencesManager.init(getContext(this));
    
    // 加载保存的设置
    this.username = (await preferencesManager.get('username', '')) as string;
    this.rememberMe = (await preferencesManager.get('rememberMe', false)) as boolean;
    this.fontSize = (await preferencesManager.get('fontSize', 16)) as number;
  }
  
  async saveSettings() {
    await preferencesManager.set('username', this.username);
    await preferencesManager.set('rememberMe', this.rememberMe);
    await preferencesManager.set('fontSize', this.fontSize);
    
    // 显示保存成功提示
    prompt.showToast({ message: '设置已保存' });
  }
  
  build() {
    Column({ space: 20 }) {
      Text('应用设置')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      // 用户名设置
      Column({ space: 8 }) {
        Text('用户名')
          .fontSize(16)
          .fontColor('#333333')
        
        TextInput({ placeholder: '请输入用户名' })
          .width('100%')
          .height(48)
          .fontSize(this.fontSize)
          .value(this.username)
          .onChange((value: string) => {
            this.username = value;
          })
      }
      
      // 记住我选项
      Row({ space: 10 }) {
        Checkbox()
          .select(this.rememberMe)
          .onChange((checked: boolean) => {
            this.rememberMe = checked;
          })
        
        Text('记住登录状态')
          .fontSize(16)
      }
      
      // 字体大小设置
      Column({ space: 8 }) {
        Text(`字体大小:${this.fontSize}`)
          .fontSize(16)
        
        Slider({
          value: this.fontSize,
          min: 12,
          max: 24,
          step: 1,
          style: SliderStyle.InSet
        })
          .width('100%')
          .onChange((value: number) => {
            this.fontSize = value;
          })
      }
      
      // 保存按钮
      Button('保存设置')
        .width('100%')
        .height(50)
        .fontSize(18)
        .onClick(() => {
          this.saveSettings();
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

三、关系型数据库:SQLite

对于需要复杂查询和事务支持的数据存储,鸿蒙提供了关系型数据库(基于SQLite)的支持。

3.1 数据库管理类

typescript

import relationalStore from '@ohos.data.relationalStore';

// 定义用户表结构
const USER_TABLE = 'users';
const USER_SCHEMA = {
  tableName: USER_TABLE,
  columns: [
    { fieldName: 'id', columnName: 'id', type: relationalStore.ValueType.INTEGER, isPrimaryKey: true },
    { fieldName: 'name', columnName: 'name', type: relationalStore.ValueType.STRING },
    { fieldName: 'email', columnName: 'email', type: relationalStore.ValueType.STRING },
    { fieldName: 'age', columnName: 'age', type: relationalStore.ValueType.INTEGER },
    { fieldName: 'createdAt', columnName: 'created_at', type: relationalStore.ValueType.INTEGER },
    { fieldName: 'updatedAt', columnName: 'updated_at', type: relationalStore.ValueType.INTEGER }
  ]
};

class DatabaseManager {
  private rdbStore: relationalStore.RdbStore | null = null;
  
  // 初始化数据库
  async init(context: any) {
    const config: relationalStore.StoreConfig = {
      name: 'myApp.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };
    
    try {
      this.rdbStore = await relationalStore.getRdbStore(context, config);
      
      // 创建用户表
      await this.createTable(USER_SCHEMA);
      
      console.log('数据库初始化成功');
    } catch (error) {
      console.error('数据库初始化失败:', error);
    }
  }
  
  // 创建表
  private async createTable(schema: any) {
    if (!this.rdbStore) return;
    
    const sql = `
      CREATE TABLE IF NOT EXISTS ${schema.tableName} (
        ${schema.columns.map((col: any) => 
          `${col.columnName} ${this.getSqlType(col.type)} ${col.isPrimaryKey ? 'PRIMARY KEY' : ''}`
        ).join(', ')}
      )
    `;
    
    await this.rdbStore.executeSql(sql);
  }
  
  // 类型映射
  private getSqlType(type: relationalStore.ValueType): string {
    switch (type) {
      case relationalStore.ValueType.INTEGER: return 'INTEGER';
      case relationalStore.ValueType.STRING: return 'TEXT';
      case relationalStore.ValueType.FLOAT: return 'REAL';
      case relationalStore.ValueType.BLOB: return 'BLOB';
      default: return 'TEXT';
    }
  }
  
  // 插入用户
  async insertUser(user: { name: string, email: string, age: number }) {
    if (!this.rdbStore) return false;
    
    const valueBucket: relationalStore.ValuesBucket = {
      'name': user.name,
      'email': user.email,
      'age': user.age,
      'created_at': Date.now(),
      'updated_at': Date.now()
    };
    
    try {
      const rowId = await this.rdbStore.insert(USER_TABLE, valueBucket);
      return rowId > 0;
    } catch (error) {
      console.error('插入用户失败:', error);
      return false;
    }
  }
  
  // 查询所有用户
  async getAllUsers(): Promise<any[]> {
    if (!this.rdbStore) return [];
    
    const predicates = new relationalStore.RdbPredicates(USER_TABLE);
    predicates.orderByAsc('created_at');
    
    try {
      const resultSet = await this.rdbStore.query(predicates, [
        'id', 'name', 'email', 'age', 'created_at', 'updated_at'
      ]);
      
      const users: any[] = [];
      while (resultSet.goToNextRow()) {
        users.push({
          id: resultSet.getDouble(resultSet.getColumnIndex('id')),
          name: resultSet.getString(resultSet.getColumnIndex('name')),
          email: resultSet.getString(resultSet.getColumnIndex('email')),
          age: resultSet.getDouble(resultSet.getColumnIndex('age')),
          createdAt: resultSet.getDouble(resultSet.getColumnIndex('created_at')),
          updatedAt: resultSet.getDouble(resultSet.getColumnIndex('updated_at'))
        });
      }
      
      resultSet.close();
      return users;
    } catch (error) {
      console.error('查询用户失败:', error);
      return [];
    }
  }
  
  // 更新用户
  async updateUser(id: number, updates: Partial<{ name: string, email: string, age: number }>) {
    if (!this.rdbStore) return false;
    
    const predicates = new relationalStore.RdbPredicates(USER_TABLE);
    predicates.equalTo('id', id);
    
    const valueBucket: relationalStore.ValuesBucket = {
      'updated_at': Date.now()
    };
    
    if (updates.name) valueBucket['name'] = updates.name;
    if (updates.email) valueBucket['email'] = updates.email;
    if (updates.age) valueBucket['age'] = updates.age;
    
    try {
      const rowsUpdated = await this.rdbStore.update(valueBucket, predicates);
      return rowsUpdated > 0;
    } catch (error) {
      console.error('更新用户失败:', error);
      return false;
    }
  }
  
  // 删除用户
  async deleteUser(id: number) {
    if (!this.rdbStore) return false;
    
    const predicates = new relationalStore.RdbPredicates(USER_TABLE);
    predicates.equalTo('id', id);
    
    try {
      const rowsDeleted = await this.rdbStore.delete(predicates);
      return rowsDeleted > 0;
    } catch (error) {
      console.error('删除用户失败:', error);
      return false;
    }
  }
  
  // 事务操作
  async batchInsertUsers(users: Array<{ name: string, email: string, age: number }>) {
    if (!this.rdbStore) return false;
    
    try {
      await this.rdbStore.beginTransaction();
      
      for (const user of users) {
        await this.insertUser(user);
      }
      
      await this.rdbStore.commit();
      return true;
    } catch (error) {
      await this.rdbStore.rollback();
      console.error('批量插入失败:', error);
      return false;
    }
  }
}

export const databaseManager = new DatabaseManager();

3.2 用户管理界面

typescript

@Entry
@Component
struct UserManagerPage {
  @State users: any[] = [];
  @State loading: boolean = false;
  @State newUserName: string = '';
  @State newUserEmail: string = '';
  @State newUserAge: string = '';
  
  async aboutToAppear() {
    await databaseManager.init(getContext(this));
    this.loadUsers();
  }
  
  async loadUsers() {
    this.loading = true;
    this.users = await databaseManager.getAllUsers();
    this.loading = false;
  }
  
  async addUser() {
    if (!this.newUserName.trim() || !this.newUserEmail.trim()) {
      prompt.showToast({ message: '请填写完整信息' });
      return;
    }
    
    const age = parseInt(this.newUserAge) || 0;
    const success = await databaseManager.insertUser({
      name: this.newUserName,
      email: this.newUserEmail,
      age: age
    });
    
    if (success) {
      prompt.showToast({ message: '用户添加成功' });
      this.newUserName = '';
      this.newUserEmail = '';
      this.newUserAge = '';
      this.loadUsers();
    }
  }
  
  async deleteUser(id: number) {
    const success = await databaseManager.deleteUser(id);
    if (success) {
      prompt.showToast({ message: '用户删除成功' });
      this.loadUsers();
    }
  }
  
  build() {
    Column({ space: 20 }) {
      Text('用户管理')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      // 添加用户表单
      Column({ space: 12 }) {
        TextInput({ placeholder: '姓名' })
          .width('100%')
          .height(48)
          .value(this.newUserName)
          .onChange((value: string) => {
            this.newUserName = value;
          })
        
        TextInput({ placeholder: '邮箱' })
          .width('100%')
          .height(48)
          .value(this.newUserEmail)
          .onChange((value: string) => {
            this.newUserEmail = value;
          })
        
        TextInput({ placeholder: '年龄' })
          .width('100%')
          .height(48)
          .value(this.newUserAge)
          .onChange((value: string) => {
            this.newUserAge = value;
          })
        
        Button('添加用户')
          .width('100%')
          .height(50)
          .onClick(() => {
            this.addUser();
          })
      }
      .padding(20)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .shadow({ radius: 4, color: '#10000000' })
      
      // 用户列表
      if (this.loading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else {
        List({ space: 12 }) {
          ForEach(this.users, (user: any) => {
            ListItem() {
              UserItem({ 
                user: user,
                onDelete: () => {
                  this.deleteUser(user.id);
                }
              })
            }
          })
        }
        .layoutWeight(1)
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }
}

@Component
struct UserItem {
  @Prop user: any;
  @Prop onDelete: () => void;
  
  build() {
    Row({ space: 15 }) {
      Column({ space: 5 }) {
        Text(this.user.name)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
        
        Text(this.user.email)
          .fontSize(14)
          .fontColor('#666666')
        
        Text(`年龄:${this.user.age}`)
          .fontSize(12)
          .fontColor('#999999')
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
      
      Button('删除')
        .width(80)
        .height(36)
        .fontSize(14)
        .backgroundColor('#FF3B30')
        .onClick(() => {
          this.onDelete();
        })
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({ radius: 4, color: '#10000000' })
  }
}

四、文件存储

对于需要存储大型文件或自定义格式的数据,可以使用文件系统API。

4.1 文件管理类

typescript

import fs from '@ohos.file.fs';
import fileIo from '@ohos.fileio';

class FileManager {
  // 获取应用文件目录
  private getFilesDir(context: any): string {
    return context.filesDir;
  }
  
  // 获取缓存目录
  private getCacheDir(context: any): string {
    return context.cacheDir;
  }
  
  // 检查文件是否存在
  async fileExists(context: any, filePath: string): Promise<boolean> {
    try {
      await fileIo.access(filePath);
      return true;
    } catch {
      return false;
    }
  }
  
  // 写入文本文件
  async writeTextFile(context: any, fileName: string, content: string): Promise<boolean> {
    const filePath = `${this.getFilesDir(context)}/${fileName}`;
    
    try {
      const file = await fileIo.open(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
      await fileIo.write(file.fd, content);
      await fileIo.close(file.fd);
      return true;
    } catch (error) {
      console.error('写入文件失败:', error);
      return false;
    }
  }
  
  // 读取文本文件
  async readTextFile(context: any, fileName: string): Promise<string> {
    const filePath = `${this.getFilesDir(context)}/${fileName}`;
    
    try {
      const file = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
      const stat = await fileIo.stat(filePath);
      const buffer = new ArrayBuffer(stat.size);
      await fileIo.read(file.fd, buffer);
      await fileIo.close(file.fd);
      
      const textDecoder = new util.TextDecoder('utf-8');
      return textDecoder.decode(buffer);
    } catch (error) {
      console.error('读取文件失败:', error);
      return '';
    }
  }
  
  // 保存JSON数据
  async saveJson(context: any, fileName: string, data: any): Promise<boolean> {
    const jsonString = JSON.stringify(data, null, 2);
    return this.writeTextFile(context, fileName, jsonString);
  }
  
  // 读取JSON数据
  async loadJson<T = any>(context: any, fileName: string): Promise<T | null> {
    const content = await this.readTextFile(context, fileName);
    if (!content) return null;
    
    try {
      return JSON.parse(content) as T;
    } catch (error) {
      console.error('解析JSON失败:', error);
      return null;
    }
  }
  
  // 保存图片到缓存
  async saveImageToCache(context: any, imageUrl: string, imageData: ArrayBuffer): Promise<string> {
    const cacheDir = this.getCacheDir(context);
    const fileName = `${Date.now()}_${imageUrl.split('/').pop()}`;
    const filePath = `${cacheDir}/${fileName}`;
    
    try {
      const file = await fileIo.open(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
      await fileIo.write(file.fd, imageData);
      await fileIo.close(file.fd);
      return filePath;
    } catch (error) {
      console.error('保存图片失败:', error);
      return '';
    }
  }
  
  // 获取缓存大小
  async getCacheSize(context: any): Promise<number> {
    const cacheDir = this.getCacheDir(context);
    
    try {
      const dir = await fileIo.opendir(cacheDir);
      let totalSize = 0;
      
      let dirent = await dir.read();
      while (dirent) {
        const filePath = `${cacheDir}/${dirent.name}`;
        const stat = await fileIo.stat(filePath);
        totalSize += stat.size;
        dirent = await dir.read();
      }
      
      await dir.close();
      return totalSize;
    } catch (error) {
      console.error('获取缓存大小失败:', error);
      return 0;
    }
  }
  
  // 清空缓存
  async clearCache(context: any): Promise<boolean> {
    const cacheDir = this.getCacheDir(context);
    
    try {
      const dir = await fileIo.opendir(cacheDir);
      
      let dirent = await dir.read();
      while (dirent) {
        const filePath = `${cacheDir}/${dirent.name}`;
        await fileIo.unlink(filePath);
        dirent = await dir.read();
      }
      
      await dir.close();
      return true;
    } catch (error) {
      console.error('清空缓存失败:', error);
      return false;
    }
  }
}

export const fileManager = new FileManager();

4.2 文件操作界面

typescript

@Entry
@Component
struct FileManagerPage {
  @State notes: string = '';
  @State cacheSize: string = '0 KB';
  @State loading: boolean = false;
  
  private context = getContext(this);
  
  async aboutToAppear() {
    this.loadNotes();
    this.loadCacheSize();
  }
  
  async loadNotes() {
    const savedNotes = await fileManager.readTextFile(this.context, 'notes.txt');
    this.notes = savedNotes || '';
  }
  
  async saveNotes() {
    const success = await fileManager.writeTextFile(this.context, 'notes.txt', this.notes);
    if (success) {
      prompt.showToast({ message: '保存成功' });
    }
  }
  
  async loadCacheSize() {
    this.loading = true;
    const sizeInBytes = await fileManager.getCacheSize(this.context);
    this.cacheSize = this.formatFileSize(sizeInBytes);
    this.loading = false;
  }
  
  async clearAllCache() {
    const success = await fileManager.clearCache(this.context);
    if (success) {
      prompt.showToast({ message: '缓存已清除' });
      this.loadCacheSize();
    }
  }
  
  private formatFileSize(bytes: number): string {
    if (bytes === 0) return '0 B';
    
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }
  
  build() {
    Column({ space: 20 }) {
      Text('文件管理')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      // 笔记编辑器
      Column({ space: 12 }) {
        Text('个人笔记')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        
        TextArea({ placeholder: '请输入笔记内容...' })
          .width('100%')
          .height(200)
          .value(this.notes)
          .onChange((value: string) => {
            this.notes = value;
          })
        
        Button('保存笔记')
          .width('100%')
          .height(50)
          .onClick(() => {
            this.saveNotes();
          })
      }
      .padding(20)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .shadow({ radius: 4, color: '#10000000' })
      
      // 缓存管理
      Column({ space: 12 }) {
        Text('缓存管理')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        
        Row() {
          Text('缓存大小:')
            .fontSize(16)
          
          if (this.loading) {
            LoadingProgress()
              .width(20)
              .height(20)
          } else {
            Text(this.cacheSize)
              .fontSize(16)
              .fontColor('#007DFF')
          }
        }
        
        Button('清除缓存')
          .width('100%')
          .height(50)
          .backgroundColor('#FF9500')
          .onClick(() => {
            this.clearAllCache();
          })
      }
      .padding(20)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .shadow({ radius: 4, color: '#10000000' })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }
}

五、数据加密与安全

对于敏感数据,需要进行加密存储。

5.1 数据加密工具

typescript

import cryptoFramework from '@ohos.security.cryptoFramework';

class CryptoManager {
  // 生成密钥
  async generateKey(): Promise<string> {
    try {
      const symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');
      const symKey = await symKeyGenerator.generateSymKey();
      const keyData = await symKey.getEncoded();
      
      // 转换为base64字符串
      const base64Key = this.arrayBufferToBase64(keyData.data);
      return base64Key;
    } catch (error) {
      console.error('生成密钥失败:', error);
      throw error;
    }
  }
  
  // 加密数据
  async encrypt(text: string, keyBase64: string): Promise<string> {
    try {
      const keyData = this.base64ToArrayBuffer(keyBase64);
      
      const symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');
      const symKey = await symKeyGenerator.convertKey({ data: keyData });
      
      const cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7');
      await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, null);
      
      const input = { data: this.stringToArrayBuffer(text) };
      const encrypted = await cipher.doFinal(input);
      
      return this.arrayBufferToBase64(encrypted.data);
    } catch (error) {
      console.error('加密失败:', error);
      throw error;
    }
  }
  
  // 解密数据
  async decrypt(encryptedBase64: string, keyBase64: string): Promise<string> {
    try {
      const keyData = this.base64ToArrayBuffer(keyBase64);
      const encryptedData = this.base64ToArrayBuffer(encryptedBase64);
      
      const symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');
      const symKey = await symKeyGenerator.convertKey({ data: keyData });
      
      const cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7');
      await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, null);
      
      const input = { data: encryptedData };
      const decrypted = await cipher.doFinal(input);
      
      return this.arrayBufferToString(decrypted.data);
    } catch (error) {
      console.error('解密失败:', error);
      throw error;
    }
  }
  
  // 工具方法
  private arrayBufferToBase64(buffer: ArrayBuffer): string {
    let binary = '';
    const bytes = new Uint8Array(buffer);
    for (let i = 0; i < bytes.byteLength; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
  }
  
  private base64ToArrayBuffer(base64: string): ArrayBuffer {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i);
    }
    return bytes.buffer;
  }
  
  private stringToArrayBuffer(str: string): ArrayBuffer {
    const encoder = new TextEncoder();
    return encoder.encode(str).buffer;
  }
  
  private arrayBufferToString(buffer: ArrayBuffer): string {
    const decoder = new TextDecoder('utf-8');
    return decoder.decode(buffer);
  }
}

export const cryptoManager = new CryptoManager();

5.2 安全存储示例

typescript

@Entry
@Component
struct SecureStoragePage {
  @State secretKey: string = '';
  @State plainText: string = '';
  @State encryptedText: string = '';
  @State decryptedText: string = '';
  @State loading: boolean = false;
  
  async generateKey() {
    this.loading = true;
    try {
      this.secretKey = await cryptoManager.generateKey();
      await preferencesManager.set('secret_key', this.secretKey);
      prompt.showToast({ message: '密钥已生成并保存' });
    } catch (error) {
      console.error('生成密钥失败:', error);
    }
    this.loading = false;
  }
  
  async loadKey() {
    this.secretKey = (await preferencesManager.get('secret_key', '')) as string;
  }
  
  async encryptData() {
    if (!this.secretKey) {
      prompt.showToast({ message: '请先生成或加载密钥' });
      return;
    }
    
    if (!this.plainText) {
      prompt.showToast({ message: '请输入要加密的文本' });
      return;
    }
    
    this.loading = true;
    try {
      this.encryptedText = await cryptoManager.encrypt(this.plainText, this.secretKey);
      prompt.showToast({ message: '加密成功' });
    } catch (error) {
      console.error('加密失败:', error);
      prompt.showToast({ message: '加密失败' });
    }
    this.loading = false;
  }
  
  async decryptData() {
    if (!this.secretKey) {
      prompt.showToast({ message: '请先加载密钥' });
      return;
    }
    
    if (!this.encryptedText) {
      prompt.showToast({ message: '请输入要解密的文本' });
      return;
    }
    
    this.loading = true;
    try {
      this.decryptedText = await cryptoManager.decrypt(this.encryptedText, this.secretKey);
      prompt.showToast({ message: '解密成功' });
    } catch (error) {
      console.error('解密失败:', error);
      prompt.showToast({ message: '解密失败' });
    }
    this.loading = false;
  }
  
  aboutToAppear() {
    preferencesManager.init(getContext(this));
    this.loadKey();
  }
  
  build() {
    Column({ space: 20 }) {
      Text('安全存储')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      // 密钥管理
      Column({ space: 12 }) {
        Text('密钥管理')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        
        Row({ space: 10 }) {
          Button('生成密钥')
            .width(120)
            .height(40)
            .onClick(() => {
              this.generateKey();
            })
          
          Button('加载密钥')
            .width(120)
            .height(40)
            .onClick(() => {
              this.loadKey();
            })
        }
        
        if (this.secretKey) {
          Text('密钥已加载')
            .fontSize(14)
            .fontColor('#34C759')
        }
      }
      .padding(20)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .shadow({ radius: 4, color: '#10000000' })
      
      // 加密区域
      Column({ space: 12 }) {
        Text('加密')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        
        TextInput({ placeholder: '请输入要加密的文本' })
          .width('100%')
          .height(80)
          .value(this.plainText)
          .onChange((value: string) => {
            this.plainText = value;
          })
        
        Button('加密')
          .width('100%')
          .height(50)
          .onClick(() => {
            this.encryptData();
          })
        
        if (this.encryptedText) {
          Text('加密结果:')
            .fontSize(14)
          Text(this.encryptedText)
            .fontSize(12)
            .fontColor('#666666')
            .maxLines(3)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        }
      }
      .padding(20)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .shadow({ radius: 4, color: '#10000000' })
      
      // 解密区域
      Column({ space: 12 }) {
        Text('解密')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        
        TextInput({ placeholder: '请输入要解密的文本' })
          .width('100%')
          .height(80)
          .value(this.encryptedText)
          .onChange((value: string) => {
            this.encryptedText = value;
          })
        
        Button('解密')
          .width('100%')
          .height(50)
          .onClick(() => {
            this.decryptData();
          })
        
        if (this.decryptedText) {
          Text('解密结果:')
            .fontSize(14)
          Text(this.decryptedText)
            .fontSize(14)
            .fontColor('#333333')
        }
      }
      .padding(20)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .shadow({ radius: 4, color: '#10000000' })
      
      if (this.loading) {
        LoadingProgress()
          .width(50)
          .height(50)
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }
}

六、存储方案选择指南

存储方案 适用场景 优点 缺点
Preferences 用户设置、登录状态、简单配置 使用简单、性能好 不适合大量数据、复杂查询
关系型数据库 用户数据、消息记录、复杂数据结构 支持复杂查询、事务 使用相对复杂、需要SQL知识
文件存储 大文件、图片、文档、自定义格式数据 灵活、适合大量数据 需要手动管理文件、无内置查询功能
加密存储 敏感数据(密码、支付信息) 安全性高 性能开销大、需要密钥管理

七、最佳实践

7.1 分层存储策略

typescript

class StorageStrategy {
  // 内存缓存(最快)
  private memoryCache = new Map<string, any>();
  
  // Preferences(中等速度)
  private preferences: PreferencesManager;
  
  // 数据库/文件(最慢,但持久化)
  private database: DatabaseManager;
  private fileManager: FileManager;
  
  constructor() {
    this.preferences = new PreferencesManager();
    this.database = new DatabaseManager();
    this.fileManager = new FileManager();
  }
  
  // 获取数据(多层缓存)
  async getData<T>(key: string): Promise<T | null> {
    // 1. 检查内存缓存
    if (this.memoryCache.has(key)) {
      return this.memoryCache.get(key);
    }
    
    // 2. 检查Preferences
    const prefData = await this.preferences.get(key);
    if (prefData) {
      this.memoryCache.set(key, prefData);
      return prefData as T;
    }
    
    // 3. 检查数据库/文件
    // ... 根据key的类型选择不同的存储方式
    
    return null;
  }
}

7.2 数据同步策略

typescript

class DataSyncManager {
  // 同步本地和远程数据
  async syncData<T>(
    localKey: string,
    remoteFetch: () => Promise<T>,
    shouldUpdate: (local: T, remote: T) => boolean
  ): Promise<T> {
    // 1. 从本地获取数据
    const localData = await this.getLocalData<T>(localKey);
    
    // 2. 从远程获取数据
    let remoteData: T;
    try {
      remoteData = await remoteFetch();
    } catch (error) {
      // 网络失败,返回本地数据
      console.warn('网络请求失败,使用本地数据');
      return localData;
    }
    
    // 3. 比较数据,决定是否更新
    if (!localData || shouldUpdate(localData, remoteData)) {
      await this.saveLocalData(localKey, remoteData);
      return remoteData;
    }
    
    return localData;
  }
}

八、总结与下期预告

8.1 本文要点回顾

  1. Preferences:轻量级键值存储,适合简单配置

  2. 关系型数据库:复杂数据存储,支持SQL查询

  3. 文件存储:大文件和自定义格式数据存储

  4. 数据加密:敏感数据的安全存储

  5. 存储策略:如何根据场景选择合适的存储方案

8.2 下期预告:《鸿蒙开发之:服务卡片开发实战》

下篇文章将深入讲解:

  • 服务卡片的基本概念和使用场景

  • 卡片布局和样式设计

  • 卡片数据更新和交互

  • 卡片配置和部署

  • 实战:创建各种类型的服务卡片


动手挑战

任务1:实现备忘录应用
要求:

  • 使用数据库存储备忘录数据

  • 支持备忘录的增删改查

  • 支持备忘录分类和标签

  • 实现数据备份和恢复功能

任务2:实现图片收藏应用
要求:

  • 使用文件系统保存图片

  • 使用数据库保存图片元数据

  • 支持图片的分类和搜索

  • 实现图片加密存储

任务3:实现数据同步框架
要求:

  • 支持本地和远程数据同步

  • 实现冲突解决策略

  • 支持离线编辑和自动同步

  • 实现数据版本控制

将你的代码分享到评论区,我会挑选优秀实现进行详细点评!


常见问题解答

Q:Preferences和数据库有什么区别?
A:Preferences适合存储简单的键值对,如用户设置;数据库适合存储结构化数据,支持复杂查询。

Q:如何选择文件存储还是数据库存储?
A:如果是结构化数据且需要查询,用数据库;如果是文件、图片或自定义格式,用文件存储。

Q:加密存储会影响性能吗?
A:会有一定性能影响,但对于敏感数据,安全比性能更重要。可以对关键数据进行加密,非敏感数据明文存储。

Q:如何备份用户数据?
A:可以将数据库或重要文件打包,保存到云端或设备外部存储。


PS:现在HarmonyOS应用开发者认证正在做活动,初级和高级都可以免费学习及考试,赶快加入班级学习啦:【注意,考试只能从此唯一链接进入】
https://developer.huawei.com/consumer/cn/training/classDetail/33f85412dc974764831435dc1c03427c?type=1?ha_source=hmosclass&ha_sourceld=89000248
 

版权声明:本文为《鸿蒙开发系列》第6篇,原创文章,转载请注明出处。

标签:#HarmonyOS #鸿蒙开发 #本地存储 #数据库 #文件操作 #数据加密 #华为开发者

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐