公司动态
【寻迹校园 HarmonyOS NEXT 实战 18】六维 AND 组合筛选:ArkTS 纯函数实现关键词、类型、类别、地点、时间与颜色
【寻迹校园 HarmonyOS NEXT 实战 18】六维 AND 组合筛选ArkTS 纯函数实现关键词、类型、类别、地点、时间与颜色这是“寻迹校园 HarmonyOS NEXT 实战”系列第 18 篇。本文结合ReportFilterPolicy.ets和真实自动化用例说明如何把关键词、失物类型、物品类别、校园区域、自然日范围和颜色六个条件统一成可测试的 AND 组合筛选并避免页面与 Service 各自维护一套规则。上图为原创生成的技术插画不是项目截图。六个条件像六道连续门禁未选择的条件自动放行已经选择的条件必须全部通过记录才会进入最终列表。一、多条件筛选最常见的错误是 OR 泄漏校园失物列表通常同时提供关键词搜索全部/丢失/拾得类型物品类别校园区域今天/近 3 天/近 7 天颜色短词。用户选择“拾得 箱包 第一教学楼 今天 灰色”真实意图是所有条件同时成立。若实现中某一段使用||拼错就会把“体育馆的灰色水杯”或“昨天的黑色双肩包”混进结果。更隐蔽的问题是页面先筛一部分Service 再筛另一部分。两个入口只要归一化方式不同同一查询就会产生不同结果。二、先用一个查询模型收拢六个维度项目用HomeReportQuery表达完整输入exportclassHomeReportQuery{keyword:string;type:HomeReportFilterHomeReportFilter.ALL;category:string;areaKeyword:string;timeRange:HomeTimeRangeHomeTimeRange.ALL;color:string;}页面只负责把输入框、筛选标签和弹窗选择转换为查询对象。ReportService.listReports(query)读取 Repository 的权威记录后统一交给filterHomeReports()。这样做的直接收益是页面切换布局、增加大屏侧栏或复用到其他入口时不需要复制筛选实现。三、先排除不允许公开的状态组合筛选之前先做状态门禁类型不是当前选择或状态属于DRAFT/HIDDEN/WITHDRAWN时直接返回false。完整代码会在后面的 AND 收口部分统一展示。DRAFT、HIDDEN和WITHDRAWN无论其他字段多么匹配都不能进入首页公开结果。把状态排除放在前面还能避免后续字符串拼接和日期计算。当前策略没有排除RESOLVED。这意味着首页是否展示已完成记录由产品语义继续决定但匹配候选链路会另外限制为OPEN/CLAIMING。文章必须区分“首页可见”和“可成为新匹配候选”两个概念。四、所有文本输入先归一化筛选函数先对关键词、类别、地点和颜色统一执行trim().toLowerCase()。trim()解决筛选标签、输入框和历史数据中的首尾空格toLowerCase()为英文、型号和混合文本提供不区分大小写的比较。中文本身不受大小写影响但项目可能出现USB-C、MatePad等内容。统一归一化比只对某个字段特殊处理更稳定。若未来需要拼音、同义词或分词搜索应建立独立搜索索引而不是继续往这个纯函数里堆大量replace()。五、关键词应该搜索哪些公开字段项目把标题、类别、区域和公开描述拼成searchable再执行includes()。这段逻辑与状态门禁、归一化和其他条件共同收口在同一个筛选函数中避免拆成多个碎代码块。私密核验特征不进入搜索。拾得者填写的夹层贴纸、内部划痕等信息只在认领审核时使用不能因为用户搜索关键词而公开泄漏。这条边界也适用于未来的小艺 Agent公开搜索、候选召回和 Agent 输入都不应默认携带私密字段。六、类别用精确匹配地点用片段匹配类别是稳定内部值“箱包”不应该误命中“箱包配件”因此采用归一化后的全等比较。地点则使用权威词表里的filterKeyword。例如发布记录保存“第一教学楼附近”筛选标签显示“第一教学楼”所以采用归一化后的片段匹配。精确与模糊不是随意选择而是由数据契约决定。类别的 value 与标签一致地点则明确区分 label、保存 value 和 filterKeyword。七、颜色只读取公开文本颜色搜索范围被刻意限制为标题和公开描述代码只构造title description的公开搜索文本不读取privateFeature。如果公开描述没有“灰色”而私密特征写了“夹层是灰色”颜色筛选不能命中。这既保护私密信息也避免把核验答案暴露成可枚举条件。当前颜色筛选只是文本包含不是图片识别也没有同义词映射。“深灰”可以包含“灰”但“银色”和“灰色”不会自动互通。这是可解释规则的边界不应宣传为计算机视觉能力。八、AND 语义要在一个返回表达式里收口最终把全部条件写进同一个返回表达式。下面是从状态门禁到 AND 收口的完整核心实现保留一段连续代码比拆成许多两三行代码块更利于阅读exportfunctionfilterHomeReports(reports:ItemReport[],query:HomeReportQuery,nowMs:numberDate.now()):ItemReport[]{constkeywordquery.keyword.trim().toLowerCase();constcategoryquery.category.trim().toLowerCase();constareaquery.areaKeyword.trim().toLowerCase();constcolorquery.color.trim().toLowerCase();returnreports.filter((report:ItemReport){constmatchesTypequery.typeHomeReportFilter.ALL||(query.typeHomeReportFilter.LOSTreport.reportTypeReportType.LOST)||(query.typeHomeReportFilter.FOUNDreport.reportTypeReportType.FOUND);if(!matchesType||report.statusReportStatus.DRAFT||report.statusReportStatus.HIDDEN||report.statusReportStatus.WITHDRAWN){returnfalse;}constsearchable${report.title}${report.category}${report.area}${report.description}.toLowerCase();constcolorSearchable${report.title}${report.description}.toLowerCase();constmatchesKeywordkeyword.length0||searchable.includes(keyword);constmatchesCategorycategory.length0||report.category.trim().toLowerCase()category;constmatchesAreaarea.length0||report.area.trim().toLowerCase().includes(area);constmatchesColorcolor.length0||colorSearchable.includes(color);returnmatchesKeywordmatchesCategorymatchesAreamatchesColormatchesHomeTimeRange(report.eventDate,query.timeRange,nowMs);});}类型和状态已经在前置门禁完成其余五项与时间范围同时满足才保留记录。未选择条件时对应逻辑返回true。这种“空条件放行”的写法比动态拼接大量分支更容易测试也能清楚表达 AND 语义。上图展示页面、Service 与纯规则的职责页面构造查询Service 读取 RepositoryPolicy 只接收普通数据并返回筛选结果。ArkUI 控件、数据库和系统 Context 都没有进入纯函数。九、为什么要把规则写成纯函数filterHomeReports(reports, query, nowMs)没有 UI 状态、没有数据库调用、没有全局单例也不修改输入数组。因此它可以在 Node 环境直接导入测试使用固定时间验证日期边界构造最小记录验证每个字段在重构页面时保持业务结果不变在未来远端分页前复用同一契约作为参考。纯函数不代表所有筛选都必须在内存完成。数据量增长后可以把可下推条件转换为 RelationalStore 谓词或服务端查询但应该先保留一份明确的行为契约和契约测试。十、组合筛选测试不能只测单条件单独测试“类型能筛选”“类别能筛选”仍无法发现 OR 泄漏。项目构造四条记录一条六个条件全部命中一条类型错误一条地点错误一条日期错误。然后一次性传入完整查询constresultfilterHomeReports(records,query({keyword:反光,type:FOUND,category:箱包,areaKeyword:第一教学楼,timeRange:TODAY,color:灰色}),NOW);assert.deepEqual(result.map(itemitem.id),[match]);只有match留下才能证明所有维度是 AND。再配合单条件边界测试可以同时获得覆盖面与定位能力。十一、测试还覆盖了哪些契约当前report-filter-policy.test.mjs还检查权威分类值稳定且唯一地点 value 唯一label/value/filterKeyword 都非空DRAFT/HIDDEN/WITHDRAWN被排除类别精确匹配地点片段匹配颜色不读取私密字段今天、近 3 天、近 7 天边界包含正确未来日期和非法日期不进入活动时间筛选。这些测试不依赖 ArkUI 和 RelationalStore失败时可以直接定位规则而不是从整页交互倒推原因。十二、性能边界与演进方向当前项目是单机演示规模列表先从 Repository 读取再在内存执行六维筛选复杂度约为 O(n)。对几十或几百条本地记录足够直观。数据量增长后可以分层优化类型、状态、类别、事件日期下推到 RelationalStore为类别、状态和日期增加合适索引关键词改为专门搜索索引或服务端检索保留纯函数作为小数据回退和契约对照对分页结果明确筛选发生在分页前还是分页后。不能直接对一页数据做本地筛选后宣称得到全量结果那会把真实命中项留在未加载页面。十三、空态与用户反馈筛选结果为空不等于系统异常。页面需要区分Repository 读取失败当前没有任何公开记录有记录但被筛选条件全部排除权限或初始化仍在加载。对于“筛选后为空”应显示当前条件摘要并提供一键清空筛选。不要把六个控件分别重置却遗漏某个隐藏条件也不要在空态里暗示数据被删除。十四、真实验证与证据边界本地规则测试可通过下面的项目脚本运行powershell-ExecutionPolicy Bypass-File.\scripts\test-report-filter.ps1它能证明固定输入下的纯规则行为不能证明 RelationalStore 查询、页面弹窗、键盘输入、响应式布局或真实设备性能。设备层还应验证连续选择六个条件、返回后条件是否保持、空态是否正确、撤回/隐藏后是否即时消失、长关键词和小屏布局是否可用。各项证据必须独立记录。十五、维护筛选规则的检查表新增或修改一个筛选维度时要同时检查查询模型是否有稳定字段页面控件输出的是 label 还是 valueService 是否仍只有一个筛选入口归一化和空值语义是否明确是否读取了不应公开的字段与其他条件是否保持 AND固定组合用例是否能发现 OR 泄漏数据量增长后是否需要下推查询空态和清空筛选是否同步更新。这份清单比在页面里继续增加if更能保护长期一致性。十六、新增筛选维度前的影响分析再增加“品牌”“楼层”或“校区”时不能只在页面多放一个控件。需要先确认数据源是否已有稳定字段、旧记录如何兼容、空值是否放行、查询模型如何表达、Service 是否仍保持单一入口以及新条件与现有六维是 AND 还是产品明确要求的分组 OR。如果新字段只存在于显示文案中直接用字符串模糊匹配会把数据质量问题转移到筛选层。更稳妥的做法是先补齐标准值与迁移规则再扩展纯函数和固定数据集页面最后接入这样每层都有明确合同。十七、错误结果、性能退化与回滚策略筛选错误不一定表现为崩溃更常见的是结果过多、结果为空或某些历史记录静默消失。发布前应准备宽条件、窄条件、冲突条件、非法日期、旧词表值和私密字段污染等数据集并记录每组预期 ID而不是只验证结果数量。当数据量增长后若全量读取再过滤导致卡顿应先以同一组契约测试固定行为再把状态、类型、日期等可索引条件下推到 Repository。回滚时恢复到旧查询实现同时保持查询模型和页面输入不变避免性能优化演变成用户可见语义变更。十八、从单元测试到设备验收的证据链纯函数测试证明确定输入会得到确定结果Repository 测试证明真实字段能被正确映射ArkUI 设备验收证明筛选控件、返回恢复、空态和重置按钮能够驱动相同查询。三层证据缺一不可也不能互相替代。建议把固定数据集、查询对象、命中 ID、运行命令和设备结果写入同一验收记录。若未来修改分类词表或时间规则只需重放记录即可判断变化是有意产品调整还是回归缺陷。对于尚未进行的真实设备性能与跨时区测试应明确标记not run。十九、本文小结六维组合筛选的难点不是写出六个布尔表达式而是统一数据契约、状态门禁、归一化方式和隐私边界。“寻迹校园”使用HomeReportQuery收拢输入由ReportService读取权威数据再交给纯函数filterHomeReports()以 AND 语义处理关键词、类型、类别、地点、时间和颜色。测试既验证全组合只留下唯一命中也验证历史状态、精确/片段匹配和私密字段隔离。系列导航第 18 篇 / 共 50 篇。上一篇《编辑、撤回、删除与结案》下一篇《今天/近 3 天/近 7 天的自然日边界》。