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

发布时间:2026/7/21 7:47:12
【OpenHarmony/HarmonyOs 】本地收藏管理实战:ArkTS Service 层实现网址 CRUD 与容错解析 【OpenHarmony/HarmonyOs 】本地收藏管理实战ArkTS Service 层实现网址 CRUD 与容错解析前言当应用允许用户添加收藏后问题就不再只是“把数组显示出来”。我们还要处理空标题、非法链接、重复 URL、旧版本脏数据、排序规则和异常反馈。本文通过EntryManageService展示如何在 ArkTS 中构建一个职责清晰的本地 CRUD 服务。️一、为什么要有 Service 层如果添加、删除、校验和 JSON 解析都写在页面中会出现三个问题多个页面重复规则行为逐渐不一致UI 与数据存储强耦合未来难以迁移 Cloud DB业务逻辑只能通过 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() :;consturltypeofitem.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):PromiseUrlItem[] {consttitle titleRaw.trim();consturlthis.normalizeHttpsUrl(urlRaw);if(!title)thrownewError(EMPTY_TITLE);if(!url)thrownewError(INVALID_URL);constlistawaitthis.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; }更新后保留原id和createdAt只刷新updatedAt。如果 URL 发生变化还要排除当前记录后再检查重复const duplicate list.some(itemitem.id!iditem.url nextUrl );if(duplicate) throw new Error(DUPLICATE_URL);这是 CRUD 中很常见却容易遗漏的细节。六、删除与排序语义asyncremoveCustomSite(idRaw:string):PromiseUrlItem[] {constid idRaw.trim();if(!id)thrownewError(INVALID_ID);constlist awaitthis.listCustomSites();constnext list.filter(itemitem.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 页面保持简洁并为数据库与云同步升级预留空间。