鸿蒙应用开发实战【27】— DatabaseService 数据库初始化

鸿蒙应用开发实战【27】--- DatabaseService 数据库初始化

本文是「号码助手全栈开发系列」第 27 篇,持续更新中...

开源社区:https://openharmonycrossplatform.csdn.net


前言

上一篇我们了解了 RDB 的整体架构和核心 API,现在进入实战------创建 DatabaseService。这是数据库访问的入口,负责初始化 RdbStore、维护单例、执行建表 SQL 和管理数据库版本。

本篇涵盖:getRdbStore 初始化流程与 StoreConfig、单例模式 getInstance / getStore、CREATE TABLE IF NOT EXISTS 建表、3 张核心表(card / app_binding / sms_candidate)的字段设计、版本管理 onUpgrade 回调、完整 DatabaseService 类代码。


一、DatabaseService 类设计

DatabaseService 的核心职责是:

  1. 创建/打开数据库 --- 调用 relationalStore.getRdbStore() 初始化 RdbStore
  2. 单例管理 --- 全局唯一实例,避免重复打开数据库连接
  3. 建表与升级 --- 在 onUpgrade 回调中执行 CREATE TABLE 和 ALTER TABLE
  4. 提供 RdbStore --- 供 DAO 层获取操作对象

完整类结构

typescript 复制代码
import { relationalStore } from '@kit.ArkData';
import { UIAbilityContext } from '@kit.AbilityKit';

export class DatabaseService {
  // 单例实例
  private static instance: DatabaseService;
  // RdbStore 实例
  private store: relationalStore.RdbStore | null = null;

  // 私有构造,外部不可 new
  private constructor() {}

  // 获取单例
  static getInstance(): DatabaseService {
    if (!DatabaseService.instance) {
      DatabaseService.instance = new DatabaseService();
    }
    return DatabaseService.instance;
  }
}

二、数据库初始化流程

2.1 StoreConfig 配置

初始化 RdbStore 需要准备 StoreConfig

typescript 复制代码
private getStoreConfig(): relationalStore.StoreConfig {
  return {
    name: 'haomazhushou.db',
    securityLevel: relationalStore.SecurityLevel.S2,
    encrypt: true,
  };
}

字段说明:

  • name --- 数据库文件名,最终路径为 {app沙盒}/app_database/haomazhushou.db
  • securityLevel --- S2 级加密保护个人信息
  • encrypt --- 开启加密(S2 默认已加密,显式声明更清晰)

2.2 getStore 方法

typescript 复制代码
async getStore(context: UIAbilityContext): Promise<relationalStore.RdbStore> {
  if (this.store) {
    return this.store;
  }

  const config = this.getStoreConfig();
  this.store = await relationalStore.getRdbStore(context, config);
  return this.store;
}

这段代码实现了 延迟初始化------只有首次调用时才创建 RdbStore,后续调用直接返回缓存的实例。

2.3 完整的初始化方法

加入 onUpgrade 回调,在建表/升级时自动执行 SQL:

typescript 复制代码
async getStore(context: UIAbilityContext): Promise<relationalStore.RdbStore> {
  if (this.store) {
    return this.store;
  }

  const config: relationalStore.StoreConfig = {
    name: 'haomazhushou.db',
    securityLevel: relationalStore.SecurityLevel.S2,
    encrypt: true,
  };

  this.store = await relationalStore.getRdbStore(context, config, async (): Promise<void> => {
    await this.createTables();
  });

  return this.store;
}

private async createTables(): Promise<void> {
  if (!this.store) return;

  // 建表操作在 getRdbStore 的 onUpgrade 回调中自动执行
  await this.store.executeSql(CREATE_TABLE_CARD);
  await this.store.executeSql(CREATE_TABLE_APP_BINDING);
  await this.store.executeSql(CREATE_TABLE_SMS_CANDIDATE);

  console.info('Database tables created successfully');
}

📌 注意getRdbStore 的第三个参数是 onUpgrade 回调,它在数据库版本升高时触发。首次安装时版本从 0→1,也会触发该回调。


三、三张核心表设计

3.1 card --- 卡号表

存储用户的手机号卡信息:

typescript 复制代码
const CREATE_TABLE_CARD: string = `
  CREATE TABLE IF NOT EXISTS card (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    label TEXT NOT NULL,
    phone TEXT NOT NULL,
    operator TEXT,
    color TEXT DEFAULT '#4F7CFF',
    sort_order INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now', 'localtime')),
    updated_at TEXT DEFAULT (datetime('now', 'localtime'))
  );
`;
字段 类型 说明
id INTEGER PK AUTOINCREMENT 主键,自增
label TEXT NOT NULL 卡号标签,如"主卡"、"副卡"、"流量卡"
phone TEXT NOT NULL 手机号码
operator TEXT 运营商,如"中国移动"、"中国联通"、"中国电信"
color TEXT DEFAULT '#4F7CFF' 卡片主题色,十六进制
sort_order INTEGER DEFAULT 0 排序权重,越小越靠前
created_at TEXT 创建时间,默认当前时间
updated_at TEXT 更新时间,默认当前时间

