- 后端
- 前端
- 社交
- 人工智能
【免费下载链接】NewsBlur
NewsBlur is a personal news reader that brings people together to talk about the world. A new sound of an old instrument.
NewsBlur 在推出 Saved Story Tagging(已保存故事标签)之后,为每个已保存故事补充了天然的第二个分类维度——来源站点(site),并上线了专门的 Saved Stories 视图:你可以按站点浏览已保存的故事,点击任一包含站点的文件夹即可聚合查看该文件夹内所有来源的收藏,还能借助全文搜索在所有已保存故事中检索。本文结合 NewsBlur 当前开源仓库的前后端实现,从使用方式、接口参数到 MongoDB 数据模型逐层拆解这一功能,帮助你完全掌握并复用这套"个人剪藏库"的构建思路。
功能背景:为什么需要"按站点"这个分类维度
2014 年初,NewsBlur 上线了 Saved Story Tagging(已保存故事标签),允许用户为收藏的故事打上自定义标签。在标签体系落地之后,官方博客指出一个客观事实:每个已保存故事都存在一个固有的、无需用户额外维护的分类维度——它来自哪个站点(site)。与其只允许用户通过站点去"筛选"已保存故事,不如直接构建一个专门的视图,让用户按站点浏览自己的收藏。
这个思路在功能页 templates/static/feature_saved_stories.xhtml 中被概括为一句定位:Your personal clipping library, searchable and shareable(一个可搜索、可分享的个人剪藏库)。功能上对应四条核心能力:
- 一键(或快捷键)保存任意故事;
- 为已保存故事添加自定义标签,按主题组织;
- 在侧边栏按标签浏览已保存故事;
- 在所有已保存故事与标签上进行搜索。
按站点视图正是在这套"标签库"之外,用站点作为另一套自动形成的索引。
核心功能一:按站点浏览已保存故事
在 Web 阅读器侧边栏中,Saved Stories(已保存故事)以虚拟文件夹的形式存在,点击即可进入/saved视图。前端路由逻辑位于 media/js/newsblur/reader/reader.js 的open_starred_stories:
- 无标签参数时,URL 导航到
/saved(this.active_feed = 'starred',侧边栏NB-starred-header被选中); - 点击某个标签时,URL 变为
/saved/<tag_slug>,例如/saved/linux; - 对应路由在 newsblur_web/urls.py 中声明:
^saved/(?P<tag_name>\d+)?与^saved/?都落到reader_views.index。
侧边栏的展开、折叠由 media/js/newsblur/views/sidebar.js 中的open_starred_stories/collapse_starred_stories事件驱动,并把starred作为一个可折叠的虚拟文件夹(NB-starred-folder)纳入Preferences.collapsed_folders管理。
按站点查看的实现依据:后端统计模型 apps/rss_feeds/models.py 中的MStarredStoryCounts同时记录了三种维度——tag(标签)、feed_id(站点)、is_highlights(高亮),其__unicode__分别输出Tag: xxx (n)、Feed: xxx (n)、Highlights: xxx (n)。starred_counts接口(apps/reader/views.py)把统计结果连同总量一起返回给前端;侧边栏据此渲染出按站点与按标签分组的收藏目录树,点击站点即可进入该站点维度下的已保存故事列表。
核心功能二:文件夹聚合——一个文件夹,看遍全部站点
新视图最有价值的一点在于它同样适用于文件夹(Folder):点击包含多个站点的文件夹,即可看到该文件夹内所有站点的已保存故事,相当于对收藏按你自己的信息架构(订阅文件夹划分)做了一次自动聚合。
从实现看,这套聚合能力建立在两个基础上:
- 站点维度统计已就绪:
MStarredStoryCounts为每个feed_id单独维护计数,文件夹天然拥有其下站点的集合,前端选中文件夹时即可把站点点位展开为聚合视图; - 故事数据按站点可追溯:
MStarredStory中保存了story_feed_id(故事所属站点)字段,后端查询时按user_id+ 排序/过滤条件取出故事,再通过story_feed_id回填订阅信息。
这与 NewsBlur 中"River of Stories"按文件夹聚合的思路一致:文件夹是用户的订阅分组,Saved Stories 视图复用这套分组,让收藏自动归位到对应的信息架构中,无需用户手工整理。
核心功能三:全文搜索——检索所有已保存故事
在按站点视图发布之前的一个月,NewsBlur 刚刚上线了全文搜索能力;按站点视图直接接入了这套搜索,使得所有已保存故事(无论按站点还是按标签浏览)都可以被直接检索。
后端搜索入口是 apps/reader/views.py 的load_starred_stories(前端通过/reader/starred_stories调用,见 media/js/newsblur/common/assetmodel.js)。当请求携带query参数时,走MStarredStory.find_stories分支(apps/rss_feeds/models.py):
- 匹配范围:
story_title、story_author_name、story_tags三个字段的icontains模糊匹配; - 可叠加过滤:
tag(仅匹配含该标签的故事)、date_filter_start/date_filter_end(按starred_date时间段过滤); - 排序与分页:
order(newest/oldest)决定-starred_date方向,offset/limit控制分页; - 权限限制:搜索分支要求
user.profile.is_premium,否则返回空结果并提示"You must be a premium subscriber to search."(非 Premium 用户不能使用已保存故事搜索)。
load_starred_stories支持的完整查询参数如下(这些参数同样支撑了按标签、按高亮、按日期等浏览模式):
| 参数 | 含义 | 默认值 / 取值 |
|---|---|---|
offset | 起始偏移 | 0 |
limit | 每页条数 | 10 |
page | 页码(存在时offset = limit * (page-1)) | 0 |
query | 搜索关键词 | 空(不搜索) |
order | 排序方向 | newest/oldest |
read_filter | 已读/未读过滤 | all |
date_filter_start/date_filter_end | 保存日期区间(Premium Archive 功能,非 Archive 用户会被置空) | null |
tag | 按标签过滤(Premium) | 空 |
highlights | 仅看含高亮片段的故事(Premium) | false |
h/h[] | 指定故事哈希批量取回(最多 100 条) | 空 |
v | API 版本 | 1(前端传2) |
值得一提的是,当前仓库实现中搜索走的是 MongoDB 文本匹配(SearchStarredStory的 Elasticsearch 版调用在 apps/reader/views.py 被注释),这正好印证了博客原文"full text search"所描述的体验:搜标题、作者、故事标签,全部命中已保存故事库。
数据模型:已保存故事如何存储
按站点视图与搜索能力的底层是 apps/rss_feeds/models.py 中的MStarredStory(MongoDB 集合starred_stories)。关键字段:
| 字段 | 说明 |
|---|---|
user_id | 所属用户(与story_guid构成唯一约束) |
starred_date/starred_updated | 保存时间 / 最近更新时间 |
story_feed_id | 故事来源站点 ID(按站点视图的核心依据) |
story_date | 故事原始发布时间 |
story_title/story_content/story_content_z | 标题、正文(正文在save()时 zlib 压缩进*_z字段) |
story_author_name/story_permalink/story_guid | 作者、原文链接、全局唯一 GUID |
story_hash | feed_id:story_hash形式的故事哈希,由feed_guid_hash计算并覆写 |
story_tags | 故事自带的文章标签(来自 RSS) |
user_tags | 用户自定义标签(Saved Story Tagging 的落点,最大 128 字符/个) |
user_notes | 用户笔记 |
highlights | 高亮片段(Premium 特性) |
image_urls | 配图 URL 列表 |
模型自带四个索引组合:(user_id, -starred_date)(按保存时间倒序浏览)、(user_id, story_feed_id)(按站点聚合)、(user_id, story_hash)(按哈希去重/取回)、story_feed_id(站点维度计数)。story_feed_id索引的存在正是按站点视图能够在 MongoDB 侧高效分组的直接证据。
标签系统:按标签浏览、重命名与 RSS 导出
按站点视图上线于 2014 年,而标签(Tag)是它的前置功能。标签体系在仓库中同样完整保留:
- 按标签浏览:前端
open_starred_stories检测options.tag,在starred_feeds集合中按tag_slug()或tag匹配模型(media/js/newsblur/reader/reader.js),后端则用user_tags__contains=tag过滤(Premium 限定,见 apps/reader/views.py)。 - 标签重命名/删除:
rename_starred_tag与delete_starred_tag(apps/reader/views.py)分别完成"全库标签改名"与"移除标签(故事保留)",路由注册于 apps/reader/urls.py。 - 测试佐证:apps/reader/tests.py 的
Test_RenameStarredTag用例验证了重命名会同步更新所有故事、合并已有同名标签的计数、且不影响其余标签——这是标签数据一致性的直接保障。 - RSS 导出:每个标签(以及全部已保存故事)都可以生成独立的 Atom/RSS feed。
starred_stories_rss_feed_tag(apps/reader/views.py)以user_id + secret_token + tag_slug为寻址方式生成Saved Stories - <tag>标题的订阅源;MStarredStoryCounts.rss_url属性(apps/rss_feeds/models.py)拼接出形如/reader/starred_rss/<user_id>/<secret_token>/<slug>的订阅地址。这让收藏可以订阅到其他阅读器、或通过 IFTTT/Zapier 等工具继续流转——功能页 FAQ 中明确写着"Each tag gets its own RSS feed"。
前端交互链路:从点击到渲染
一次"打开已保存故事"的完整调用链为:
- 侧边栏点击 Starred 头部或标签 →
sidebar.js的open_starred_stories; - →
reader.js的open_starred_stories(options):设置active_feed、flags['starred_tag']、URL 导航,并决定 story view 布局(feed/page); - →
assetmodel.js的fetch_starred_stories(page, tag, highlights, ...):组装GET /reader/starred_stories请求,携带query(来自全局搜索框)、order(视图设置)、tag、highlights、date_filter_*、v:2; - → 后端
load_starred_stories返回stories(经Feed.format_stories格式化)+starred_counts等元数据; - 前端把结果渲染进故事标题栏/故事视图,同时侧边栏用
MStarredStoryCounts.user_counts的结果(apps/rss_feeds/models.py)刷新标签与站点的计数徽标。
移动端规划
博客原文最后预告:按站点视图与已保存故事搜索能力将逐步移植到 iOS 与 Android 客户端。当前仓库中移动端仍在持续演进,Web 端这套"站点 + 文件夹 + 标签 + 搜索"的组织模型(MStarredStory统一数据源 +starred_counts统计接口)正是多端共用同一套后端接口的基础——这也是 NewsBlur 各端数据同步的前提。
总结
NewsBlur 的 Saved Stories 视图给出了一个非常实用的收藏组织范式:用系统自动维护的站点维度 + 用户自定义的标签维度,叠加全文搜索与 RSS 导出,把"以后再看"的收藏变成可浏览、可检索、可分享的个人知识库。其技术落点在当前仓库中清晰可循——MongoDB 侧的MStarredStory与MStarredStoryCounts双模型(apps/rss_feeds/models.py)承担存储与聚合,load_starred_stories/starred_counts(apps/reader/views.py)承担查询与统计,前端reader.js/sidebar.js/assetmodel.js完成交互闭环。如果你想自建类似的"剪藏 + 站点分组 + 搜索"系统,这套按feed_id建立二级索引、以标签与站点双维度聚合、并为聚合结果提供订阅导出的方案,值得直接参考。
- 后端
- 前端
- 社交
- 人工智能
【免费下载链接】NewsBlur
NewsBlur is a personal news reader that brings people together to talk about the world. A new sound of an old instrument.
相关推荐
Frontail生产环境实战:Docker部署与Nginx反向代理配置终极指南
Frontail生产环境实战:Docker部署与Nginx反向代理配置终极指南 Frontail是一款强大的Node.js日志流式传输工具,它能够将服务器日志实
运维后端强力收藏:AcFunDown让你的A站视频永久保存
强力收藏:AcFunDown让你的A站视频永久保存 你是否曾经遇到过这样的情况:在AcFun上看到一个精彩视频,想要收藏起来反复观看,却发现视频突然下架了?或者
音视频桌面应用CLI深度探索xrpl-dev-portal:揭秘XRP Ledger的consensus协议
深度探索xrpl dev portal:揭秘XRP Ledger的consensus协议 XRP Ledger作为去中心化支付系统的核心,其consensus协
文档/教程区块链
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考