HarmonyOS应用开发实战:猫猫大作战-relationalStore 建库建表、增删改查 SQL、事务批量插入、与 Preference 键值取
前言前面我们用 HTTP 拉排行榜、上报得分——但应用关闭后本地数据全没了下次开要重新拉。战绩历史、玩家昵称、皮肤解锁这种结构化持久数据要存本地数据库。HarmonyOS 提供ohos.data.relationalStoreSQLite 封装做关系型存储——建表、插入、查询、更新、删除SQL 语法齐全。本篇以「猫猫大作战」战绩历史表建表 插入 查询 Top 50 删除为锚点把relationalStore 建库建表、增删改查 SQL、事务批量插入、与 Preference 键值取舍四大要点讲透。提示本系列不讲 ArkTS 基础语法与环境搭建假设你已跟完第 1–63 篇。本篇是阶段四第四篇。一、场景拆解战绩历史持久化「猫猫大作战」战绩历史第 47、48 篇 LazyForEach 用过GameRecord[]当前是内存数组——应用关就没了。玩家想本地持久化战绩每局结束插入一条记录得分/时间/连击/日期。战绩页查询最近 50 条按日期降序。删除单条战绩误录清除。批量插入历史导入从服务器拉 100 条一次写入。// 预演HistoryDb 建表 CRUD import { relationalStore } from kit.ArkData; export class HistoryDb { private store: relationalStore.RdbStore | null null; private readonly dbName: string catgame.db; private readonly tableName: string history; async init(context: Context): Promisevoid { const config: relationalStore.StoreConfig { name: this.dbName, securityLevel: relationalStore.SecurityLevel.S1 }; this.store await relationalStore.getRdbStore(context, config); await this.createTable(); } private async createTable(): Promisevoid { const sql CREATE TABLE IF NOT EXISTS ${this.tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, score INTEGER NOT NULL, duration INTEGER NOT NULL, max_combo INTEGER NOT NULL, merge_count INTEGER NOT NULL, highest_level INTEGER NOT NULL, date TEXT NOT NULL ); await this.store!.executeSql(sql); } // 插入 async insert(record: OmitGameRecord, id): Promisenumber { const values: relationalStore.ValuesBucket { score: record.score, duration: record.duration, max_combo: record.maxCombo, merge_count: record.mergeCount, highest_level: record.highestLevel, date: record.date }; const rowId await this.store!.insert(this.tableName, values); return rowId; } // 查询最近 50 条 async queryRecent(limit: number 50): PromiseGameRecord[] { const sql SELECT * FROM ${this.tableName} ORDER BY date DESC LIMIT ?; const result await this.store!.querySql(sql, [limit]); const records: GameRecord[] []; while (result.goToNextRow()) { records.push(this.mapRow(result)); } return records; } // 删除单条 async delete(id: number): Promisevoid { const predicates new relationalStore.RdbPredicates(this.tableName); predicates.equalTo(id, id); await this.store!.delete(predicates); } private mapRow(result: relationalStore.ResultSet): GameRecord { return { id: result.getLong(result.getColumnIndex(id)), score: result.getLong(result.getColumnIndex(score)), duration: result.getLong(result.getColumnIndex(duration)), maxCombo: result.getLong(result.getColumnIndex(max_combo)), mergeCount: result.getLong(result.getColumnIndex(merge_count)), highestLevel: result.getLong(result.getColumnIndex(highest_level)), date: result.getString(result.getColumnIndex(date)) }; } }核心问题relationalStore怎么建库建表增删改查四种 SQL 怎么用参数化查询怎么防注入事务怎么批量插入SQLite 和 Preference 键值存储怎么选二、relationalStore 建库建表2.1 申请数据存储权限修改entry/src/main/module.json5{ module: { requestPermissions: [ { name: ohos.permission.data.relationalStore, reason: $string:reason_database, usedScene: { abilities: [EntryAbility], when: always } } ] } }提示某些 HarmonyOS 版本 relationalStore 不需要单独权限——应用沙盒内数据库自动授权。以实际 SDK 为准权限报错时补申请。2.2 创建 RdbStoreimport { relationalStore } from kit.ArkData; const config: relationalStore.StoreConfig { name: catgame.db, // 数据库文件名 securityLevel: relationalStore.SecurityLevel.S1 // 安全等级 }; const store: relationalStore.RdbStore await relationalStore.getRdbStore(context, config);config 字段字段含义推荐值name数据库文件名.dbapp.dbsecurityLevel安全等级S1–S4S1最低普通数据安全等级等级含义适合S1开放低公开数据排行榜缓存S2普通用户数据战绩、昵称S3个人敏感身份信息S4严格机密密码、财务关键经验「战绩/昵称」用 S2「公开缓存」用 S1——不要全部 S4否则备份/迁移受限。2.3 建表 SQLconst sql CREATE TABLE IF NOT EXISTS history ( id INTEGER PRIMARY KEY AUTOINCREMENT, score INTEGER NOT NULL, duration INTEGER NOT NULL, max_combo INTEGER NOT NULL, merge_count INTEGER NOT NULL, highest_level INTEGER NOT NULL, date TEXT NOT NULL ); await store.executeSql(sql);SQL 拆解片段含义CREATE TABLE IF NOT EXISTS建表存在则跳过幂等history表名id INTEGER PRIMARY KEY AUTOINCREMENT主键自增score INTEGER NOT NULL整数字段非空date TEXT NOT NULL文本字段非空关键经验建表用IF NOT EXISTS——重启应用多次建表幂等不报错。2.4 SQLite 字段类型SQLite 是动态类型但建表仍推荐声明声明实际存储适合INTEGER整数得分、时长、连击TEXT文本日期、昵称、UUIDREAL浮点评分、坐标BLOB二进制头像、序列化数据三、增删改查 SQL3.1 增insert// 方式 AAPI insert推荐防注入 const values: relationalStore.ValuesBucket { score: 1500, duration: 180, max_combo: 5, merge_count: 28, highest_level: 3, date: 2026-07-24T10:30:00Z }; const rowId await store.insert(history, values); // 方式 B原始 SQL要手动参数化防注入 const sql INSERT INTO history (score, duration, max_combo, merge_count, highest_level, date) VALUES (?, ?, ?, ?, ?, ?); await store.executeSql(sql, [1500, 180, 5, 28, 3, 2026-07-24T10:30:00Z]);关键经验插入用store.insert(tableName, values)API——自动参数化防注入不用拼 SQL。3.2 查querySql// 查询最近 50 条参数化 const sql SELECT * FROM history ORDER BY date DESC LIMIT ?; const result await store.querySql(sql, [50]); const records: GameRecord[] []; while (result.goToNextRow()) { records.push({ id: result.getLong(result.getColumnIndex(id)), score: result.getLong(result.getColumnIndex(score)), /* ... */ }); } return records;ResultSet 遍历API作用result.goToNextRow()移到下一行返回 false 表示结束result.getColumnIndex(name)取字段索引result.getLong(idx)/getString(idx)取字段值关键经验查询用querySql 参数化——LIMIT ?配[50]不拼字符串防注入。3.3 改update// 方式 ARdbPredicates推荐 const predicates new relationalStore.RdbPredicates(history); predicates.equalTo(id, 5); const values: relationalStore.ValuesBucket { score: 2000 }; const changedRows await store.update(predicates, values); // 方式 B原始 SQL const sql UPDATE history SET score ? WHERE id ?; await store.executeSql(sql, [2000, 5]);3.4 删delete// 方式 ARdbPredicates推荐 const predicates new relationalStore.RdbPredicates(history); predicates.equalTo(id, 5); const deletedRows await store.delete(predicates); // 方式 B原始 SQL const sql DELETE FROM history WHERE id ?; await store.executeSql(sql, [5]);3.5 RdbPredicates 条件构造const predicates new relationalStore.RdbPredicates(history); // 等于 predicates.equalTo(score, 1500); // 大于/小于 predicates.greaterThan(score, 1000); predicates.lessThan(duration, 300); // 范围 predicates.between(score, 1000, 2000); // 模糊匹配 predicates.like(date, 2026-07-%); // 排序 分页 predicates.orderByAsc(date); // 升序 predicates.orderByDesc(score); // 降序 predicates.limitAs(50); // 取 50 条 predicates.offsetAs(10); // 跳过 10 条关键经验RdbPredicates 链式构造条件——比手写 SQL 安全自动参数化可读性强。四、事务批量插入4.1 为什么要事务场景从服务器拉 100 条历史导入本地——单条 insert 100 次中途某条失败要回滚已插的否则数据不一致。事务保证要么全成功要么全回滚。4.2 事务 APIawait store.beginTransaction(); // 开启事务 try { for (const record of records) { await store.insert(history, { score: record.score, duration: record.duration, /* ... */ }); } await store.commit(); // 提交全成功才持久化 } catch (error) { await store.rollBack(); // 回滚失败恢复到事务前 throw error; }4.3 性能对比方式100 条插入耗时原因单条 insert × 100~2s每条都触发磁盘 IO 索引更新事务批量~50ms一次磁盘 IO索引一次更新关键经验批量插入必用事务——性能提升 40 倍且保证原子性。五、实战HistoryDb 完整实现5.1 创建 HistoryDb新建entry/src/main/ets/services/HistoryDb.etsimport { relationalStore } from kit.ArkData; import { GameRecord, CatLevel } from ../components/GameTypes; export class HistoryDb { private store: relationalStore.RdbStore | null null; private readonly dbName: string catgame.db; private readonly tableName: string history; async init(context: Context): Promisevoid { const config: relationalStore.StoreConfig { name: this.dbName, securityLevel: relationalStore.SecurityLevel.S2 }; this.store await relationalStore.getRdbStore(context, config); await this.createTable(); } private async createTable(): Promisevoid { const sql CREATE TABLE IF NOT EXISTS ${this.tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, score INTEGER NOT NULL, duration INTEGER NOT NULL, max_combo INTEGER NOT NULL, merge_count INTEGER NOT NULL, highest_level INTEGER NOT NULL, date TEXT NOT NULL ); await this.store!.executeSql(sql); } // 插入单条 async insert(record: OmitGameRecord, id): Promisenumber { const values: relationalStore.ValuesBucket { score: record.score, duration: record.duration, max_combo: record.maxCombo, merge_count: record.mergeCount, highest_level: record.highestLevel, date: record.date }; return await this.store!.insert(this.tableName, values); } // 事务批量插入服务器导入用 async batchInsert(records: OmitGameRecord, id[]): Promisevoid { await this.store!.beginTransaction(); try { for (const record of records) { const values: relationalStore.ValuesBucket { score: record.score, duration: record.duration, max_combo: record.maxCombo, merge_count: record.mergeCount, highest_level: record.highestLevel, date: record.date }; await this.store!.insert(this.tableName, values); } await this.store!.commit(); } catch (error) { await this.store!.rollBack(); throw error; } } // 查询最近 N 条战绩页用 async queryRecent(limit: number 50, offset: number 0): PromiseGameRecord[] { const predicates new relationalStore.RdbPredicates(this.tableName); predicates.orderByDesc(date); predicates.limitAs(limit); predicates.offsetAs(offset); const result await this.store!.query(predicates); return this.mapResultSet(result); } // 查询最高分统计 async queryHighestScore(): Promisenumber { const sql SELECT MAX(score) as max_score FROM ${this.tableName}; const result await this.store!.querySql(sql); if (result.goToNextRow()) { return result.getLong(result.getColumnIndex(max_score)) || 0; } return 0; } // 查询总场数 async queryTotalCount(): Promisenumber { const sql SELECT COUNT(*) as total FROM ${this.tableName}; const result await this.store!.querySql(sql); if (result.goToNextRow()) { return result.getLong(result.getColumnIndex(total)); } return 0; } // 按得分范围查询 async queryByScoreRange(min: number, max: number): PromiseGameRecord[] { const predicates new relationalStore.RdbPredicates(this.tableName); predicates.between(score, min, max); predicates.orderByDesc(score); const result await this.store!.query(predicates); return this.mapResultSet(result); } // 更新单条 async update(id: number, updates: PartialGameRecord): Promisenumber { const predicates new relationalStore.RdbPredicates(this.tableName); predicates.equalTo(id, id); const values: relationalStore.ValuesBucket {}; if (updates.score ! undefined) values[score] updates.score; if (updates.duration ! undefined) values[duration] updates.duration; if (updates.maxCombo ! undefined) values[max_combo] updates.maxCombo; if (updates.mergeCount ! undefined) values[merge_count] updates.mergeCount; if (updates.highestLevel ! undefined) values[highest_level] updates.highestLevel; return await this.store!.update(predicates, values); } // 删除单条 async delete(id: number): Promisenumber { const predicates new relationalStore.RdbPredicates(this.tableName); predicates.equalTo(id, id); return await this.store!.delete(predicates); } // 删除所有清空表 async deleteAll(): Promisenumber { const predicates new relationalStore.RdbPredicates(this.tableName); return await this.store!.delete(predicates); } // 删除 30 天前的旧记录清理 async deleteOlderThan(days: number): Promisenumber { const cutoffDate new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); const sql DELETE FROM ${this.tableName} WHERE date ?; return await this.store!.executeSql(sql, [cutoffDate]); } private mapResultSet(result: relationalStore.ResultSet): GameRecord[] { const records: GameRecord[] []; while (result.goToNextRow()) { records.push(this.mapRow(result)); } return records; } private mapRow(result: relationalStore.ResultSet): GameRecord { return { id: result.getLong(result.getColumnIndex(id)), score: result.getLong(result.getColumnIndex(score)), duration: result.getLong(result.getColumnIndex(duration)), maxCombo: result.getLong(result.getColumnIndex(max_combo)), mergeCount: result.getLong(result.getColumnIndex(merge_count)), highestLevel: result.getLong(result.getColumnIndex(highest_level)) as CatLevel, date: result.getString(result.getColumnIndex(date)) }; } // 销毁关数据库连接 async close(): Promisevoid { if (this.store) { this.store null; // 简化实际可能有 release/close API } } }5.2 Index 结束游戏时插入战绩// 来源entry/src/main/ets/pages/Index.ets endGameSQLite 改造后 import { HistoryDb } from ../services/HistoryDb; import { common } from kit.AbilityKit; Entry Component struct Index { /* ... 其他 state */ private historyDb: HistoryDb new HistoryDb(); private context: common.UIAbilityContext getContext(this) as common.UIAbilityContext; aboutToAppear() { this.historyDb.init(this.context); // 初始化数据库 } endGame() { this.gameState GameState.GAME_OVER; this.maxCombo this.gameEngine.getMaxCombo(); this.mergeCount this.gameEngine.getMergeCount(); this.highestLevel this.gameEngine.getHighestLevel(); if (this.score this.highScore) { this.highScore this.score; } this.clearTimers(); // 异步插入战绩到 SQLite不阻塞 UI this.saveHistory(); } async saveHistory() { try { await this.historyDb.insert({ score: this.score, duration: this.gameTime, maxCombo: this.maxCombo, mergeCount: this.mergeCount, highestLevel: this.highestLevel, date: new Date().toISOString() }); console.info(战绩保存成功); } catch (error) { console.error(战绩保存失败: ${(error as Error).message}); } } aboutToDisappear() { this.clearTimers(); this.historyDb.close(); } /* ... 其他方法 */ }5.3 改造 LeaderboardPage 显示本地战绩// 来源entry/src/main/ets/pages/LeaderboardPage.etsSQLite 改造后 import { GameRecord, CatLevel } from ../components/GameTypes; import { HttpService } from ../services/HttpService; import { HistoryDb } from ../services/HistoryDb; import { common } from kit.AbilityKit; Component export struct LeaderboardPage { State history: GameRecord[] []; State loading: boolean false; State errorMsg: string ; private httpService: HttpService new HttpService(); private historyDb: HistoryDb new HistoryDb(); private context: common.UIAbilityContext getContext(this) as common.UIAbilityContext; aboutToAppear() { this.historyDb.init(this.context).then(() { this.loadHistory(); }); } aboutToDisappear() { this.httpService.destroy(); this.historyDb.close(); } async loadHistory() { this.loading true; this.errorMsg ; try { // 先查本地 SQLite快 const local await this.historyDb.queryRecent(50); if (local.length 0) { this.history local; } // 异步拉服务器同步第 67 篇会专讲同步策略 this.syncFromServer(); } catch (error) { this.errorMsg (error as Error).message; } finally { this.loading false; } } async syncFromServer() { try { const remote await this.httpService.fetchHistory(50); // 事务批量插入服务器数据到本地 await this.historyDb.batchInsert(remote.map(r ({ score: r.score, duration: r.duration, maxCombo: r.maxCombo, mergeCount: r.mergeCount, highestLevel: r.highestLevel, date: r.date }))); // 重新查本地 this.history await this.historyDb.queryRecent(50); } catch (error) { console.error(服务器同步失败: ${(error as Error).message}); } } build() { Column() { Row() { Text( 战绩历史).fontSize(20).fontWeight(FontWeight.Bold).fontColor(#2C3E50) } .width(100%).height(56).padding({ left: 16 }) .justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Center) if (this.loading) { Column() { LoadingProgress().width(48).height(48).color(#3498DB) Text(加载中...).fontSize(16).fontColor(#95A5A6).margin({ top: 12 }) } .width(100%).layoutWeight(1) .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center) } else if (this.history.length 0) { Column() { Text().fontSize(48).margin({ bottom: 12 }) Text(暂无战绩).fontSize(16).fontColor(#95A5A6) } .width(100%).layoutWeight(1) .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center) } else { List() { ForEach(this.history, (record: GameRecord) { ListItem() { Row() { Column() { Text(得分).fontSize(10).fontColor(#95A5A6) Text(record.score.toString()) .fontSize(18).fontWeight(FontWeight.Bold).fontColor(#2ECC71) } .alignItems(HorizontalAlign.Start).layoutWeight(2) Column() { Text(用时).fontSize(10).fontColor(#95A5A6) Text(this.formatTime(record.duration)) .fontSize(16).fontColor(#2C3E50) } .alignItems(HorizontalAlign.Center).layoutWeight(2) Column() { Text(连击).fontSize(10).fontColor(#95A5A6) Text(x${record.maxCombo}) .fontSize(16).fontWeight(FontWeight.Bold).fontColor(#E74C3C) } .alignItems(HorizontalAlign.End).layoutWeight(2) } .width(100%).height(64) .padding({ left: 16, right: 16, top: 8, bottom: 8 }) .onClick(() { // 长按删除第 51 篇 onTouch 思路 this.showDeleteConfirm(record.id); }) } }, (record: GameRecord) history_${record.id}) } .width(100%).layoutWeight(1) .divider({ strokeWidth: 1, color: #ECF0F1, startMargin: 16, endMargin: 16 }) } } .width(100%).height(100%) .backgroundColor(#FFFFFF) } formatTime(seconds: number): string { const min Math.floor(seconds / 60); const sec seconds % 60; return ${min.toString().padStart(2, 0)}:${sec.toString().padStart(2, 0)}; } showDeleteConfirm(id: number) { // 简化用 AlertDialog 或自定义确认弹窗 console.info(确认删除战绩 id${id}); } }六、SQLite vs Preference 取舍6.1 对比维度SQLiterelationalStorePreferencekvStore数据类型表行×列键值key→value适合结构化、列表、关系少量配置、标志位查询SQL条件/排序/聚合按 key 取体积中建表开销小性能中SQL 解析高直接取事务✅ 支持❌ 无6.2 「猫猫大作战」取舍数据推荐原因战绩历史50 条SQLite列表结构要排序查询玩家昵称Preference单字符串最高分Preference单数字音效开关Preference单布尔皮肤解锁列表SQLite多条目要查询鉴权 tokenPreference单字符串最近一次登录时间Preference单数字关键经验「列表/关系/要查询」用 SQLite「单值/标志/配置」用 Preference——本系列第 65 篇会专讲 Preference。七、踩坑提示7.1 忘初始化 store 就用// ❌ 错误没 init 就 insertstore 是 null async insert(record) { await this.store!.insert(/* ... */); // store 是 null抛错 } // ✅ 正确aboutToAppear 先 init aboutToAppear() { this.historyDb.init(this.context); } // init 完成后才 insert7.2 SQL 注入拼接字符串// ❌ 错误拼接字符串注入风险 const sql SELECT * FROM history WHERE date ${userInput}; // userInput OR 11 --拉出全部数据 // ✅ 正确参数化查询 const sql SELECT * FROM history WHERE date ?; await store.querySql(sql, [userInput]); // 或用 RdbPredicates const predicates new RdbPredicates(history); predicates.equalTo(date, userInput);7.3 ResultSet 忘遍历// ❌ 错误query 返回 ResultSet没遍历就当数组用 const result await store.query(predicates); console.info(result.length); // undefinedResultSet 没 length // ✅ 正确goToNextRow 遍历 const records []; while (result.goToNextRow()) { records.push(/* ... */); }7.4 事务忘回滚// ❌ 错误事务失败没 rollBack部分数据已插入 await store.beginTransaction(); for (const record of records) { await store.insert(/* ... */); // 第 50 条失败抛错 } // 忘了 catch 里 rollBack前 49 条已插入但数据不完整 await store.commit(); // ✅ 正确try-catch rollBack await store.beginTransaction(); try { for (const record of records) { await store.insert(/* ... */); } await store.commit(); } catch (error) { await store.rollBack(); // 失败回滚 throw error; }7.5 忘 close 泄漏// ❌ 错误组件销毁没 close连接泄漏 aboutToDisappear() { /* 忘了 this.historyDb.close(); */ } // ✅ 正确销毁时关连接 aboutToDisappear() { this.historyDb.close(); }7.6 主键自增理解错// ❌ 错误插入时手动设 id与 AUTOINCREMENT 冲突 await store.insert(history, { id: 100, score: 1500, /* ... */ }); // ✅ 正确不设 id让数据库自增 await store.insert(history, { score: 1500, /* ... */ }); // 数据库自动分配 id 1, 2, 3, ...八、调试技巧console.info打执行的 SQL追查询/插入是否正确。DevEco Database Inspector查看数据库文件、表结构、数据类似 Android Database Inspector。查询返回空排查检查表名是否对检查 WHERE 条件是否过严检查表是否真有数据。插入失败排查检查必填字段NOT NULL是否都传检查类型是否匹配INTEGER 传字符串会报错。九、性能与最佳实践结构化/列表/要查询用 SQLite单值/配置用 Preference——按数据类型选。建表用 IF NOT EXISTS——重启幂等不报错。查询用参数化querySql ?或 RdbPredicates——防 SQL 注入。批量插入用事务——性能提升 40 倍且保证原子性。事务失败必 rollBack——否则数据不一致。aboutToAppear initaboutToDisappear close——避免连接泄漏。不手动设主键——让 AUTOINCREMENT 自增。安全等级选对——普通数据 S2公开缓存 S1敏感 S3/S4。十、阶段四进度61–65本篇是阶段四「网络与数据」第 4 篇篇主题核心要点61HTTP 请求http 模块、GET/POST、错误处理62REST/GraphQL接口设计取舍63数据序列化JSON stringify/parse、Protobuf64本篇SQLite 持久化relationalStore 建表、CRUD、事务65Preference 键值轻量配置存储总结本篇我们从 SQLite 持久化切入掌握了relationalStore 建库建表StoreConfig CREATE TABLE、增删改查insert/querySql/update/delete RdbPredicates 防注入、事务批量插入beginTransaction commit rollBack、**与 Preference 取舍列表用 SQLite单值用 Preference**四大要点并给出了 HistoryDb 完整实现 战绩页改造代码。核心要点建表 IF NOT EXISTS 幂等查询参数化防注入批量用事务提升 40 倍aboutToAppear init aboutToDisappear close列表用 SQLite 单值用 Preference。下一篇我们将拆解 Preference——键值存储轻量配置。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源「猫猫大作战」项目源码本仓库entry/src/main/ets/services/HistoryDb.ets、entry/src/main/ets/pages/Index.ets、entry/src/main/ets/pages/LeaderboardPage.etsohos.data.relationalStore 官方指南RdbPredicates API 官方文档HarmonyOS 数据存储最佳实践开源鸿蒙跨平台社区HarmonyOS 开发者官方文档首页系列索引本仓库articles/INDEX.md