3.2 app_binding --- 应用绑定表

记录每个应用绑定了哪张卡:

typescript 复制代码
const CREATE_TABLE_APP_BINDING: string = `
  CREATE TABLE IF NOT EXISTS app_binding (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    app_name TEXT NOT NULL,
    category TEXT,
    card_id INTEGER REFERENCES card(id),
    status TEXT DEFAULT '使用中',
    remark TEXT,
    icon_key TEXT,
    created_at TEXT DEFAULT (datetime('now', 'localtime')),
    updated_at TEXT DEFAULT (datetime('now', 'localtime'))
  );
`;
字段 类型 说明
id INTEGER PK AUTOINCREMENT 主键,自增
app_name TEXT NOT NULL 应用名称,如"微信"、"支付宝"
category TEXT 应用分类,如"社交"、"支付"、"金融"
card_id INTEGER REFERENCES card(id) 外键,关联 card 表
status TEXT DEFAULT '使用中' 绑定状态
remark TEXT 用户的备注信息
icon_key TEXT 应用图标键值
created_at TEXT 创建时间
updated_at TEXT 更新时间

3.3 sms_candidate --- 短信候选表

存储从短信中提取的待处理绑定记录:

typescript 复制代码
const CREATE_TABLE_SMS_CANDIDATE: string = `
  CREATE TABLE IF NOT EXISTS sms_candidate (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    sms_sender TEXT,
    sms_body TEXT,
    app_name TEXT,
    card_label TEXT,
    source TEXT DEFAULT 'sms',
    imported INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now', 'localtime'))
  );
`;
字段 类型 说明
id INTEGER PK AUTOINCREMENT 主键,自增
sms_sender TEXT 短信发送者号码/名称
sms_body TEXT 短信原文
app_name TEXT 从短信中解析出的应用名
card_label TEXT 从短信中解析出的卡号标签
source TEXT DEFAULT 'sms' 来源:'sms' / 'paste' / 'manual'
imported INTEGER DEFAULT 0 导入标记:0=未导入,1=已导入
created_at TEXT 创建时间

四、版本管理与升级

4.1 版本号策略

typescript 复制代码
private static readonly DB_VERSION: number = 1;
  • 初始版本号设为 1,对应首次建表
  • 每次数据库结构变更,版本号 +1
  • 已有数据不会被删除,只在 onUpgrade 中执行 ALTER TABLE

4.2 onUpgrade 升级回调

typescript 复制代码
this.store = await relationalStore.getRdbStore(context, {
  name: 'haomazhushou.db',
  securityLevel: relationalStore.SecurityLevel.S2,
  encrypt: true,
}, async (oldVersion: number, newVersion: number): Promise<void> => {
  console.info(`Database upgrade from ${oldVersion} to ${newVersion}`);

  if (oldVersion < 1) {
    // 版本 0→1:初始建表
    await this.createTables();
  }

  // 后续版本升级示例:
  // if (oldVersion < 2) {
  //   await this.store?.executeSql('ALTER TABLE card ADD COLUMN ...');
  // }
  // if (oldVersion < 3) {
  //   await this.store?.executeSql('CREATE TABLE IF NOT EXISTS ...');
  // }
});

⚠️ 关键点 :每个升级步骤必须判断 oldVersion < N,这样用户无论从哪个旧版本升级都能逐步执行到最新。比如用户从 v1 直接跳到 v3,会依次执行 v1→v2 和 v2→v3 的升级。


五、完整 DatabaseService 代码

整合以上所有功能,得到完整的 DatabaseService 类:

typescript 复制代码
import { relationalStore } from '@kit.ArkData';
import { UIAbilityContext } from '@kit.AbilityKit';

const CREATE_TABLE_CARD: string = `
  CREATE TABLE IF NOT EXISTS card (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    label TEXT NOT NULL,
    phone TEXT NOT NULL,
    operator TEXT,
    color TEXT DEFAULT '#4F7CFF',
    sort_order INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now', 'localtime')),
    updated_at TEXT DEFAULT (datetime('now', 'localtime'))
  );
`;

