【OpenHarmony/HarmonyOs 】本地收藏管理实战:ArkTS Service 层实现网址 CRUD 与容错解析
2026/7/21 7:47:11 网站建设 项目流程

【OpenHarmony/HarmonyOs 】本地收藏管理实战:ArkTS Service 层实现网址 CRUD 与容错解析

前言

当应用允许用户添加收藏后,问题就不再只是“把数组显示出来”。我们还要处理空标题、非法链接、重复 URL、旧版本脏数据、排序规则和异常反馈。本文通过EntryManageService展示如何在 ArkTS 中构建一个职责清晰的本地 CRUD 服务。🗂️

一、为什么要有 Service 层

如果添加、删除、校验和 JSON 解析都写在页面中,会出现三个问题:

  1. 多个页面重复规则,行为逐渐不一致;
  2. UI 与数据存储强耦合,未来难以迁移 Cloud DB;
  3. 业务逻辑只能通过 UI 测试,单元测试成本很高。

Service 层让页面只表达意图:

const service =EntryManageService.getInstance(); this.customSites = await service.addCustomSite(title,url);

至于如何校验、排序和持久化,由服务内部负责。

二、单例服务与依赖

exportclassEntryManageService{privatestaticinstance: EntryManageService;privatestorage: StorageUtil = StorageUtil.getInstance();privateconstructor(){}staticgetInstance(): EntryManageService {if(!EntryManageService.instance) { EntryManageService.instance =newEntryManageService(); }returnEntryManageService.instance; } }

这个服务当前依赖 Preferences。进一步工程化时,可以定义SiteRepository接口并注入实现,使本地仓库与云仓库可替换。

三、读取数据时永远不要信任磁盘内容

Preferences 中保存的是 JSON 字符串。应用升级、手动调试或异常中断都可能留下非法数据,所以读取过程需要两层防御:安全解析和字段规范化。

privatesafeParseArray(json:string):Object[] {if(!json)return[];try{constparsed = JSON.parse(json)asObject;returnArray.isArray(parsed) ? parsedasObject[] : []; }catch{return[]; } }

解析为数组并不代表元素合法,还要逐项验证:

private normalizeUrlItem(raw:Object): UrlItem |null{constitem = rawasRawUrlItem;constid =typeofitem.id ==='string'?item.id:'';consttitle =typeofitem.title ==='string'? item.title.trim() :'';consturl=typeofitem.url ==='string'?this.normalizeHttpsUrl(item.url) :null;if(!id || !title || !url)returnnull;return{ id, title,url,categoryId: item.categoryId ||'custom',sort:typeofitem.sort ==='number'?item.sort:0,createdAt:typeofitem.createdAt ==='number'?item.createdAt:0,updatedAt:typeofitem.updatedAt ==='number'?item.updatedAt:0}; }

无效项被丢弃,缺失的可选字段获得默认值。这能防止一个坏对象让整个收藏页崩溃。

四、新增:校验、去重、生成元数据

asyncaddCustomSite(titleRaw:string,urlRaw:string):Promise<UrlItem[]> {consttitle = titleRaw.trim();consturl=this.normalizeHttpsUrl(urlRaw);if(!title)thrownewError('EMPTY_TITLE');if(!url)thrownewError('INVALID_URL');constlist=awaitthis.listCustomSites();if(list.some(item => item.url ===url)) {thrownewError('DUPLICATE_URL'); }constnow =Date.now();constnewItem: UrlItem = {id:now.toString(), title,url,categoryId:'custom',sort:0,createdAt: now,updatedAt: now };constnext = [newItem, ...list];awaitthis.persistCustomSites(next);returnnext; }

服务返回更新后的数组,页面可以一次性替换@State,触发声明式 UI 刷新。错误使用稳定代码而不是完整中文文案,页面可根据场景决定 AlertDialog、Toast 或表单行内提示。

时间戳作为 ID 对单机原型足够直观,但极端情况下同一毫秒可能冲突。生产项目建议使用 UUID 或由数据库生成主键。

五、更新时保留不可变字段

更新接口接收 Patch,让调用者只传变化部分:

exportinterfaceUrlItemPatch { title?:string; url?:string; categoryId?:string; sort?:number; }

更新后保留原idcreatedAt,只刷新updatedAt。如果 URL 发生变化,还要排除当前记录后再检查重复:

const duplicate = list.some(item=>item.id!==id&&item.url === nextUrl );if(duplicate) throw new Error('DUPLICATE_URL');

这是 CRUD 中很常见却容易遗漏的细节。

六、删除与排序语义

asyncremoveCustomSite(idRaw:string):Promise<UrlItem[]> {constid = idRaw.trim();if(!id)thrownewError('INVALID_ID');constlist =awaitthis.listCustomSites();constnext = list.filter(item=>item.id!== id);awaitthis.persistCustomSites(next);returnnext; }

当前实现对不存在的 ID 采用幂等删除:结果仍是成功状态。这对于用户快速重复点击、重试或未来云同步都很友好。

读取后按updatedAt降序,同一时间再按标题排序,能够保证显示结果稳定。稳定排序很重要,否则列表可能在每次刷新时随机跳动。

七、本地搜索的权重策略

Service 为自定义网址提供加权搜索:标题前缀 20 分、URL 前缀 10 分、标题包含 5 分、URL 包含 3 分。规则虽然简单,却比单纯过滤更符合用户预期。

更大规模时可以继续增加:

  • 拼音首字母;
  • 访问频率加分;
  • 最近使用衰减;
  • 标签匹配;
  • 用户固定排序优先。

八、存储方案的演进边界

JSON + Preferences 适合 MVP,但每次修改都要读写整份数组。当数据量、并发和查询复杂度上升时,应迁移:

PreferencesJSON↓ 数据量增加 ArkData RDB(离线结构化查询) ↓ 多设备同步 本地 RDB + AGCCloudDB + 冲突合并

由于页面只依赖 Service,替换底层仓库时页面代码可以基本不动,这正是分层设计的价值。✅

九、总结

可靠的收藏功能来自一组明确规则:输入先清洗、URL 强制 HTTPS、写入前去重、读取时容错、更新时间可追踪、错误码保持稳定。将这些规则集中到 Service 层,可以让 ArkUI 页面保持简洁,并为数据库与云同步升级预留空间。

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

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

立即咨询