相关新闻

HarmonyOS应用开发实战:猫猫大作战-`replaceUrl` 与 `pushUrl` 的核心区别、无回退栈的设计原则、以及在实际项目中的正确使用方

HarmonyOS应用开发实战:猫猫大作战-`replaceUrl` 与 `pushUrl` 的核心区别、无回退栈的设计原则、以及在实际项目中的正确使用方

前言 在某些场景下,我们不希望用户通过返回键回到之前的页面——例如登录页跳转到主页、启动引导页跳转到首页、游戏结束后重新开始。HarmonyOS 提供了 router.replaceUrl 来替换当前页面,被替换的页面会从页面栈中移除,用户无法通过返回键回…

2026/7/28 0:22:49阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-在 HarmonyOS 应用中,WindowStage(窗口舞台)是连接 UIAbility 与页面的中间枢纽

HarmonyOS应用开发实战:猫猫大作战-在 HarmonyOS 应用中,WindowStage(窗口舞台)是连接 UIAbility 与页面的中间枢纽

前言 在 HarmonyOS 应用中,WindowStage(窗口舞台)是连接 UIAbility 与页面的中间枢纽——它负责管理应用窗口的创建、显示、隐藏、销毁全周期,并承载 loadContent 加载首屏页面。理解 onWindowStageCreate 回调的完整能力&#x…