const CREATE_TABLE_APP_BINDING: string = `
  CREATE TABLE IF NOT EXISTS app_binding (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    app_name TEXT NOT NULL,
    category TEXT,
    card_id INTEGER REFERENCES card(id),
    status TEXT DEFAULT '使用中',
    remark TEXT,
    icon_key TEXT,
    created_at TEXT DEFAULT (datetime('now', 'localtime')),
    updated_at TEXT DEFAULT (datetime('now', 'localtime'))
  );
`;

const CREATE_TABLE_SMS_CANDIDATE: string = `
  CREATE TABLE IF NOT EXISTS sms_candidate (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    sms_sender TEXT,
    sms_body TEXT,
    app_name TEXT,
    card_label TEXT,
    source TEXT DEFAULT 'sms',
    imported INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now', 'localtime'))
  );
`;

export class DatabaseService {
  private static instance: DatabaseService;
  private store: relationalStore.RdbStore | null = null;
  private static readonly DB_VERSION: number = 1;

  private constructor() {}

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

  async getStore(context: UIAbilityContext): Promise<relationalStore.RdbStore> {
    if (this.store) {
      return this.store;
    }

    this.store = await relationalStore.getRdbStore(context, {
      name: 'haomazhushou.db',
      securityLevel: relationalStore.SecurityLevel.S2,
      encrypt: true,
    }, async (oldVersion: number, newVersion: number): Promise<void> => {
      console.info(`Upgrade from ${oldVersion} to ${newVersion}`);

      if (oldVersion < 1) {
        await this.createTables();
      }
    });

    return this.store;
  }

  private async createTables(): Promise<void> {
    if (!this.store) return;

    await this.store.executeSql(CREATE_TABLE_CARD);
    await this.store.executeSql(CREATE_TABLE_APP_BINDING);
    await this.store.executeSql(CREATE_TABLE_SMS_CANDIDATE);

    console.info('All tables created');
  }

  // 页面销毁时不需要主动关闭 RdbStore,系统会管理生命周期
}

六、使用示例

在 UIAbility 的 onCreate 中初始化:

typescript 复制代码
import { UIAbility } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  async onCreate(): Promise<void> {
    try {
      const dbService = DatabaseService.getInstance();
      await dbService.getStore(this.context);
      console.info('Database initialized');
    } catch (error) {
      console.error('Database init failed:', error);
    }
  }
}

在页面中获取数据库进行 DAO 操作:

typescript 复制代码
@Entry
@Component
struct HomePage {
  async aboutToAppear(): Promise<void> {
    const store = await DatabaseService.getInstance().getStore(getContext(this));
    // 将 store 传递给 DAO 进行操作
  }
}

小结

本篇完成了数据库初始化的核心工作:

步骤 关键代码
单例模式 static getInstance() + 私有构造
延迟初始化 getStore() 首次调用时创建
StoreConfig name + securityLevel + encrypt
建表 CREATE TABLE IF NOT EXISTS 三张表
版本升级 onUpgrade 回调,逐版本迁移
完整类 DatabaseService 封装全部逻辑

下一篇将深入「CardEntity 数据模型与 CardColor 枚举设计」,定义卡号实体的 TypeScript 接口和 7 色枚举。


如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
拥抱太阳06161 小时前
HarmonyOS 应用开发《掌上英语》第13篇:面向对象 vs 函数式:ArkTS 中的编程范式选择
harmonyos
全堆鸿蒙1 小时前
鸿蒙应用开发实战【34】— 批量操作:batchUpdateStatus 与 deleteMany
华为·harmonyos·鸿蒙系统
研☆香1 小时前
JavaScript 绘制简单不规则图形:三角形与五角星实战教程
开发语言·javascript·ecmascript
ApacheSeaTunnel1 小时前
一套替代四套!同程旅行用 Apache SeaTunnel 搭建多模态统一数据通道
大数据·数据库·ai·开源·数据集成·seatunnel·技术分享·数据同步
段一凡-华北理工大学1 小时前
向量数据库实战:选型、调优与落地~系列文章02:Embedding 嵌入模型选型指南:OpenAI、BGE、Jina、Cohere 横评
大数据·数据库·人工智能·embedding·高炉炼铁·jina·工业智能体
想要成为糕糕手1 小时前
NO.48 旋转图像 —— LeetCode 热题 100 面试导向深度解析
javascript·算法·面试
️学习的小王2 小时前
MySQL 数据库基础操作全解:从建库到增删改查
数据库·mysql·oracle
贾伟康2 小时前
【安心陪诊 Agent v1.1】首页可信设计实战:HTML/CSS 状态卡与急症分流
css·html·harmonyos·ai agent·首页设计
Spider Cat 蜘蛛猫2 小时前
OIDC 联邦 SSO(复旦 OP / 清华 RP)完整全链路(纯标准OIDC、无User Storage SPI、含用户浏览器操作)
数据库·iam