十二条热词撑起榜单:鸿蒙搜索历史种子数据与标签流效果


实例:搜索历史记录(SearchHistory)|技术:种子注入 + trim 预淘汰
一、种子数据的设计目标
实例 7 的种子数据要撑起「标签流」页面,设计目标:
- 12 条热词:超出 MAX_KEEP=10 上限——故意超限,让 trim 淘汰逻辑首次运行就被触发,展示「保留最近 10 条」的效果;
- 话题相关性:热词围绕「HarmonyOS 开发」主题(HarmonyOS 开发、ArkTS 语法、SQLite 实战…),像一个开发者搜索历史,有真实感;
- 次数梯度:部分词 count > 1(HarmonyOS 开发 ×8),触发次数徽标展示;
- 时间梯度:从 1 小时前到 8 天前,时间线列表有层次。
二、12 条种子数据全表
| # | 关键词 | 次数 | 时间偏移 |
|---|---|---|---|
| 1 | HarmonyOS 开发 | 8 | 1 小时前 |
| 2 | ArkTS 语法 | 6 | 3 小时前 |
| 3 | SQLite 实战 | 5 | 5 小时前 |
| 4 | RDB 数据库 | 4 | 9 小时前 |
| 5 | 状态管理 | 7 | 1 天前 |
| 6 | 声明式 UI | 3 | 1 天 2 小时前 |
| 7 | 列表懒加载 | 2 | 2 天前 |
| 8 | 弹窗组件 | 5 | 3 天前 |
| 9 | 数据持久化 | 4 | 4 天前 |
| 10 | 网络请求 | 6 | 5 天前 |
| 11 | 卡片开发 | 3 | 6 天前 |
| 12 | 元服务 | 2 | 8 天前 |
为什么 12 条要超过 10 条上限? 这是刻意的设计——首次注入后 trim 会被调用,最旧的两条(卡片开发、元服务)被淘汰,最终页面显示 10 条。种子数据故意「超限」,让淘汰逻辑在真实数据下被验证,页面标题「保留最近 10 条」名副其实是 10 条而不是 12 条。
三、代码实现:注入 + 预淘汰
const now = Date.now();
const hour = 3600000;
const seed: HistorySeed[] = [ /* 12 条,见上表 */ ];
for (const s of seed) {
const values: relationalStore.ValuesBucket = {
keyword: s.keyword, search_time: s.searchTime, count: s.count,
};
await store.insert(SearchHistoryDao.TABLE, values);
}
await SearchHistoryDao.trim(context); // 注入后立即淘汰超限的
hilog.info(DOMAIN, TAG, '已填充 12 条搜索历史种子数据');
关键行:await SearchHistoryDao.trim(context)——注入 12 条后立即调用 trim,超出 10 条的部分(最旧的 2 条)被删除。种子数据与业务逻辑共享同一个淘汰方法,保证「种子注入」与「用户新增」走完全一致的路径——如果种子直接插 10 条绕过 trim,就和真实行为不一致了。
HistorySeed 接口:
interface HistorySeed {
keyword: string;
searchTime: number;
count: number;
}
三字段与表结构一一对应,极简。
四、注入后的页面效果预测
| 区块 | 预期效果 |
|---|---|
| 标题栏 | 「保留最近 10 条」(注入 12 淘汰 2 后正好 10) |
| 标签流 | 10 个白底胶囊:HarmonyOS 开发(×8 徽标)、ArkTS 语法(×6)、SQLite 实战(×5)… |
| 次数徽标 | HarmonyOS 开发、ArkTS 语法、SQLite 实战、RDB 数据库、状态管理等 6 个词显示蓝色「N次」徽标 |
| 时间线 | 10 行:今天 1 小时前 HarmonyOS 开发、今天 3 小时前 ArkTS 语法…6月X日 元服务已被淘汰 |
演示脚本:指着标签流说「这是最近的 10 条搜索」→ 点「HarmonyOS 开发」重新搜索 → 该词上浮到最前、次数变 9 → 再搜一个不在历史里的新词(如「组件通信」)→ 最旧的词被挤出(弹窗组件)→ 展示「保留最近 10 条」的自动淘汰。
五、验证方法
- hilog 日志:搜
SearchHistoryDao,看到「已填充 12 条搜索历史种子数据」; - 页面显示:标签流 10 个胶囊,含次数徽标;时间线 10 行;
- 数据库直查:
SELECT COUNT(*) FROM search_history; -- 10(12 注入,trim 淘汰 2) SELECT keyword, count FROM search_history ORDER BY search_time DESC; -- 首行 HarmonyOS 开发 ×8,末行 数据持久化(卡片开发/元服务已被淘汰) - 淘汰验证:插入一条新词(页面搜索),观察最旧的被挤出,COUNT 保持 10。
六、FAQ
Q1:为什么有的词次数是 8、6、5 这种梯度?
A:模拟「搜索频率差异」——高频词(HarmonyOS 开发 ×8)说明用户最近很关注,低频词(列表懒加载 ×2)偶尔搜过。次数梯度让标签流的徽标层次丰富,也验证 count 累计逻辑。
Q2:时间偏移为什么用「小时」粒度?
A:搜索历史的时效性强——1 小时、3 小时、5 小时前这种近时粒度让「今天」的时间线充实。用天做单位的话,前几条都显示「今天」,时间线就失去层次了。时间粒度匹配业务时效性。
Q3:为什么选「HarmonyOS 开发」这类词?
A:读者正在读本书(鸿蒙 × SQLite 实战),搜索历史用相关技术词(HarmonyOS 开发、ArkTS 语法、SQLite 实战)有场景代入感——像是一个「正在学习鸿蒙开发的开发者」的搜索记录。种子数据的选词服务于目标读者。
Q4:种子数据里可以加 emoji 吗?
A:可以,keyword 是 TEXT,存 emoji 无压力。但搜索词通常不含 emoji(用户不会搜「🍕」),保持纯文本更真实。表情符号更适合习惯名(实例 5)、商品图(实例 8)这类字段。
Q5:如果用户清空历史后重装,种子会重新注入吗?
A:清空只是删数据(表还在),initSeedData 的幂等判断依据是「COUNT > 0」——清空后 COUNT=0,下次进入页面会重新注入 12 条(再淘汰 2 条)。这是「Demo 永远有内容」的刻意设计,与 4-4 文章 Q9 一致。
Q6:12 条种子为什么要用相对时间而不是绝对时间?
A:与前面实例同理——相对时间(now - N*hour)保证任何时刻打开,历史都是「最近 8 天内」的搜索记录,时间线文案(今天/6月X日)始终合理。绝对时间会随时间推移而「过期」。
七、文章小结
本篇文章展示了实例 7 的种子数据:12 条技术热词(故意超限)+ 次数梯度 + 时间梯度 + 注入后预淘汰。设计核心是「种子数据与业务逻辑共享同一路径」——注入 12 条后调用 trim 淘汰 2 条,让页面上限逻辑在真实数据下被验证。种子数据到此已第五次出场,模板高度成熟:幂等判断、相对时间、场景叙事、路径共享,四个要素缺一不可。
下一篇(7-5)是实例 7 的收官文章,展示 SearchHistoryPage 全量代码与运行效果。
八、12 条热词完整档案:从偏移量到最终命运
前文表格只给了「时间偏移」,种子落库时写入的是绝对时间戳(searchTime = now - 偏移量)。以「15:30 首次注入」为参考时刻,把 12 条种子换算成「最后搜索时间」,并标注 trim 淘汰后的命运:
| # | 关键词 | 搜索次数 | 最后搜索时间(15:30 注入为例) | trim 后命运 |
|---|---|---|---|---|
| 1 | HarmonyOS 开发 | 8 | 今天 14:30 | 存活 · 时间线榜首 |
| 2 | ArkTS 语法 | 6 | 今天 12:30 | 存活 |
| 3 | SQLite 实战 | 5 | 今天 10:30 | 存活 |
| 4 | RDB 数据库 | 4 | 今天 06:30 | 存活 |
| 5 | 状态管理 | 7 | 昨天 15:30 | 存活 |
| 6 | 声明式 UI | 3 | 昨天 13:30 | 存活 |
| 7 | 列表懒加载 | 2 | 前天 15:30 | 存活 |
| 8 | 弹窗组件 | 5 | 3 天前 15:30 | 存活 |
| 9 | 数据持久化 | 4 | 4 天前 15:30 | 存活 |
| 10 | 网络请求 | 6 | 5 天前 15:30 | 存活 |
| 11 | 卡片开发 | 3 | 6 天前 15:30 | 被 trim 淘汰 |
| 12 | 元服务 | 2 | 8 天前 15:30 | 被 trim 淘汰 |
注意最后两列的对应关系:淘汰依据是 search_time(最旧),而不是 count(最少)。卡片开发只有 3 次但排在 11 位被淘汰,网络请求 6 次因 5 天前而存活——「时间决定存亡、次数决定徽标」,两个维度各司其职。
种子数组在代码里的真实写法(全部 12 条):
const now = Date.now();
const hour = 3600000;
const seed: HistorySeed[] = [
{ keyword: 'HarmonyOS 开发', count: 8, searchTime: now - 1 * hour },
{ keyword: 'ArkTS 语法', count: 6, searchTime: now - 3 * hour },
{ keyword: 'SQLite 实战', count: 5, searchTime: now - 5 * hour },
{ keyword: 'RDB 数据库', count: 4, searchTime: now - 9 * hour },
{ keyword: '状态管理', count: 7, searchTime: now - 24 * hour },
{ keyword: '声明式 UI', count: 3, searchTime: now - 26 * hour },
{ keyword: '列表懒加载', count: 2, searchTime: now - 48 * hour },
{ keyword: '弹窗组件', count: 5, searchTime: now - 72 * hour },
{ keyword: '数据持久化', count: 4, searchTime: now - 96 * hour },
{ keyword: '网络请求', count: 6, searchTime: now - 120 * hour },
{ keyword: '卡片开发', count: 3, searchTime: now - 144 * hour },
{ keyword: '元服务', count: 2, searchTime: now - 192 * hour },
];
九、种子设计复盘:热词梯度与时间分布
热词梯度:金字塔结构
12 条种子的搜索次数分布:8、7、6、6、5、5、4、4、3、3、2、2,呈现典型的金字塔结构:
| 梯度 | 次数区间 | 词数 | 代表词 | 设计意图 |
|---|---|---|---|---|
| 高频 | ≥ 6 次 | 4 | HarmonyOS 开发、状态管理 | 视觉锚点:徽标最醒目 |
| 中频 | 4~5 次 | 4 | SQLite 实战、弹窗组件 | 标签流主体 |
| 低频 | ≤ 3 次 | 4 | 列表懒加载、元服务 | 长尾,验证徽标边界 |
为什么要有梯度?搜索历史页面按时间排序,次数只影响徽标——没有梯度的话,要么全是「×8」要么全无徽标,页面就少了层次。梯度让标签流出现「高频词带徽标、低频词纯文本」的对比,一眼能看出用户的兴趣重心。
时间分布:近密远疏
12 条种子覆盖 8 个不同的「天」,但分布不均匀——越近越密:
| 时间段 | 词数 | 时间线文案 |
|---|---|---|
| 24 小时内 | 4 | 今天 14:30 / 12:30 / 10:30 / 06:30 |
| 1~2 天 | 2 | 昨天 15:30 / 昨天 13:30 |
| 2~8 天 | 6 | 前天到 8 天前各 1~2 条 |
这个分布是刻意的:近 24 小时 4 条,让「今天」分组充实;后面逐渐稀疏,时间线滚动有「近期密集、远期稀疏」的真实感。如果 12 条均匀分布在 12 天,时间线反而像假的。
十、initSeedData 幂等实现:COUNT 守卫
种子注入方法必须幂等——重复调用不产生重复数据。落地版用 queryCount 做守卫:
static async initSeedData(context: common.Context): Promise<void> {
const store = await SearchHistoryDao.getStore(context);
// 幂等判断:COUNT > 0 直接返回,防止重复注入
const cnt = await store.queryCount(
new relationalStore.RdbPredicates(SearchHistoryDao.TABLE)
);
if (cnt > 0) {
hilog.info(DOMAIN, TAG, '已存在搜索历史数据,跳过种子注入');
return;
}
// 首次注入:12 条相对时间种子(见第八节全表)
const now = Date.now();
const hour = 3600000;
const seed: HistorySeed[] = [ /* 12 条,见第八节 */ ];
for (const s of seed) {
const values: relationalStore.ValuesBucket = {
keyword: s.keyword,
search_time: s.searchTime,
count: s.count,
};
await store.insert(SearchHistoryDao.TABLE, values);
}
await SearchHistoryDao.trim(context); // 注入后立即预淘汰 2 条
hilog.info(DOMAIN, TAG, '已填充 12 条搜索历史种子数据');
}
两个设计点值得强调:
- 判断依据是 COUNT 而不是 id:清空历史后表还在,COUNT=0 才重新注入;用「查第一条」判断,遇到空表要额外处理 null,COUNT 语义更干净;
- 注入与淘汰在同一方法内完成:
trim与种子共享同一套淘汰逻辑,而不是「插 10 条正好卡上限」——后者一旦 MAX_KEEP 调整(如改成 8),种子就立刻越界,与页面参数耦合。
十一、注入后的标签流与热词榜效果预测
首次进入页面,initSeedData 注入 12 条 → trim 淘汰 2 条 → 页面拿到 10 条,各区块效果预测如下:
| 区块 | 预期呈现 | 验证点 |
|---|---|---|
| 标签流首行 | HarmonyOS 开发 ×8(1 小时前) | 时间倒序生效 |
| 徽标层次 | 高频词带「×N」蓝徽标,低频词纯文本 | count 梯度生效 |
| 标签流末行 | 数据持久化 / 网络请求(5 天前) | 最旧存活边界正确 |
| 时间线 | 今天 4 行 → 昨天 2 行 → 更早逐行递减 | 相对时间换算正确 |
| 标题统计 | 「保留最近 10 条」且实际 10 条 | 12−2=10 成立 |
交互效果预测(演示脚本的完整版):
- 点「HarmonyOS 开发」→ 标签流刷新,该词仍在首位,次数 8→9(徽标实时变化);
- 连续搜索 2 个新词(组件通信、路由跳转)→ 弹窗组件、数据持久化依次被挤出,总数恒为 10;
- 长按删除「列表懒加载」→ 总数变 9;再搜一个新词 → 总数回到 10 且无人被挤出(9+1=10 未超限)——说明 trim 只在超限时出手,删除是主动腾位,两者是「动态平衡」而非「一次性裁剪」。
十二、验证 SQL 与 FAQ
验证 SQL(DevEco 数据库工具直查)
-- 1. 总数:12 注入 − 2 淘汰 = 10
SELECT COUNT(*) FROM search_history;
-- 2. 淘汰对象:卡片开发、元服务应查不到
SELECT keyword FROM search_history WHERE keyword IN ('卡片开发', '元服务');
-- 3. 榜首与徽标数据:首行 HarmonyOS 开发 ×8
SELECT keyword, count, search_time FROM search_history
ORDER BY search_time DESC LIMIT 3;
-- 4. 幂等:连续调用两次 initSeedData,总数仍是 10
SELECT COUNT(*) FROM search_history;
-- 5. 梯度分布:次数按 2~8 呈金字塔
SELECT count AS 次数, COUNT(*) AS 词数 FROM search_history
GROUP BY count ORDER BY count DESC;
FAQ
Q1:种子注入为什么要做幂等?不加会怎样?
A:不加的话,每次进入页面都再插 12 条——第二次进入时 trim 会把旧 10 条全删掉只留新注入的 10 条,数据反复洗牌。queryCount > 0 一行守卫,保证种子只在「空表」时注入一次。
Q2:种子为什么要 12 条而不是正好 10 条?
A:正好 10 条时 trim 不触发,淘汰逻辑在 Demo 里永远不被验证,页面标题「保留最近 10 条」就成了口号。超限 2 条让 trim 在首次注入就执行,并让读者直观看到「12 条变 10 条」的过程——种子的价值之一是验证业务逻辑。
Q3:时间偏移为什么是 1/3/5/9 小时这种非均匀间隔?
A:均匀间隔(1/2/3/4 小时)会显得像程序生成的假数据。真实用户的搜索是「随机但近密远疏」的:1/3/5/9 小时模拟了「下午集中搜了几次、早上偶尔搜一次」的自然节奏。非均匀 + 近密远疏,时间线才有真实感。
Q4:清空历史后种子会重新注入吗?与 4-4 文章是否一致?
A:会。清空(DELETE 全表)后 COUNT=0,再次进入页面 initSeedData 重新注入 12 条(再淘汰 2 条)——这是「Demo 永远有内容」的统一设计,与 4-4 文章(Diary 清空后重新注入 12 篇)完全一致,种子四要素(幂等判断、相对时间、场景叙事、路径共享)跨实例复用。
更多推荐




所有评论(0)