在这里插入图片描述
在这里插入图片描述

实例:搜索历史记录(SearchHistory)|技术:种子注入 + trim 预淘汰

一、种子数据的设计目标

实例 7 的种子数据要撑起「标签流」页面,设计目标:

  1. 12 条热词:超出 MAX_KEEP=10 上限——故意超限,让 trim 淘汰逻辑首次运行就被触发,展示「保留最近 10 条」的效果;
  2. 话题相关性:热词围绕「HarmonyOS 开发」主题(HarmonyOS 开发、ArkTS 语法、SQLite 实战…),像一个开发者搜索历史,有真实感;
  3. 次数梯度:部分词 count > 1(HarmonyOS 开发 ×8),触发次数徽标展示;
  4. 时间梯度:从 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 条」的自动淘汰。

五、验证方法

  1. hilog 日志:搜 SearchHistoryDao,看到「已填充 12 条搜索历史种子数据」;
  2. 页面显示:标签流 10 个胶囊,含次数徽标;时间线 10 行;
  3. 数据库直查
    SELECT COUNT(*) FROM search_history;  -- 10(12 注入,trim 淘汰 2)
    SELECT keyword, count FROM search_history ORDER BY search_time DESC;
    -- 首行 HarmonyOS 开发 ×8,末行 数据持久化(卡片开发/元服务已被淘汰)
    
  4. 淘汰验证:插入一条新词(页面搜索),观察最旧的被挤出,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 条搜索历史种子数据');
}

两个设计点值得强调:

  1. 判断依据是 COUNT 而不是 id:清空历史后表还在,COUNT=0 才重新注入;用「查第一条」判断,遇到空表要额外处理 null,COUNT 语义更干净;
  2. 注入与淘汰在同一方法内完成trim 与种子共享同一套淘汰逻辑,而不是「插 10 条正好卡上限」——后者一旦 MAX_KEEP 调整(如改成 8),种子就立刻越界,与页面参数耦合。

十一、注入后的标签流与热词榜效果预测

首次进入页面,initSeedData 注入 12 条 → trim 淘汰 2 条 → 页面拿到 10 条,各区块效果预测如下:

区块 预期呈现 验证点
标签流首行 HarmonyOS 开发 ×8(1 小时前) 时间倒序生效
徽标层次 高频词带「×N」蓝徽标,低频词纯文本 count 梯度生效
标签流末行 数据持久化 / 网络请求(5 天前) 最旧存活边界正确
时间线 今天 4 行 → 昨天 2 行 → 更早逐行递减 相对时间换算正确
标题统计 「保留最近 10 条」且实际 10 条 12−2=10 成立

交互效果预测(演示脚本的完整版):

  1. 点「HarmonyOS 开发」→ 标签流刷新,该词仍在首位,次数 8→9(徽标实时变化);
  2. 连续搜索 2 个新词(组件通信、路由跳转)→ 弹窗组件、数据持久化依次被挤出,总数恒为 10;
  3. 长按删除「列表懒加载」→ 总数变 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 篇)完全一致,种子四要素(幂等判断、相对时间、场景叙事、路径共享)跨实例复用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