2026/7/28 0:22:49阅读更多 →
Applera1n:iOS 15-16.6激活锁绕过终极解决方案深度解析

Applera1n:iOS 15-16.6激活锁绕过终极解决方案深度解析

Applera1n:iOS 15-16.6激活锁绕过终极解决方案深度解析 【免费下载链接】applera1n icloud bypass for ios 15-16 项目地址: https://gitcode.com/gh_mirrors/ap/applera1n 在iOS设备安全领域,激活锁(Activation Lock)一直…

2026/7/28 0:22:49阅读更多 →
Tiny11Builder终极指南:快速打造精简版Windows 11系统镜像

Tiny11Builder终极指南:快速打造精简版Windows 11系统镜像

Tiny11Builder终极指南:快速打造精简版Windows 11系统镜像 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder Tiny11Builder是一款强大的PowerShell脚本工…

2026/7/28 1:34:59阅读更多 →
3步改造Flipper Zero:从单调白屏到炫彩RGB背光的完整指南

3步改造Flipper Zero:从单调白屏到炫彩RGB背光的完整指南

3步改造Flipper Zero:从单调白屏到炫彩RGB背光的完整指南 【免费下载链接】awesome-flipperzero 🐬 A collection of awesome resources for the Flipper Zero device. 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-flipperzero 你…

