网站规划和建设方案做网站的服务器带宽一般多少
2026/6/20 9:30:51 网站建设 项目流程
网站规划和建设方案,做网站的服务器带宽一般多少,欧米茄官网网站,舟山网站建设免费咨询文章目录 一、 为什么必须是单例#xff1f;二、 DAO 层与 CRUD 实战三、 在 EntryAbility 中激活它四、 总结 在上一篇文章中#xff0c;我们像绘制建筑蓝图一样#xff0c;设计了 Project、Contact、Meeting 三张核心表#xff0c;并理清了它们之间错综复杂的关联关系。我…文章目录一、 为什么必须是单例二、 DAO 层与 CRUD 实战三、 在 EntryAbility 中激活它四、 总结在上一篇文章中我们像绘制建筑蓝图一样设计了 Project、Contact、Meeting 三张核心表并理清了它们之间错综复杂的关联关系。我们还决定抛弃简单的 UserPreferences拥抱强大的 RelationalStore。但这仅仅是纸上谈兵。现在的数据库还只是躺在代码文件里的几行 SQL 字符串它既不能存数据也不能查数据。今天我们要当一次泥瓦匠把这些蓝图变成实实在在的房子。对于大家来说调用 API 打开数据库并不难。难的是如何优雅地管理这个数据库连接。你是否遇到过这样的情况App 运行久了变得卡顿排查发现是数据库连接打开了无数次却没关闭或者 App 发布了 2.0 版本用户更新后一打开就闪退因为你加了新表却忘记处理旧数据的迁移这些都是烂代码埋下的雷。今天我们要写的代码就是要排掉这些雷。我们要封装一个全局单例的 RdbManager它不仅能安全地管理数据库连接还能聪明地处理版本升级。一、 为什么必须是单例在移动端开发中数据库连接RdbStore是一个非常重的对象。它对应着底层的文件句柄和内存缓冲区。如果你在每次点击保存按钮时都去getRdbStore用完又忘了关或者在多个页面同时持有多个连接实例那么很快你的 App 就会因为资源耗尽而崩溃或者遇到文件锁冲突导致写入失败。所以我们必须强制使用单例模式。这意味着无论你的 App 有多少个页面无论你何时何地需要查数据你拿到的永远是同一个数据库连接实例。让我们打开entry/src/main/ets/data/db/RdbManager.ts对上一篇的雏形进行深度改造。这一次我们要加入一个至关重要的逻辑STORE_CONFIG的完整配置。// entry/src/main/ets/data/db/RdbManager.ts import { relationalStore } from kit.ArkData; import { common } from kit.AbilityKit; import { DB_NAME, DB_SECURITY_LEVEL, SQL_CREATE_PROJECT, SQL_CREATE_CONTACT, SQL_CREATE_MEETING } from ./MeetingRdb; export class RdbManager { // 静态私有变量持有唯一实例 private static instance: RdbManager; // 持有数据库操作对象 private rdbStore: relationalStore.RdbStore | null null; // 应用上下文初始化时注入 private context: common.UIAbilityContext | null null; // 这里的版本号非常关键后续升级全靠它 private static readonly DB_VERSION 1; /** * 获取单例 * 无论调用多少次返回的都是同一个 RdbManager 对象 */ public static getInstance(): RdbManager { if (!RdbManager.instance) { RdbManager.instance new RdbManager(); } return RdbManager.instance; } /** * 初始化方法 * 建议在 EntryAbility 的 onCreate 中调用 * 这样 App 一启动上下文就准备好了 */ public init(context: common.UIAbilityContext): void { this.context context; // 可以在这里预热数据库也可以懒加载 } /** * 核心方法获取 RdbStore * 采用了“懒加载”策略只有真正需要查数据时才打开连接 */ public async getRdbStore(): PromiserelationalStore.RdbStore { // 1. 如果已经打开且未关闭直接返回复用 if (this.rdbStore) { return this.rdbStore; } // 2. 检查 Context 是否注入 if (!this.context) { throw new Error([RdbManager] Context is null! Call init() first.); } // 3. 配置数据库参数 const config: relationalStore.StoreConfig { name: DB_NAME, securityLevel: DB_SECURITY_LEVEL, // 开启加密可选商业级项目建议开启这里暂不演示 // encrypt: false }; try { // 4. 调用系统 API 打开或创建数据库 this.rdbStore await relationalStore.getRdbStore(this.context, config); // 5. 检查版本并升级 // 这是一个非常关键的步骤决定了你的 App 能否平滑更新 if (this.rdbStore.version 0) { // version 为 0 说明是全新安装直接建表 await this.initTables(this.rdbStore); this.rdbStore.version RdbManager.DB_VERSION; } else if (this.rdbStore.version RdbManager.DB_VERSION) { // 这里的 version 是旧版本号说明用户是覆盖安装 // 我们需要执行升级逻辑 await this.upgradeTables(this.rdbStore, this.rdbStore.version, RdbManager.DB_VERSION); this.rdbStore.version RdbManager.DB_VERSION; } return this.rdbStore; } catch (e) { console.error([RdbManager] Get Store Failed: ${JSON.stringify(e)}); // 这里可以抛出自定义异常让 UI 层捕获并提示用户 throw new Error(Database init failed); } } /** * 第一次建表逻辑 * 干净利落把所有 CREATE TABLE 语句执行一遍 */ private async initTables(store: relationalStore.RdbStore) { console.info([RdbManager] Initialize tables...); // 使用事务可以加快批量执行速度 store.beginTransaction(); try { await store.executeSql(SQL_CREATE_PROJECT); await store.executeSql(SQL_CREATE_CONTACT); await store.executeSql(SQL_CREATE_MEETING); store.commit(); } catch (e) { console.error([RdbManager] Init tables failed: ${e}); store.rollBack(); throw e; } } /** * 数据库升级逻辑 * 随着 App 版本迭代这里会越来越长 */ private async upgradeTables(store: relationalStore.RdbStore, oldVersion: number, newVersion: number) { console.info([RdbManager] Upgrade DB from ${oldVersion} to ${newVersion}); // 假设未来我们发布了 2.0 版本DB_VERSION 变成了 2 // if (oldVersion 2) { // // 在 Project 表加一个字段 sort_order // await store.executeSql(ALTER TABLE project ADD COLUMN sort_order INTEGER); // } // 假设发布了 3.0 版本 // if (oldVersion 3) { ... } } }通过这段代码我们把复杂的生命周期管理封装在了一个黑盒子里。对于调用者比如 ViewModel来说它只需要await RdbManager.getInstance().getRdbStore()就能拿到一个健康、可用、版本正确的数据库对象完全不需要关心底下发生了什么。二、 DAO 层与 CRUD 实战有了管家我们还需要工人。在架构设计中我们通常会引入DAOData Access Object层或者叫Repository仓库层专门负责具体的增删改查业务。我们不要把 SQL 语句散落在 App 的各个角落。请在entry/src/main/ets/data/db目录下新建ProjectRepo.ts。我们以项目Project这个最基础的实体为例来演示如何实现一个标准的 CRUD 流程。这里有一个痛点数据库 API 需要的是ValuesBucket用于插入和返回ResultSet用于查询而我们的业务层需要的是Project对象。中间的转换代码写起来非常枯燥但必须写而且要写得健壮。我们先写插入Create。TypeScript// entry/src/main/ets/data/db/ProjectRepo.ts import { relationalStore } from kit.ArkData; import { Project } from ../models/Project; import { RdbManager } from ./RdbManager; import { TABLE_PROJECT } from ./MeetingRdb; /** * 插入一个新项目 * param project 业务对象 * returns 插入的行 ID */ export async function insertProject(project: Project): Promisenumber { // 1. 获取数据库实例 const store await RdbManager.getInstance().getRdbStore(); // 2. 构建 ValuesBucket // 这是鸿蒙 RdbStore 要求的特定格式类似于一个 Map // 键是数据库列名值是数据 // 注意我们这里手动映射了驼峰属性到下划线列名 const valueBucket: relationalStore.ValuesBucket { id: project.id, name: project.name, description: project.description || , // 处理 undefined color: project.color, created_at: project.createdAt, updated_at: project.updatedAt }; // 3. 执行插入 // relationalStore.ConflictResolution.REPLACE 表示如果 ID 冲突则覆盖 // 这对于去重非常有用 const rowId await store.insert(TABLE_PROJECT, valueBucket, relationalStore.ConflictResolution.REPLACE); return rowId; }接下来是查询Read。这是最繁琐的部分因为ResultSet是一个游标我们需要遍历它把每一列的数据取出来填到Project对象里。为了不让重复代码淹没我们建议把“单行转对象”的逻辑抽离成一个 Helper 函数。TypeScript// entry/src/main/ets/data/db/ProjectRepo.ts /** * 查询所有项目 * 按更新时间倒序排列最近活跃的在前 */ export async function queryAllProjects(): PromiseProject[] { const store await RdbManager.getInstance().getRdbStore(); // 1. 构建查询条件 // RdbPredicates 是鸿蒙提供的强大的查询构建器类似 SQL 的 WHERE 子句 const predicates new relationalStore.RdbPredicates(TABLE_PROJECT); predicates.orderByDesc(updated_at); // 排序 // 2. 执行查询 const resultSet await store.query(predicates); // 3. 遍历结果集 const projects: Project[] []; try { // goToNextRow() 返回 false 表示游标走到头了 while (resultSet.goToNextRow()) { // 调用转换函数 projects.push(convertRowToProject(resultSet)); } } finally { // 4. 极其重要关闭 ResultSet 释放内存 // 很多内存泄漏都是因为忘了这一步 resultSet.close(); } return projects; } /** * 辅助函数将当前游标指向的行转换为 Project 对象 */ function convertRowToProject(resultSet: relationalStore.ResultSet): Project { // getColumnIndex 是为了防范列名写错或者列不存在的情况 // 虽然稍微损耗一点性能但更安全 return { id: resultSet.getString(resultSet.getColumnIndex(id)), name: resultSet.getString(resultSet.getColumnIndex(name)), description: resultSet.getString(resultSet.getColumnIndex(description)), color: resultSet.getString(resultSet.getColumnIndex(color)), // getLong 对应数据库的 INTEGER createdAt: resultSet.getLong(resultSet.getColumnIndex(created_at)), updatedAt: resultSet.getLong(resultSet.getColumnIndex(updated_at)) }; }你看通过这种模式无论 Project 表有多少列复杂的取值逻辑都被封装在了convertRowToProject这一处。以后如果我们要给项目加个“图标”字段只需要改这一个函数而不用去改每一个查询方法。最后简单带过更新Update和删除Delete。它们的逻辑和查询类似都是先构建RdbPredicates来锁定要操作的行。TypeScript// entry/src/main/ets/data/db/ProjectRepo.ts export async function updateProjectName(id: string, newName: string): Promisenumber { const store await RdbManager.getInstance().getRdbStore(); const valueBucket: relationalStore.ValuesBucket { name: newName, updated_at: Date.now() // 记得更新时间戳 }; const predicates new relationalStore.RdbPredicates(TABLE_PROJECT); predicates.equalTo(id, id); // WHERE id ? return store.update(valueBucket, predicates); } export async function deleteProject(id: string): Promisenumber { const store await RdbManager.getInstance().getRdbStore(); const predicates new relationalStore.RdbPredicates(TABLE_PROJECT); predicates.equalTo(id, id); return store.delete(predicates); }三、 在 EntryAbility 中激活它现在我们的武器库已经准备好了但它们还没被加载。我们需要在 App 启动的最早时机把 Context 注入给RdbManager。打开entry/src/main/ets/entryability/EntryAbility.ts。这是 Stage 模型中 Ability 的生命周期入口。// entry/src/main/ets/entryability/EntryAbility.ts import { UIAbility, AbilityConstant, Want } from kit.AbilityKit; import { window } from kit.ArkUI; import { RdbManager } from ../data/db/RdbManager; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { // 在 Ability 创建时初始化数据库管理器 // 传入 context这样 RdbManager 就能拿到沙箱路径 RdbManager.getInstance().init(this.context); console.info([EntryAbility] RdbManager initialized); } // ... 其他生命周期方法 }这里有一个常识性的问题为什么不在Index.ets的aboutToAppear里初始化因为Index.ets是 UI 界面。如果你的 App 支持后台运行或者卡片Service Widget启动有可能 UI 界面根本不需要显示但后台服务需要读写数据库。如果在 UI 里初始化后台任务就会因为拿不到 Context 而崩溃。在EntryAbility初始化是目前最稳妥的做法。四、 总结今天我们干了一件非常硬核的事情。我们不仅写了代码更是在构建基础设施。我们封装了一个线程安全、支持版本升级的RdbManager单例彻底解决了数据库连接管理的后顾之忧。我们还实践了标准的 DAO 模式将底层的ValuesBucket和ResultSet转换逻辑屏蔽在Repo文件内部为上层的 ViewModel 提供了干净清爽的 TypeScript 接口。现在的会议随记 Pro已经具备了记忆能力。它不再是那个重启就会失忆的 Demo而是一个能持久化存储用户资产的商业级应用雏形。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询