把心事存进鸿蒙:ArkTS 为日记本设计长文本表与时间戳字段
实例电子日记本Diary技术长文本存储、时间分组查询、关键词搜索一、业务需求分析日记本的数据形态与记账本有何不同前三个实例我们处理了「任务清单」短文本状态、「通讯录」结构化字段和「记账本」数值时间。到了实例 4「电子日记本」数据形态发生了一次质变主体内容从「字段」变成了「长文本」。日记应用的核心业务需求可以归纳为五点写日记标题 正文可能上千字的长文本 心情 天气 地点一次保存按时间轴浏览日记天生是「按时间组织的个人编年史」页面要按日期/月份分组展示时间线是主视觉关键词搜索用户想「找那天写日记提到爬山的记录」需要标题/正文双字段模糊搜索编辑与删除日记写错了要能改、能删轻量统计总篇数、本月篇数支撑页面头部信息。对比记账本日记本的数据特点维度记账本实例 3日记本实例 4主数据金额 REAL 分类 TEXT正文长文本 TEXT时间角色统计维度SUM/GROUP BY组织维度时间轴分组检索方式分类精确匹配关键词 LIKE 模糊更新频率几乎不更新删了重记经常编辑改日记这些差异决定了表结构设计的不同侧重。下面进入字段设计。二、字段设计表每一列都为「时间轴」服务字段名类型约束说明idINTEGERPRIMARY KEY AUTOINCREMENT自增主键titleTEXTNOT NULL标题contentTEXTNOT NULL正文长文本可数千字moodTEXTDEFAULT ‘’心情 emoji…weatherTEXTDEFAULT ‘’天气晴/多云/雨…locationTEXTDEFAULT ‘’地点created_timeINTEGERNOT NULL创建时间戳毫秒updated_timeINTEGERNOT NULL最近修改时间戳毫秒设计要点逐条拆解1. content 用 TEXT 存长文本不需要分表。这是初学者最容易纠结的问题「正文那么长要不要单独建一张 content 表」答案是不需要。SQLite 单条 TEXT 字段可以存数百 MB一篇几千字的日记约 10KB毫无压力。RDB 的设计原则是「一行一个业务实体」——一篇日记就是一行正文是它的一个属性。强行拆表反而增加 JOIN 复杂度得不偿失。2. mood 直接存 emoji 字符。心情用 emoji//而非数字编码。为什么这里不用记账本 type 的 0/1 数字编码因为心情没有「参与计算」的需求——不做 SUM、不做 GROUP BY 统计只有展示。当枚举值只用于展示时直接存可读文本UI 零转换是最务实的方案。判断用数字还是文本的标准这个字段会不会参与聚合运算会如记账本 type就编码成数字不会如心情就存文本。3. location 取代「标签」体系。早期的文章版设计里有tags逗号分隔标签字段落地版将其替换为location地点。原因个人日记场景下「标签」概念偏重而「地点」新家/公司/书房更贴近日记的自然属性也与文章版 4-2 的时间轴节点设计一致。如果你需要标签体系逗号分隔 LIKE 查询的模式可以随时加回来——SQLite 对字符串的处理非常灵活。4. created_time 与 updated_time 双时间戳。这是内容型应用的标准配置created_time创建时刻永不变更updated_time每次编辑刷新时间轴排序用它——「最近修改的日记排前面」比「最早写的排前面」更符合用户心智刚编辑完的日记大概率还想继续看。5. 为什么只给 created_time 建索引落地版的建表 SQL 为created_time建了idx_diary_time索引排序、按月份LIKE 2025-06%过滤都走它。其实更严谨的做法是索引updated_time排序字段但个人日记几百条数据两者性能差异不可感知选择 created_time 是因为它的值域稳定插入后不变索引不会因频繁更新而失效。这个细节体现了「索引服务于查询」的朴素原则。三、建表 SQL把设计变成现实CREATETABLEIFNOTEXISTSdiary(idINTEGERPRIMARYKEYAUTOINCREMENT,titleTEXTNOTNULL,contentTEXTNOTNULL,moodTEXTDEFAULT,weatherTEXTDEFAULT,locationTEXTDEFAULT,created_timeINTEGERNOTNULL,updated_timeINTEGERNOTNULL);CREATEINDEXIFNOTEXISTSidx_diary_timeONdiary(created_time);注意三点IF NOT EXISTS幂等重复执行不报错这是 getStore 里建表语句的统一要求DEFAULT mood 有默认值插入时可以省略代码更简洁NOT NULL DEFAULT ‘’weather、location 可空但默认空串避免 NULL 泄漏到 UI 层页面|| 兜底是双保险。四、DiaryDao 封装把 SQL 关进类里数据层核心是DiaryDao职责边界与前面实例一致只管数据库不管 UI。先看实体接口exportinterfaceDiary{id:number;title:string;content:string;// 长文本正文mood:string;// 心情 emoji…weather:string;// 天气晴/多云/雨…location:string;// 地点createdTime:number;// 日记时间戳毫秒updatedTime:number;}字段命名依然是「DB 蛇形 ↔ 代码驼峰」的双风格映射集中在rowToDiary方法privatestaticrowToDiary(result:relationalStore.ResultSet):Diary{return{id:result.getLong(result.getColumnIndex(id)),title:result.getString(result.getColumnIndex(title)),content:result.getString(result.getColumnIndex(content)),mood:result.getString(result.getColumnIndex(mood))||,weather:result.getString(result.getColumnIndex(weather))||,location:result.getString(result.getColumnIndex(location))||,createdTime:result.getLong(result.getColumnIndex(created_time)),updatedTime:result.getLong(result.getColumnIndex(updated_time)),};}与文章版代码的关键差异这是本实例读者最容易踩的坑文章版用row: relationalStore.ValuesBucketrow.id as number强转落地版改用ResultSetgetColumnIndexgetLong/getString——前者拿到的getRow()是弱类型的键值容器强转不安全后者类型安全、NULL 可兜底。文章版静态方法里写this.store落地版一律DiaryDao.store——ArkTS 禁止静态上下文使用thisarkts-no-this-in-static。文章版 context 用common.UIAbilityContext落地版统一common.Context基类复用性更强。getStore单例复用模式不变staticasyncgetStore(context:common.Context):PromiserelationalStore.RdbStore{if(DiaryDao.store){returnDiaryDao.store;}constconfig:relationalStore.StoreConfig{name:diary.db,securityLevel:relationalStore.SecurityLevel.S1,};DiaryDao.storeawaitrelationalStore.getRdbStore(context,config);// 建表 建索引见上节 SQLhilog.info(DOMAIN,TAG,日记表初始化成功);returnDiaryDao.store;}五、核心 CRUD写、查、改、删插入写日记staticasyncinsert(context:common.Context,d:Diary):Promisenumber{conststoreawaitDiaryDao.getStore(context);constvalues:relationalStore.ValuesBucket{title:d.title,content:d.content,mood:d.mood,weather:d.weather,location:d.location,created_time:d.createdTime,updated_time:d.updatedTime,};returnawaitstore.insert(DiaryDao.TABLE,values);}注意content作为字符串直接塞进 ValuesBucket——长文本无需特殊处理SQLite 自动分配存储这是 TEXT 类型的天然优势。全部日记按修改时间倒序staticasyncqueryAll(context:common.Context):PromiseDiary[]{conststoreawaitDiaryDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(DiaryDao.TABLE);predicates.orderByDesc(created_time);constresultawaitstore.query(predicates);returnDiaryDao.collect(result);}排序字段用created_time与索引一致页面时间轴按创建时间分组展示语义上更符合「日记编年史」。更新编辑日记刷新 updated_timestaticasyncupdate(context:common.Context,d:Diary):Promisenumber{conststoreawaitDiaryDao.getStore(context);constvalues:relationalStore.ValuesBucket{title:d.title,content:d.content,mood:d.mood,weather:d.weather,location:d.location,updated_time:d.updatedTime,};constpredicatesnewrelationalStore.RdbPredicates(DiaryDao.TABLE);predicates.equalTo(id,d.id);returnawaitstore.update(values,predicates);}注意 update 不更新created_time——创建时间不可变只刷updated_time这是双时间戳的协作方式。删除与计数staticasyncdelete(context:common.Context,id:number):Promisenumber{conststoreawaitDiaryDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(DiaryDao.TABLE);predicates.equalTo(id,id);returnawaitstore.delete(predicates);}staticasynccount(context:common.Context):Promisenumber{conststoreawaitDiaryDao.getStore(context);constresultawaitstore.querySql(SELECT COUNT(*) AS c FROM${DiaryDao.TABLE});lettotal0;if(result.goToNextRow()){totalresult.getLong(result.getColumnIndex(c));}result.close();returntotal;}六、时间分组查询按月份捞日记时间轴页面的核心需求选中某个月份只看那个月的日记。落地版提供queryByMonthstaticasyncqueryByMonth(context:common.Context,month:string):PromiseDiary[]{conststoreawaitDiaryDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(DiaryDao.TABLE);predicates.like(created_time,${month}%).orderByDesc(created_time);constresultawaitstore.query(predicates);returnDiaryDao.collect(result);}这个方法的巧妙之处like(created_time, 2025-06%)用 LIKE 前缀匹配模拟「时间范围查询」。因为 created_time 存的是毫秒时间戳的字符串形式——不对等等created_time 是 INTEGER 类型LIKE 匹配的是数字的字符串表示。2025-06-01 00:00:00的时间戳是1748707200000用LIKE 2025-06%匹配不到这是一个需要澄清的坑文章版的queryByMonth按updated_time LIKE的设计在 INTEGER 时间戳上是不成立的那是按文本日期存储时的写法。落地版页面DiaryPage采用更可靠的方式直接queryAll拉全量在内存里按fmtDate分组渲染时间轴节点。个人日记数据量小几十上百篇全量加载 前端分组是务实的选择若数据量大正确做法是between(startOfMonth, endOfMonth)毫秒区间查询——这与记账本实例的区间查询一脉相承。关键词搜索标题/正文双字段 LIKEstaticasyncsearch(context:common.Context,keyword:string):PromiseDiary[]{conststoreawaitDiaryDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(DiaryDao.TABLE);predicates.like(title,%${keyword}%).or().like(content,%${keyword}%).orderByDesc(created_time);constresultawaitstore.query(predicates);returnDiaryDao.collect(result);}LIKE %关键词%是包含匹配——标题或正文任意位置出现关键词即命中。.or()把两个条件连接为 OR 语义。注意LIKE 前导%会导致索引失效无法走 B 树全表扫描但日记本几百条数据扫描无压力。如果将来数据量到十万级应改用 FTS5 全文索引——那是另一个维度的优化本书暂不展开。七、技术要点对照表技术点实现方式生产价值长文本TEXT 字段直接存正文无需分表读写简单双时间戳created_time updated_time创建/编辑分离时间轴按编辑排序关键词搜索title/content OR LIKE双字段命中率高时间分组全量加载 前端按日期分组小数据量下的务实方案emoji 存储mood 直接存字符UI 零转换幂等建表CREATE IF NOT EXISTS重复启动不报错八、文章小结日记本数据层与记账本的数值聚合完全不同重心在长文本存储策略 双时间戳的时间轴组织 LIKE 关键词检索。TEXT 类型让长正文毫无负担双时间戳让「最近编辑优先」成为可能LIKE 让搜索零配置。下一篇4-2将展示这些数据如何被时间轴 UI 呈现——垂直时间轴 日期节点 心情天气卡片把数据变成故事。动手练习尝试给search增加第三个条件按天气过滤equalTo(weather, 晴)并观察 OR 与 AND 组合时 RdbPredicates 的链式写法。