2026/7/28 1:34:59阅读更多 →
为什么你的局部重绘总发虚、崩结构、丢细节?——揭秘ControlNet+Inpaint模型融合的3层注意力对齐机制

为什么你的局部重绘总发虚、崩结构、丢细节?——揭秘ControlNet+Inpaint模型融合的3层注意力对齐机制

更多请点击: https://kaifayun.com 第一章:为什么你的局部重绘总发虚、崩结构、丢细节?——揭秘ControlNetInpaint模型融合的3层注意力对齐机制 局部重绘失效的根本症结,往往不在掩码精度或提示词强度,而在于ControlN…

2026/7/28 1:34:59阅读更多 →
大麦网自动抢票终极指南:告别手速焦虑,三步搞定热门演出门票

大麦网自动抢票终极指南:告别手速焦虑,三步搞定热门演出门票

大麦网自动抢票终极指南:告别手速焦虑,三步搞定热门演出门票 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 你是否曾经盯着手机屏幕,心跳…

2026/7/28 1:34:59阅读更多 →
通义千问API网关集成最佳实践:QPS提升3.8倍、延迟压降至86ms的4个关键配置点

通义千问API网关集成最佳实践:QPS提升3.8倍、延迟压降至86ms的4个关键配置点

更多请点击: https://intelliparadigm.com 第一章:通义千问API网关集成的性能跃迁全景图 通义千问API网关集成并非简单的接口代理升级,而是一次面向高并发、低延迟与弹性伸缩能力的系统性架构演进。通过将Qwen大模型服务统一纳管至自研API网…

2026/7/28 1:34:59阅读更多 →
p064基于Python的网络小说数据分析系统的设计与实现_hive+flask+spider31(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_

p064基于Python的网络小说数据分析系统的设计与实现_hive+flask+spider31(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_

p064基于Python的网络小说数据分析系统的设计与实现_hiveflaskspider31(设计源文件万字报告讲解)(支持资料、图片参考_相关定制)_ python3.7flaskhivespidermysql5.7vue

2026/7/28 1:32:59阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/27 1:14:34阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/27 1:14:52阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/27 1:14:56阅读更多 →
告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:29阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:29阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:29阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/27 16:57:54阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/26 19:05:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/26 19:05:21阅读更多 →