OpenHarmonyOS 鸿蒙开发关系型数据库简单使用

下载文件数据库储存操作:配合super_fast_file_trans使用
super_fast_file_trans

import { relationalStore, ValueType } from "@kit.ArkData";
import { common } from "@kit.AbilityKit";
import { LogUtil } from "./LogUtil";
import { FileUtil } from "./FileUtil";
import { GlobalContext } from "./GlobalContext";
import { DownloadManager } from "@hadss/super_fast_file_trans";
import { url } from "@kit.ArkTS";
import { PreferencesUtil } from "./PreferencesUtil";
import { Constant } from "../model/Constant";

const STORE_CONFIG: relationalStore.StoreConfig = {
  name: 'sfft_download.db',
  securityLevel: relationalStore.SecurityLevel.S1,
  encrypt: false
};

export class RDBUtils {
  private static instance: RDBUtils | null = null;
  // 文件下载使用relationalStore,如需操作其他数据库单独初始化
  private downloadRdbStore: relationalStore.RdbStore | undefined;

  public static getInstance(): RDBUtils {
    if (!RDBUtils .instance) {
      RDBUtils .instance = new RDBUtils ();
    }
    return RDBUtils .instance;
  }

  public async initDownloadRdbStore(context: common.Context) {
    try {
      this.downloadRdbStore =
        await relationalStore.getRdbStore(context, STORE_CONFIG);
      this.updateDownloadUrl()
    } catch (err) {
      LogUtil.error(`initDownloadRdbStore error,code: ${err.code}, message: ${err.message}`);
    }
  }

  public getDownloadRdbStore(){
    return this.downloadRdbStore
  }

  public async clearDownloadCache(): Promise<void> {
    try {
      // 1. 获取下载任务数据
      const downloadFile = await this.getDownloadFileNames();
      // 2. 获取本地下载目录中的文件
      const context = GlobalContext.getContext();
      const pickerPath = `${GlobalContext.getDownloadPathByPicker()}`;
      const localDownloadFile: string[] = []
      if (FileUtil.accessSync(pickerPath + 'apk/')) {
        const apkFiles = FileUtil.listFileSync(pickerPath + 'apk/', { recursion: true }) || [];
        localDownloadFile.push(...apkFiles)
      }
      if (FileUtil.accessSync(pickerPath + 'resource/')) {
        const resourceFiles = FileUtil.listFileSync(pickerPath + 'resource/', { recursion: true }) || [];
        localDownloadFile.push(...resourceFiles)
      }
      // 3. 如果没有本地文件,清理所有下载记录
      if (localDownloadFile.length === 0) {
        await DownloadManager.getInstance().cleanAll(context);
        await DownloadManager.getInstance().init(context)
        return;
      }

      // 4. 对比数据库和本地文件,清理不存在的记录
      const cleanupPromises: Promise<void>[] = [];
      for (const fileName of Array.from(downloadFile.keys())) {
        if (!localDownloadFile.includes(`/${fileName}`) && downloadFile.get(fileName)) {
          // 并行执行数据库清理操作
          let resultSet = await this.getData('sfft_download_block', 'host_id', downloadFile.get(fileName)!!)
          resultSet.goToNextRow()
          const content_length = resultSet.getLong(resultSet.getColumnIndex("content_length"));
          const content_offset = resultSet.getLong(resultSet.getColumnIndex("current_offset"));
          if (content_length === content_offset) {
            cleanupPromises.push(
              DownloadRDBUtils.getInstance().deleteByKv('*************', 'file_name', fileName)
            );
            cleanupPromises.push(
              DownloadRDBUtils.getInstance().deleteByKv('sfft_download_block', 'host_id', downloadFile.get(fileName)!!)
            );
          }
        }
      }
      // 等待所有清理操作完成
      if (cleanupPromises.length > 0) {
        await Promise.all(cleanupPromises);
      }
    } catch (error) {
      LogUtil.error('clearDownloadCache error:', error);
    }
  }

  // 删除数据库
  public deleteDB(context: common.Context, dbName: string) {
    relationalStore.deleteRdbStore(context, dbName, (err) => {
      if (err) {
        LogUtil.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
        return;
      }
      LogUtil.info('Succeeded in deleting RdbStore ==> ' + dbName);
    });
  }

  async deleteByKv(tableName: string, key: string, value: ValueType) {
    let predicates = new relationalStore.RdbPredicates(tableName);
    predicates.equalTo(key, value)
    if (this.downloadRdbStore != undefined) {
      try {
        let rows = this.downloadRdbStore.deleteSync(predicates);
        LogUtil.info(`${tableName}, Delete rows: ${rows}} `)
      } catch (err) {
        LogUtil.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
      }
    }
  }