相关新闻

Docker Desktop最新版安装踩坑全记录(Windows_Mac_Linux)【2026 4.74.0 终版】

Docker Desktop最新版安装踩坑全记录(Windows_Mac_Linux)【2026 4.74.0 终版】

✅ 适配版本:Docker Desktop 4.74.0(2026最新稳定版) ✅ 适配系统:Windows10/11、Mac Intel/Apple Silicon、Linux 全主流发行版 ✅ 阅读收益:一次性解决99%安装报错、启动失败、镜像超时、环境冲突问题 📝…

2026/10/26 0:56:56 阅读更多 →
率能SS6850H 20V/1A/双通道H桥驱动芯片,零待机电流与ENA使能控制(ESOP8),用于家电/打印机/电机驱动

率能SS6850H 20V/1A/双通道H桥驱动芯片,零待机电流与ENA使能控制(ESOP8),用于家电/打印机/电机驱动

在12V系统电机驱动应用中,封装形式的差异直接影响芯片的散热性能和PCB布局密度。领芯微(LEADPOWER)推出的SS6850H是一款2通道H桥驱动芯片,专为12V系统产品的电机驱动而优化。每个H桥可提供最大1A峰值电流和0.7A均方根电流&#xf…

2026/10/29 1:39:17 阅读更多 →
AI论文写作工具哪个最好?2026亲测

AI论文写作工具哪个最好?2026亲测

"开题报告改5版仍被打回","文献综述堆30篇却毫无逻辑","格式排版耗3天还不符合学校要求","AI生成内容被AIGC检测标红"——2026年高校AI学术规范全面收紧的背景下,毕业生在选择AI论文写作…

2026/10/24 16:33:44 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/28 23:48:14 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/28 7:28:00 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/28 9:02:11 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/28 22:10:30 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/28 9:00:54 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/28 7:29:02 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 16:58:40 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 9:01:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 19:25:09 阅读更多 →