1. 需求理解与产品定位
先说结论:这三个月我反复打磨的“rea”,核心思路就是把“re-”这个前缀的语义价值吃透——你手上同时堆着阅读器、研究笔记、素材收藏夹、灵感记录本好几个应用,每次从A跳到B都要重新适应一遍交互逻辑,这种割裂感本身就是最大的痛点。rea要做的不是又一个聚合工具,而是一个把“读、研、记、憩”四件事串成一条流的工作台。
先说清楚这个标题的拆法。rea可以拆成read(阅读)、research(研究)、record(记录)、recreation(休憩)四条功能线,它们共享同一个re前缀,天然就有“回归、反复、重新”的意味——这正好贴合知识工作的真实路径:读一遍是输入,咀嚼是加工,重访是巩固,输出是沉淀。所以rea的产品定位不是“某某领域的替代品”,而是“个人知识工作流的统一容器”。
目标用户我锁定在两类人身上。第一类是内容创作者和研究者,他们每天要读大量长文、做标注、攒素材,最后还要输出文章或报告,最痛的是素材散落在不同工具里,检索和引用都要来回切换。第二类是深度阅读爱好者,不满足于“看完即弃”,希望读完还能回看、还能把自己的思考沉淀下来。这两类人的共同点是:频繁处理长文本、需要跨时间维度的内容回溯、对信息组织有超过普通人的洁癖。
很多人会问:市面上已经有阅读器、笔记软件、稍后读工具,为什么还要做rea?我的回答是:它们解决的是单点问题,而rea解决的是连接问题。比如你可能用A做稍后读,用B做笔记,用C做长文排版,三个工具的数据格式互不相通,标签体系各说各话,每次迁移都是一场灾难。rea想做一个“中间层”,把不同来源的内容统一成一套格式,用一套组织逻辑贯穿始终。这才是“统一容器”的真正含义——不是把功能堆在一起,而是把内容流转的损耗降到最低。
在动手前,我先做了一个月的用户访谈,样本虽然不大,但反馈高度一致:他们的核心诉求不是“功能更多”,而是“找回内容的掌控感”。有人说“我收藏了三百篇文章,但基本没再打开过”,有人说“读完一本书想写点想法,发现自己根本找不到当时的划线”。这些真实的声音直接影响了rea的功能取舍——不追求功能的广度,而是把每一条功能线的“完成度”做透。
2. 方案设计与选型解析
2.1 方案对比:为什么不做“超级应用”?
立项时我列过三个备选方案:
- 方案A:做一个全新独立应用,从零搭建阅读器、笔记、收藏、统计等全部功能。
- 方案B:做一个轻量“胶水层”,只做内容格式统一和跨应用的连接,不碰具体功能。
- 方案C:从“标记-收集-回访-输出”这四件事切入,做一套完整的闭环体验,同时开放外部内容导入。
方案A的问题在于工程量失控,光是稳定好用的阅读渲染引擎就够喝一壶,更别提笔记编辑器、标签系统、全文检索这些深水区,单人开发两三年也未必能打磨到满意的完成度。方案B虽然轻,但依赖外部应用的接口稳定性,一旦对方改版或限制接口,整个体验就会崩掉,而且“胶水层”的感知价值太弱,用户很难形成使用习惯。最终我选了方案C——既做闭环体验,又用开放导入的方式控制成本。
2.2 技术栈与工程架构
rea的整个技术栈选择,遵循的是“个人项目也要奔着长期维护去”的原则,不盲目追新,所有选型都有明确理由:
- 数据层:底层统一用Markdown + JSON的双轨结构存储。Markdown负责内容本身,保证用户随时可以导出、迁移、被其他工具解析;JSON负责元数据(标签、阅读进度、划线位置、访问时间等)。为什么不用SQLite当主存储?因为rea的核心承诺是“数据自主可控”,纯文本格式是最后的保险。SQLite不是不能用,但引入后数据的透明性、可迁移性都会打折扣,个人项目没必要背这个包袱。JSON单独做一份索引,是方便功能层快速查询而做的缓存,严格来说属于“可重建数据”,丢了也不影响内容主体。
- 阅读引擎:没有自己造轮子,而是基于开源的文本排版引擎做二次开发,重点处理长文重排、翻页/滚动切换、字体渲染,以及批注锚点定位这四块。批注锚点是阅读器里最容易翻车的地方,很多现成引擎对重排后的文本锚点支持不稳定,我会在3.3节详细讲方案。
- 全文搜索:因为纯文本格式对分词不太友好,直接上全文索引开销大,所以我用“标题+标签+目录结构”的加权匹配先做一轮召回,再对候选内容做轻量正文搜索。实测下来,绝大多数场景都能在几百毫秒内出结果,远比一上来就建全文索引更可控。
- 界面层:沿用单一Web技术栈重绘全部界面,核心考虑是“一套代码多端复用”,同时Web技术栈在排版、主题定制、动效上的生态成熟,能大幅降低个人项目维护多端的成本。进一步说,rea的界面布局本身也是内容信息架构的一部分——左侧是内容树和标签,中间是阅读区,右侧是划线、批注和自动化动作按钮,这个三栏布局和很多文档工具类似,但细节上做了大量针对长文阅读的优化。
2.3 关于“re-”语义树的取舍
前面提到rea可以拆成四件事,但真正做功能矩阵时,我并没有四等分,而是把“读”和“研”作为主线,“记”作为暗线,“憩”做成轻量模式而非独立模块。
为什么这么分配?因为访谈数据显示,绝大多数用户的工作流是“先读后研”:读是入口,研是深化,记是贯穿全程的细微信号,憩则是阅读间隙的放松需求。如果我把四个方向做成并列的四个大模块,界面会很臃肿,用户反而不知道重点在哪里。所以最终的交互结构是:默认进入阅读视图,阅读过程中的全部操作(划线、批注、收集)都是“轻提醒”式的后台动作,不打断阅读流;而右侧栏集中呈现所有可执行的研究动作。这个“默认轻、按需重”的设计,才是方案C的精髓。
3. 实操过程与核心环节实现
3.1 功能矩阵与内容模型设计
这一步是整个项目的地基。我做了两件事:梳理功能矩阵,以及定义统一的内容模型。
功能矩阵我按“输入-处理-输出”的消费链路来组织:
- 输入层:直接粘贴网页剪藏内容、上传Markdown/TXT/EPUB文件、通过草稿箱暂存碎片想法。
- 处理层:阅读视图、划线高亮、批注、标签管理、全文搜索、重访提醒。
- 输出层:一键导出带上下文引用的Markdown笔记、生成内容摘要、按主题打包素材包。
内容模型方面,所有导入的内容统一归一化为“文档—区块—标记”三层结构:
- 文档:内容的最高层级,对应一篇文章、一个章节或一个草稿。
- 区块:文档内的自然语义块,比如段落、小标题、引用块。这是rea实现精确引用的基础——划线不只记录“第几行到第几行”,而是记录“第几个区块到第几个区块”。
- 标记:用户对区块做的操作,包括划线、批注和标签。
举个例子,你在某篇文章的第三段划了一句“注意分布式事务的最终一致性”,rea存的不是“第3段第4句到第5句”,而是“在文档id=xxx的区块id=3中,标记type=highlight”。这样即使你在其他文档里引用了这条批注,再回来看时仍然能精确定位到原文。这个三层结构是后面所有功能的支点,比如“重访提醒”的本质就是“找到标记数量不变但最近7天没有访问过的文档”,“素材打包”的本质则是“按标签筛选区块并输出成新文档”。
3.2 界面布局与视觉规范
rea的界面按三栏布局展开,但我把中间阅读区的面积做到了极限,左右两栏都是可折叠的。这个设计来自一个很朴素的观察:多数阅读类工具的问题是“功能抢了内容的戏”,左右栏永远张开着,把阅读区挤成窄窄一列。rea的做法是默认收起右栏,鼠标移到边缘时自动滑出,这个交互既保证了沉浸感,又不损失功能可达性。
视觉规格我定了几条硬性规范:
- 背景色:阅读区不做纯白,而是用暖灰调,长时间读眼睛不易累。
- 字体:正文用系统默认的无衬线字体,等宽字体只保留给代码块和路径显示。读长文时衬线/无衬线其实是个人习惯问题,但系统无衬线在跨设备上一致性最好。
- 字重与行高:正文设为标准字重,行高调到大间距;批注文字和标签弱化为次级色,降低视觉噪声。
- 高亮颜色:只提供四色高亮(黄、绿、蓝、红),强迫用户克制,避免满屏荧光。
这里有一个被很多人忽略的细节:rea的高亮不是绘制在文本上的“半透明覆盖层”,而是用“背景色块+前后文本嵌入”的方式实现的。前者在高亮内容换行时会出现斑驳的断层,后者能保证跨行高亮在视觉上是干净的整块色带。虽然实现成本高一些,但阅读体验的提升非常明显。
3.3 阅读引擎与批注锚点的实现细节
阅读引擎二次开发时,最头疼的就是批注锚点定位。源文本经过排版引擎的重排之后,每个字符在渲染流里的绝对位置是动态的,你在屏幕上的划线动作要能稳定映射回内容模型里的区块ID,不能因为滚动、换行、字号调整就漂移。
我的方案是两层映射:渲染层维护一个“字符偏移到屏幕坐标”的索引,每次用户划线时,先把屏幕坐标换算成字符偏移,再把字符偏移换算到区块ID与标记范围。存储层则直接存区块ID和范围,不存屏幕坐标。这样只要文档内容不变,任何时刻回访划线位置都能重新定位,哪怕你换了设备、换了字号。
实测下来,这个方案在大多数长文场景非常稳定,唯一的短板是超大文档(几万字以上)首次渲染时建索引会有半秒到一秒的卡顿,解决方案是懒构建——只在用户进入阅读视图时构建,并用异步任务避免阻塞UI。另外,导入外部内容时我会做两遍校验:第一遍去掉原排版里的控制字符和无效标签,第二遍重新计算区块边界,避免脏数据污染锚点。
3.4 标签体系与“自动动作”规则
标签体系是知识管理里最容易失控的地方。rea没有做“无限层级标签”,只做一级标签加子标签,最多两层。层级越多,维护成本越高,最后一定变成一堆无人认领的分类垃圾。实际使用中,用户对内容的记忆方式以自然语言为主,所以rea支持“自然语言标签”,你写“等老了再读”“关于分布式锁”都可以,系统会拆出关键词做检索。
更有意思的是“自动动作”规则,这是我在访谈中发现的真实需求。很多用户希望“读完标记为止的内容后,给自己一个后续动作”,但常规工具不支持,于是rea内置了一个规则引擎,允许用户定义“触发条件—自动动作”的配对。比如:
- 条件:标记超过5次 + 最近7天未访问 → 动作:自动移入“待重访”清单。
- 条件:文档标签包含“灵感” → 动作:每周日晚生成一份灵感摘要,推送到草稿箱。
- 条件:收藏的网页超过90天未读 → 动作:自动发送一封“还读不读”的提醒信。
这个设计的核心价值不是自动化本身,而是让内容系统从“被动存储”变成“主动交互”。有一点必须说明:自动动作虽然叫“自动”,但所有触发都会写入操作日志,用户随时可以回溯系统干了什么。个人工具可以适度智能,但绝不能变成黑箱。
4. 常见问题与排查技巧实录
4.1 内容导入乱码与格式丢失
导入外部网页时,最常遇到的是两个问题:全角半角符号混乱和段落结构丢失。前者的症状是引号和括号变样,后者的症状是整个长文变成一大坨无法分段的文本。
排查的顺序是先看源内容是用什么方式捕获的。如果是直接复制网页正文,大概率会带入隐藏的控制字符;如果是通过剪藏插件抓取,往往是内容被压缩成单行结构。我的处理是:写一个清洗管道,先按“标题标签”的层级关系重建段落边界,再做符号正则替换,最后按连续段落重新划分区块。清洗完的数字用“源字数→清洗后字数”的对比做兜底检查,如果减少超过30%就要人工看一下是不是误删了大段内容。
4.2 批注丢失或漂移
批注漂移的本质是内容模型和渲染层的映射出了问题。最常见的原因是:导入时清洗管道没有跑完全部区块,导致后续的锚点索引错位。
排查这类问题,我先检查“标记所在的区块ID是否存在”,再看字符偏移是否在区块的合理范围内。还有一个隐蔽场景:同一篇文章被重复导入,容易产生内容相同但ID不同的两份文档,用户的批注挂在旧文档上,阅读时看到的是新文档,表现就是“批注不见了”。我的解决方式是导入前做内容指纹校验——对正文算一个哈希值,如果在库里已存在,就提示用户“合并到已有文档”,而不是无脑新增。
4.3 全文搜索召回率低
这个问题在纯文本架构下很常见,因为中文分词器的效果直接影响搜索质量。我不想引入沉重的分词依赖,所以用“标题权重+标签权重+目录命中+正文轻搜索”的多路召回方式。如果用户搜不到想找的内容,优先建议检查标签是否写清楚了,其次检查全文检索关键词是否过于宽泛。还有一种情况是内容本身是扫描版PDF导入,文本层缺失导致搜不到,这种情况属于内容源的物理限制,只能建议用户在导入前做OCR。
4.4 常见问题速查表
| 问题 | 原因 | 解决方式 |
|---|---|---|
| 导入后段落全挤在一起 | 清洗管道未识别结构边界 | 重建段落边界并重新分区 |
| 划线后重开文档位置漂移 | 锚点索引未建立或错位 | 检查区块ID映射,重建锚点索引 |
| 重复导入造成内容重复 | 缺少内容指纹校验 | 导入前算哈希,提示合并 |
| 全文搜索找不到内容 | 标签缺失或关键词宽泛 | 检查标签、切换关键词重试 |
| 自动动作未触发 | 规则条件设置冲突 | 查看操作日志,检查触发条件 |
| 超大文档卡顿 | 首次渲染索引构建阻塞 | 启用懒构建和异步加载 |
5. 经验沉淀与后续扩展
5.1 个人项目如何保持长期动力
做rea这种想“长期用”的项目,最大的敌人不是技术难度,而是自我怀疑。中间有段时间功能越加越多,反而觉得项目变味了,后来我做了次“功能负优化”,主动砍掉了三个看上去很酷但实际没人用的功能,其中包括一个自动生成“本周阅读报告”的模块。砍完之后产品反而更顺了,这让我学会一条原则:个人项目的功能只能是“用出来的”,不能是“想出来的”。
5.2 数据自主性是底线
rea的数据格式从第一天就是纯文本,没有绑定任何私有云服务。后来被问到最多的问题是如何导出到别的工具,我的回答永远是:不用导出,因为rea的数据本身就是Markdown和JSON,格式化输出只是加一层壳。这个概念很多用户第一次听到会愣住,但用久了就会觉得安心——内容永远是自己的,工具只是临时的容器。
5.3 后续可以继续做的事
迭代方向上,我目前最想完善的是“跨文档关联”和“定期回顾日历”。跨文档关联指的是从“某篇文档里引用了另一篇文档的批注”这个行为出发,自动生成两条文档之间的链接,并呈现在阅读视图中。定期回顾日历则是把“重访提醒”从单篇维度升级到时间维度,按遗忘曲线去规划回顾节奏。技术上都不算复杂,难的是如何把提醒做得很轻、不变成新的负担。
5.4 最后分享一个实用小技巧
自动动作规则我用了段时间后,发现与其追求完全自动化,不如把触发动作设成“生成建议,但不自动执行”。比如“待重访”清单只负责把候选内容汇总起来,是否打开、何时打开由我自己决定。工具可以帮我筛选信息,但决策权还是留在自己手里,这种分寸感,可能是rea教给我的最重要的事。