  // 向数据库中插入数据
  async insertData(tableName: string, valueBucket: relationalStore.ValuesBucket) {
    if (this.downloadRdbStore != undefined) {
      this.downloadRdbStore.insert(tableName, valueBucket, (err, rowId: number) => {
        if (err) {
          LogUtil.error(`Failed to insert data. Code:${err.code}, message:${err.message}`);
          return;
        }
        LogUtil.info(`Succeeded in inserting data. rowId:${rowId}`);
      })
    }
  }


  // 对数据进行修改
  async updateData(tableName: string, valueBucket: relationalStore.ValuesBucket, key: string, value: ValueType) {
    let predicates = new relationalStore.RdbPredicates(tableName); // 创建表tableName的predicates
    predicates.equalTo(key, value); // 匹配表tableName中key为val的字段
    if (this.downloadRdbStore != undefined) {
      this.downloadRdbStore.update(valueBucket, predicates, (err, rowId: number) => {
        if (err) {
          LogUtil.error(`Failed to insert data. Code:${err.code}, message:${err.message}`);
          return;
        }
        LogUtil.info(`Succeeded in inserting data. rowId:${rowId}`);
      })
    }
  }

  async updateDownloadUrl() {
    let downloadUrls = await this.queryDownloadUrl()
    if (downloadUrls.length > 0) {
      downloadUrls.forEach(downloadUrl => {
        try {
          let urlObject = url.URL.parseURL(downloadUrl);
          let ip = PreferencesUtil.getStringSync("ip", Constant.IP);
          let port = PreferencesUtil.getStringSync("port", Constant.PORT);
          if (urlObject.host !== ip || urlObject.port !== port) {
            urlObject.host = ip
            urlObject.port = port
            const valueBucket: relationalStore.ValuesBucket = {
              'URL': urlObject.toString()
            };
            this.updateData('*************', valueBucket, 'url', downloadUrl)
          }
        } catch (error) {
          LogUtil.error(`Invalid URL: ${error.message}`);
        }
      })

    }
  }


  async queryDownloadUrl() {
    let predicates = new relationalStore.RdbPredicates('*************');
    let downloadUrl: string[] = []
    if (this.downloadRdbStore != undefined) {
      try {
        const resultSet = await this.downloadRdbStore.query(predicates);
        try {
          while (resultSet.goToNextRow()) {
            const url = resultSet.getString(resultSet.getColumnIndex("url"));
            downloadUrl.push(url)
          }
        } catch (err) {
          LogUtil.error(`queryDownloadUrl failed, code is ${err.code},message is ${err.message}`);
        } finally {
          // 释放数据集的内存,若不释放可能会引起fd泄露与内存泄露
          resultSet.close();
        }
      } catch (err) {
        LogUtil.error(`Failed to queryDownloadUrl data. Code:${err.code}, message:${err.message}`);
      }
    }
    return Promise.resolve(downloadUrl);
  }


  async getDownloadFileNames(tableName: string = '*************'): Promise<Map<string, number>> {
    let predicates = new relationalStore.RdbPredicates(tableName);
    let fileNameMap: Map<string, number> = new Map()
    if (this.downloadRdbStore != undefined) {
      try {
        const resultSet = await this.downloadRdbStore.query(predicates);
        try {
          while (resultSet.goToNextRow()) {
            const id = resultSet.getLong(resultSet.getColumnIndex("id"));
            const name = resultSet.getString(resultSet.getColumnIndex("file_name"));
            fileNameMap.set(name, id)
          }
        } catch (err) {
          LogUtil.error(`Query failed, code is ${err.code},message is ${err.message}`);
        } finally {
          // 释放数据集的内存,若不释放可能会引起fd泄露与内存泄露
          resultSet.close();
        }
      } catch (err) {
        LogUtil.error(`Failed to getDownloadFileNames data. Code:${err.code}, message:${err.message}`);
      }
    }
    return Promise.resolve(fileNameMap);
  }

  async getData(tableName: string, key: string, val: ValueType,
    atomArr: Array<string> = ['*']): Promise<relationalStore.ResultSet> {
    let predicates = new relationalStore.RdbPredicates(tableName);
    predicates.equalTo(key, val);
    if (this.downloadRdbStore != undefined) {
      return new Promise((resolve, reject) => {
        this.downloadRdbStore?.query(predicates, atomArr, (err, resultSet) => {
          if (err) {
            LogUtil.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
            reject(err);
            return;
          }
          resolve(resultSet);
          LogUtil.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
        })
      })
    }
    return Promise.reject('downloadRdbStore is undefined')
  }
}

为关系型数据库动态添加列:

import { relationalStore } from "@kit.ArkData";

