尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
ArkData RelationalStore:三方库许可证幂等入库与游标收口【鸿蒙心迹】
这是一篇关于应用本地数据库管理的开发手记Demo 叫LicenseLedger任务号LL-1506。示例导入 6 行许可证清单唯一记录 5 行、重复 1 行其中 1 行需要人工复核。所有记录、日志、页面截图均为预设的示意内容与应用市场实际审核、许可证法律结论或真机测试无关。一、同一份清单导入两遍怎么反而多了一倍做三方库梳理时经常会看到这样一种业务输入有人从工程依赖列表复制一批组件名和版本号填完许可证标识后导入本地复核台账。明明是同一份清单第二次点击导入页面上的行数却从五条变成十条。测试同学往往会说“按钮点快了”但根因是数据层根本没有约定“什么叫同一条记录”。如果把重复检测仅放在按钮防抖上应用重启、离线重试或换一个入口导入都可能重新出问题。我这次用 LicenseLedger 讲一个很小的合同库名 版本是业务唯一键用户填写的 SPDX 字符串只是待确认元数据不作为唯一身份。清单演示包含六行其中两行实际指向同一组件版本。导入后的页面显示原始 6、唯一 5、重复跳过 1、待人工复核 1、进度 100%状态写成“待复核”。这不是自动许可证合规判定只代表应用把待确认数据整理成了可追踪的本地草稿。为什么不直接拿数组做find单次会话中那当然也可以。但一旦要跨重启保留结果、按版本搜索、记录复核状态就需要结构化的数据存储。ArkData 的 RelationalStore 为应用提供数据库、条件查询和结果游标。数据库里的唯一约束承担最后一道防线应用层先查重只是为了提供“重复被跳过”的友好反馈。二、先让唯一键明确再谈插入速度第一步不是上 SQL而是规定键的规范化规则。库名统一 trim 后转小写版本号去掉两侧空格但不擅自把1.2和1.2.0合并因为这可能是不同的版本表达。在我们的演示合同里scope/name的作用域部分不能随意丢掉。另一种常见做法是把 SPDX 也放进键里我不赞成同一版本如果人工改了许可证说明不应该凭空变成一条新依赖。记录示例用五个不同库版本及一个重复输入业务代码只管结构和字符串校验不会访问远端依赖仓库更不会替产品判断许可证是否允许商用。下面先定义一个清晰的输入类型和确定性的规范化方法便于日后放进 Node.js 测试或应用单测里比较。export interface LicenseDraft { name: string; version: string; spdx: string; } export function libraryKey(item: LicenseDraft): string { const name item.name.trim().toLowerCase(); const version item.version.trim(); if (!name || !version || name.includes(|) || version.includes(|)) { throw new Error(名称或版本无效); } return ${name}|${version}; } export const DEMO_DRAFTS: LicenseDraft[] [ { name: chart-lite, version: 1.2.0, spdx: MIT }, { name: safe-json, version: 2.4.1, spdx: Apache-2.0 }, { name: ui-kernel, version: 0.9.3, spdx: UNKNOWN }, { name: text-tools, version: 3.0.0, spdx: BSD-3-Clause }, { name: tiny-math, version: 1.7.2, spdx: MIT }, { name: Chart-Lite , version: 1.2.0, spdx: MIT } ];通过这段纯业务方法我们能预先推导最后一条和第一条具有同一键chart-lite|1.2.0因此输入 6 行只有 5 个身份。注意“UNKNOWN” 被标成待复核并不是数据库报错它是许可证元数据尚不明确的业务状态。不要把一条库名或 SPDX 字段的字符串比较写成已经完成了软件合规审核。应用只能提示风险最终解释需要证据和人工判断。数据库初始化时我选择表级的PRIMARY KEY把同一库版本的重复写入约束留在存储层这样即便另一个按钮绕过了预检查重复项仍不应成为两条有效记录。示例使用kit.ArkData的relationalStore获取本地 RdbStore通过executeSql建表名称和字段都写成普通 SQL 字符串。SQLite 语义的约束和 HarmonyOS 包装 API 需要分别看待应用应根据目标 SDK 的实际错误类型细化异常分支不能只靠 catch 就当作“重复”。这里不做跨表事务因为本篇只处理单表的一条一条幂等入库批次原子替换是另一种问题不能拿来偷换。本次要解决的是重复导入后的可追踪性、键标准化与查询对象生命周期而不是数据库迁移或大规模吞吐量压力测试。import { relationalStore } from kit.ArkData; import { common } from kit.AbilityKit; const DB_CONFIG: relationalStore.StoreConfig { name: license_ledger.db, securityLevel: relationalStore.SecurityLevel.S1 }; export async function openLicenseStore( context: common.UIAbilityContext ): PromiserelationalStore.RdbStore { const store await relationalStore.getRdbStore(context, DB_CONFIG); await store.executeSql(CREATE TABLE IF NOT EXISTS license_item ( lib_key TEXT PRIMARY KEY NOT NULL, name TEXT NOT NULL, version TEXT NOT NULL, spdx TEXT NOT NULL, review TEXT NOT NULL )); return store; }这里的context必须来自合法的应用组件上下文数据库实例建议由应用自己的仓储模块统一管理不能在每个列表项的build()里频繁开库。演示代码暂不涉及多设备协同也不等同于系统级安全审计。创建表失败应停止导入而不是把“0 条记录”伪装成成功。正式项目里还要区分磁盘不足、数据库损坏、版本迁移失败等情况并有可追踪的错误记录。三、游标不能顺手扔给垃圾回收器真正需要在代码审查时盯住的是查询结果。RdbPredicates帮应用按lib_key构造查询条件返回的ResultSet不应该无限期留着。即便查询只看一行也要在 finally 里 close否则列表反复进入和大量批次检查叠加后资源持有问题会比重复数据更隐蔽。接下来这段实现把“是否已经存在”封装成一次限定字段的查询。插入前先查可以给出跳过数量但要注意查询与写入之间并不自动构成并发原子操作。如果存在多个导入入口并行运行仍需串行化仓储入口并保留 PRIMARY KEY 约束处理竞争写入。本演示通过busy门禁控制当前页面的单批次导入不将它描述为跨进程并发锁。export async function existsKey( store: relationalStore.RdbStore, key: string ): Promiseboolean { const predicates new relationalStore.RdbPredicates(license_item); predicates.equalTo(lib_key, key); const rs await store.query(predicates, [lib_key]); try { return rs.rowCount 0; } finally { rs.close(); // ResultSet 与页面状态不是同一种生命周期 } } export async function insertOnce( store: relationalStore.RdbStore, draft: LicenseDraft ): Promiseboolean { const key libraryKey(draft); if (await existsKey(store, key)) return false; const review draft.spdx.trim() UNKNOWN ? 待复核 : 待确认; await store.insert(license_item, { lib_key: key, name: draft.name.trim(), version: draft.version.trim(), spdx: draft.spdx.trim(), review: review }); return true; }业务上还要想清楚已经存在的键要不要更新许可证字段。当前实现的答案是“不更新”一次新导入不能悄悄覆盖之前人工处理过的草稿。需要更新时应当另开明确的“修改许可证信息”操作并留下审阅记录。这也解释了为什么本篇关注幂等而不是 UPSERTUPSERT 本身是能力允许不允许覆盖是产品合同。用户未确认时越方便的自动覆盖反而越容易抹掉证据。使用store.insert时写库本身可能失败此时不能把失败计进 duplicate。正确的实现应分为“新增成功”“已存在跳过”“异常中止”三种结果。我们这组静态示意数据没有模拟写库失败配图也只展示约定成功路径后续接真实数据库后应为唯一键竞争、磁盘不足等情况分别准备测试不得将这篇草稿当作覆盖那些边界的证明。四、让页面只展示结果不暗中替人审核在界面层我只显示几个容易验证的数字导入原始 6 行、入库新增 5 行、重复跳过 1 行、待人工复核 1 行。进度 100% 的含义是“本轮六个输入已得到处理结论”不是质量通过率更不是上架成功率。UNKNOWN提示人工处理其余 SPDX 也只是待确认不能因为写了 MIT、BSD 等字样就自动授予合规保证。为了避免用户连续点击按钮发起相同任务示例在当前页面设置busy然后串行执行输入逐条累计inserted和duplicate。仓储方法运行时不依赖页面文本颜色页面退出后通过 generation 忽略旧批次 UI 回写数据库已经成功提交的行也不会因为页面离开而自动撤销。这两条生命周期要分开讲UI 不更新和持久化回滚并不是同一回事。Entry Component struct LicenseLedgerPage { State busy: boolean false; State inserted: number 0; State duplicates: number 0; State status: string 待导入; private generation: number 1; aboutToDisappear(): void { this.generation; } async importBatch(store: relationalStore.RdbStore): Promisevoid { if (this.busy) return; this.busy true; const mine this.generation; let added 0; let skipped 0; try { for (const draft of DEMO_DRAFTS) { if (await insertOnce(store, draft)) added; else skipped; } if (mine ! this.generation) return; this.inserted added; this.duplicates skipped; this.status 待复核; } catch (_) { if (mine this.generation) this.status 写入失败; } finally { if (mine this.generation) this.busy false; } } build() { Column({ space: 12 }) { Text(LicenseLedger · LL-1506).fontSize(22) Text(新增 ${this.inserted} · 重复 ${this.duplicates} · ${this.status}) Text(样例数据6行其中UNKNOWN待人工复核1行) }.padding(20) } }上面的importBatch接收仓储层管理的 RdbStore示例没有把打开数据库和按钮事件接线写成一整套可直接运行的工程真实项目需要在页面创建前完成openLicenseStore并把异常反馈给用户。这里还隐藏了一个多次点击后的统计问题若第二次对同一数据库再次导入新增将是 0重复将是 6而不是永远保持 5 和 1。配图数字对应的是空表上的首次导入演示不能拿来声称每次运行都会有同样的结果。我会把关键验收拆成清晰的几种断言一是两个写法略有差异的chart-lite产生同一个键二是未知许可证被标为待人工复核但不会阻断其他行入库三是游标被主动关闭四是二次导入不会增加数据库的唯一记录数。真正的数据库测试还要配置测试库并在运行后读取表内容这一轮没有执行所以只能给出设计断言不能冒充实测报告。五、收束减少重复写入不等于完成许可证审查工具做得越像一个完整工作台越容易让人把“状态整理完了”误解成“业务审核完成了”。LicenseLedger 只保证示例中的键规则、单页导入门禁和游标关闭路径具有明确的实现位置至于许可证扫描的准确性、依赖树中的传递依赖、版权声明和法律解释都不在本篇讨论范围内。后续可以把“待复核”单独做成审核工作流但那应是新的工程任务而不是在这一页悄悄增加一个绿色通过按钮。参考华为官方RelationalStore ArkTS API 参考应用数据存储方案选择。接口写法需以实际使用的 SDK 版本为准。本文未进行 DevEco 编译或真机数据库测试02 图的 IDE 与日志及 03 图的手机内容均为独立绘制的演示界面。
RELATED

相关推荐

Agent记忆管理实战:从全量拼接翻车到三层记忆系统

Agent记忆管理实战:从全量拼接翻车到三层记忆系统

1. 从一个真实翻车现场说起去年帮一个团队调一个客服场景的 Agent,需求听起来特别朴素:用户问过的问题、Agent 查到的订单信息、之前几轮对话里确认过的偏好,全都别丢,下一轮接着用。团队第一版方案非常直接——把所有历史消息拼成…

📅 2026/10/14 22:21:23
小龙虾好吃的关键在技能组合:预处理到收汁全拆解

小龙虾好吃的关键在技能组合:预处理到收汁全拆解

1. 做小龙虾翻车几次之后,我才意识到问题出在“技能组合”上 你可能也遇到过这种情况:照着热门菜谱买了一堆调料,锅里油也烧得冒烟了,葱姜蒜爆香的味道直往鼻子里钻,结果成品端上桌,剥开虾壳——肉是散的&a…

📅 2026/10/14 22:16:21
免费SU模型网站哪里找?6个渠道+下载修模实操一次说清

免费SU模型网站哪里找?6个渠道+下载修模实操一次说清

做室内、建筑、景观方案阶段的朋友,估计都有过这种经历:方案明天要汇报,模型里还缺一个款式合适的沙发、一棵姿态好看的树,或者一套完整的中式隔断。这时候,“草图大师模型哪里有完全免费的下载网站”就成了最现实的搜…

📅 2026/10/14 22:16:21
MORE NEWS

更多资讯

📰

无人机视觉计数新方案:NGDINO显式数量建模实战

1. 从“找得到”到“数得清”:无人机视觉计数到底难在哪 无人机航拍图像里的目标计数,听起来像是“检测框一画,数一数框就行”的事。但真正做过这类项目的人都知道,检测和计数之间隔着一道巨大的鸿沟。检测模型告诉你“图里有87个…

📰

Python调海康相机SDK:环境配置到图像采集的实战指南

简介:面向Python开发者的海康威视相机软件开发工具包,专为在安防监控、工业视觉检测等场景中需要通过编程控制相机、采集图像并操作云台的工程师打造的实用资料包。压缩包内共有135个文件,核心是Python脚本与示例程序,还包含界面文…

📰

AI辅助A股复盘:四层指标体系与量价关系实战指南

2026年1月26日,周一。像往常一样,我早上八点半打开行情软件,先把隔夜外盘和周末消息面刷了一遍,再调出AI复盘助手昨晚自动生成的盘前摘要。很多人问我:像“AI观财经”这种栏目到底是怎么做的?是AI直接抛结论…

📰

YOLOv5自动脚本实战:从环境配置到避坑调优

简介:这是一份面向游戏自动化与计算机视觉学习者的YOLOv5实战项目包,聚焦DNF场景下的目标识别与自动操作实现,适合具备一定Python基础、希望深入理解YOLOv5工程落地的开发者参考。压缩包共94个文件,约27.28MB,以32个py…

📰

银企直联对接工商银行:商业资料包如何减少联调返工

简介:这份资源聚焦工商银行银企直联的接口开发与系统对接,面向ERP、财务及资金管理系统开发者与实施人员,帮助解决企业内外部系统与工行核心系统、网银或现金管理平台之间的实时结算与信息查询问题。压缩包共2000个文件,约28.47MB…

📰

Spring Cloud Gateway整合Nacos:动态路由与灰度分流完整实践

一直在调试一个模拟项目X的网关层时,遇到一个很典型的问题:后端某个服务把端口从 8081 改成 8082,网关转发就断了,只能手动改application.yml里的地址再重启网关。后来把 gateway 整合进 Nacos,同样是改配置&#xff0…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