export class DatabaseManager {
  /**
   * 动态添加可为空的列
   * @param store RdbStore实例
   * @param tableName 表名
   * @param columnDef 列定义
   */
  async addNullableColumn(
    store: relationalStore.RdbStore,
    tableName: string,
    columnDef: ColumnDefinition
  ): Promise<boolean> {

    try {
      // 1. 检查表是否存在
      if (!await this.tableExists(store, tableName)) {
        console.error(` djj djj Table ${tableName} does not exist`);
        return false;
      }

      // 2. 检查列是否已存在
      if (await this.columnExists(store, tableName, columnDef.columnName)) {
        console.info(` djj  djj djj Column ${columnDef.columnName} already exists in table ${tableName}`);
        return true;
      }

      // 3. 构建 ALTER TABLE 语句
      let alterSql = `ALTER TABLE ${tableName} ADD COLUMN ${columnDef.columnName} ${columnDef.columnType}`;

      // 添加约束条件
      if (columnDef.notNull) {
        alterSql += ' NOT NULL';
      }

      if (columnDef.defaultValue !== null && columnDef.defaultValue !== undefined) {
        // 处理不同类型的默认值
        if (typeof columnDef.defaultValue === 'string') {
          alterSql += ` DEFAULT '${columnDef.defaultValue}'`;
        } else if (typeof columnDef.defaultValue === 'number') {
          alterSql += ` DEFAULT ${columnDef.defaultValue}`;
        } else if (typeof columnDef.defaultValue === 'boolean') {
          alterSql += ` DEFAULT ${columnDef.defaultValue ? 1 : 0}`;
        } else if (columnDef.defaultValue === null) {
          alterSql += ' DEFAULT NULL';
        }
      } else if (!columnDef.notNull) {
        // 如果不为 NOT NULL 且没有指定默认值,默认可为空
        alterSql += ' DEFAULT NULL';
      }

      console.info(` djj Executing SQL: ${alterSql}`);

      // 4. 执行 SQL
      await store.executeSql(alterSql);

      // 5. 验证列是否添加成功
      const columnAdded = await this.columnExists(store, tableName, columnDef.columnName);
      if (columnAdded) {
        console.info(` djj  djjSuccessfully added nullable column: ${columnDef.columnName}`);
        return true;
      } else {
        console.error(` djj Failed to verify column addition: ${columnDef.columnName}`);
        return false;
      }
    } catch (error) {
      console.error(` djj Error adding column ${columnDef.columnName}:`, error);
    }
    return false;
  }

  /**
   * 检查表是否存在
   */
  private async tableExists(
    store: relationalStore.RdbStore,
    tableName: string
  ): Promise<boolean> {
    const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name=?`;
    const resultSet = await store.querySql(sql, [tableName]);
    const exists = resultSet.rowCount > 0;
    resultSet.close();
    return exists;
  }

  /**
   * 检查列是否存在
   */
  private async columnExists(
    store: relationalStore.RdbStore,
    tableName: string,
    columnName: string
  ): Promise<boolean> {
    const sql = `PRAGMA table_info(${tableName})`;
    const resultSet = await store.querySql(sql);

    let exists = false;
    while (resultSet.goToNextRow()) {
      const name = resultSet.getString(resultSet.getColumnIndex('name'));
      if (name === columnName) {
        exists = true;
        break;
      }
    }
    resultSet.close();
    return exists;
  }

  /**
   * 添加 appIcon 列(可为空)
   */
  async addAppIconColumn(store: relationalStore.RdbStore,): Promise<boolean> {
    const columnDef: ColumnDefinition = {
      columnName: 'appIcon',
      columnType: 'TEXT',
      defaultValue: null, // 默认值为 NULL
      notNull: false      // 允许为空
    };

    return await this.addNullableColumn(store, '**********', columnDef);
  }

  /**
   * 批量添加多个可为空的列
   */
  async addMultipleNullableColumns(store: relationalStore.RdbStore,columns: ColumnDefinition[]): Promise<boolean> {
    let allSuccess = true;
    for (const columnDef of columns) {
      try {
        const success = await this.addNullableColumn(
          store,
          '**********',
          columnDef
        );
        if (!success) {
          allSuccess = false;
          console.error(` djj Failed to add column: ${columnDef.columnName}`);
        }
      } catch (error) {
        console.error(` djj Error adding column ${columnDef.columnName}:`, error);
        allSuccess = false;
      }
    }

    return allSuccess;
  }
}

// 列定义接口
interface ColumnDefinition {
  columnName: string;
  columnType: 'TEXT' | 'INTEGER' | 'REAL' | 'BLOB';
  defaultValue?: string | number | boolean | null;
  notNull?: boolean;
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