这个仓库 newsnow 是一个专注于提供实时和热门新闻优雅阅读体验的项目。以下是对该仓库的详细介绍:
项目概述
NewsNow 目前是一个仅支持中文的演示版本,后续会推出功能更丰富、支持更好定制化和英文内容的完整版本。其目标是为用户提供简洁优雅的界面,以实现对实时和热门新闻的高效阅读。
主要特性
- 界面设计:拥有简洁优雅的 UI 设计,优化阅读体验。
- 实时更新:能够实时更新热门新闻。
- 登录与同步:支持 GitHub OAuth 登录,并实现数据同步。
- 缓存机制:默认缓存时长为 30 分钟,登录用户可强制刷新。
- 自适应抓取:根据数据源更新频率,采用自适应抓取间隔(最小 2 分钟),优化资源使用并防止 IP 被封禁。
- MCP 服务器支持:支持 MCP 服务器,可通过修改配置文件中的
BASE_URL为自定义域名。
NewsNow (ourongxing/newsnow) 仓库全面系统介绍
一、项目基础概述
1. 项目定位
NewsNow 是一套全栈TypeScript实时热点新闻聚合平台,主打清爽无干扰的多列卡片式资讯阅读,统一聚合全网热搜、技术社区、媒体资讯,支持私有化部署、GitHub账号同步、AI MCP服务接入,开源协议为 MIT,由 ourongxing 维护,持续迭代更新(最新版本 v0.0.39,2025-12-19)。
2. 核心标语与目标
Elegant reading of real-time and hottest news 核心目标:提供极简、无广告、可自定义的实时热点资讯聚合工具,兼顾个人自用、服务端部署、AI工具扩展场景。
3. 基础约束
- 开发环境要求:Node.js ≥ 20
- 当前Demo版仅完整支持中文资讯源,多语言版本在开发路线图中
- 全栈TS开发,96.4%代码为TypeScript,类型强约束
二、完整技术栈分层拆解
1. 前端层(src/)
| 技术 | 用途 |
|---|---|
| React 19 | UI渲染核心 |
| Vite 6 | 构建、开发热更新 |
| TanStack Router | 前端路由管理 |
| Jotai | 轻量全局状态(收藏、缓存、用户登录态) |
| UnoCSS | 原子化样式,替代传统CSS |
| Framer Motion | 卡片拖拽、切换动画 |
| PWA | 渐进式网页应用,支持离线缓存 |
2. 后端服务层(server/,Nitro全栈服务)
- Nitro:跨平台服务运行时,一套代码兼容 Node.js、Cloudflare Workers、Vercel、Docker,自动处理API路由、跨域、SSR
- Cheerio:网页HTML爬虫解析,适配无官方API的资讯源
- JWT:GitHub OAuth登录鉴权、用户数据加密
- MCP Server:内置模型上下文协议服务,可供AI客户端调用新闻数据
- 数据库适配器:基于 unjs/db0,兼容 Cloudflare D1(推荐)、SQLite、MySQL等关系库
3. 共享通用层(shared/)
全栈共享类型、常量、数据源配置,消除前后端类型不一致问题;统一定义NewsItem标准新闻数据结构,所有数据源输出统一格式。
4. 工程化工具
- pnpm:包管理器
- Vitest:单元测试
- Docker / docker-compose:容器一键部署
- GitHub Actions:CI/CD自动构建
三、仓库目录结构与模块职责
newsnow/
├── src/ # 前端SPA源码
│ ├── components/ # 页面组件(新闻列卡片、头部、通用UI)
│ ├── hooks/ # 自定义状态/请求钩子(刷新、缓存、收藏)
│ ├── routes/ # 页面路由
│ └── ...
├── server/ # Nitro后端API、爬虫、数据库、MCP服务
│ ├── api/ # RESTful接口(登录、新闻、MCP、用户同步)
│ ├── sources/ # 各平台数据源抓取适配器(核心扩展点)
│ ├── database/ # 数据库初始化、CRUD封装
│ └── mcp/ # MCP协议服务实现
├── shared/ # 前后端共享代码
│ ├── types.ts # 全局类型(NewsItem、Source定义)
│ └── sources.json # 所有资讯源元配置
├── public/ # 静态资源、PWA图标
├── .github/workflows # CI自动部署流水线
├── Dockerfile / docker-compose.yml # 容器部署配置
├── nitro.config.ts # Nitro服务配置
├── vite.config.ts # 前端构建配置
├── example.env.server # 环境变量模板(登录、数据库密钥)
└── CONTRIBUTING.md # 新增数据源、贡献规范文档
四、核心功能模块详解
模块1:多源新闻聚合引擎(核心)
1. 数据源插件化架构(适配器模式)
所有资讯源独立实现,统一遵循defineSource接口,新增资讯无需改动核心逻辑:
// server/sources/xxx.ts 标准模板
export default defineSource({
id: "weibo",
name: "微博热搜",
interval: 2, // 最小抓取间隔2分钟
async fetch(): Promise<NewsItem[]> {
// 1. 请求API/爬取页面
// 2. 清洗数据统一转为NewsItem标准结构
// 3. 返回标准化新闻列表
}
})
当前内置源覆盖:微博、知乎、B站、IT之家、V2EX、Hacker News、GitHub Trending、QQ视频热搜、ProductHunt等国内外资讯平台。
2. 智能自适应抓取调度(策略模式)
- 基础最小抓取间隔:2分钟,避免高频请求封禁IP
- 自适应策略:根据资讯源更新热度动态调整轮询频率,冷门源降低抓取频次节省资源
- 缓存机制:全局默认30分钟缓存;登录用户支持手动强制刷新绕过缓存
模块2:用户系统 & GitHub OAuth 数据同步
- 登录鉴权流程
- 创建GitHub App,配置回调地址
/api/oauth/github - OAuth获取用户信息,签发JWT令牌存储登录态
- 用户持久化数据
- 收藏新闻、自定义资讯列布局、阅读偏好全部存入数据库
- 多端部署同一域名时,登录账号自动同步全部配置
模块3:分层缓存与持久化存储
- 内存短期缓存:Nitro运行时内存缓存,30分钟过期,减少重复爬虫请求
- 数据库持久层:
- 推荐:Cloudflare D1(Serverless零运维)
- 兼容:SQLite、MySQL等db0支持的关系型数据库
- 存储内容:用户信息、收藏记录、抓取历史、新闻快照
- 初始化开关:
INIT_TABLE=true首次部署自动创建数据表
模块4:MCP 模型上下文协议服务(特色扩展能力)
内置标准MCP服务,可供Claude、Cursor等AI客户端调用全网新闻数据,开箱即用配置:
{
"mcpServers": {
"newsnow": {
"command": "npx",
"args": ["-y", "newsnow-mcp-server"],
"env": { "BASE_URL": "https://你的部署域名" }
}
}
}
作用:AI大模型可实时读取全网热点资讯,实现联网新闻问答、热点总结。
模块5:前端交互与UI能力
- 多列自由拖拽布局:可自定义展示哪些资讯源、调整列顺序
- 暗黑模式(默认关闭浅色模式,文档说明浅色UI观感较差)
- PWA支持:添加到桌面、离线缓存页面
- 多语言页面:README提供英文/简体中文/日语版本,页面国际化正在开发
- 极简无广告阅读,仅展示标题、热度、发布时间、来源链接
五、完整部署架构(三种主流方案)
方案1:Serverless无服务器部署(推荐,Cloudflare Pages/Vercel)
- Fork仓库,导入平台
- 构建命令:
pnpm run build - 输出目录:
dist/output/public - 可选配置GitHub OAuth、绑定D1数据库实现登录同步
方案2:Docker容器本地/服务器部署
项目根目录执行一键启动:
docker compose up
可在docker-compose.yml注入环境变量(GitHub密钥、JWT密钥、数据库开关),开箱即用。
方案3:本地开发调试
corepack enable
pnpm i
pnpm dev
启动前后端一体热更新服务,本地调试新增数据源、前端页面。
六、核心设计模式与架构优势
- 适配器模式:数据源统一标准化,新增资讯源仅需新增独立ts文件,核心业务无侵入
- 策略模式:抓取、缓存策略动态适配不同资讯源,平衡实时性与IP安全
- 前后端共享类型:shared目录统一TS类型,杜绝接口字段不一致问题
- 微内核插件化:爬虫、MCP、用户系统、前端UI完全解耦,可单独裁剪功能(无登录简易部署可关闭数据库/OAuth)
- 跨运行时兼容:Nitro底层屏蔽Node、Workers、容器差异,一套代码多平台部署
七、开发路线图(Roadmap)
- 多语言完整支持(英文、多语种资讯源)
- 个性化推荐、资讯分类订阅
- 扩充全球海外资讯数据源
- 完善自定义主题、布局偏好配置
八、扩展开发指南(新增资讯源流程)
- 在
server/sources/新建xxx.ts,实现defineSource标准接口 - 在
shared/sources.json注册该数据源元信息 - 本地
pnpm dev调试抓取逻辑 - 提交PR,参考
CONTRIBUTING.md规范完成贡献
九、项目优缺点总结
优势
- 全栈TS类型安全,代码结构清晰易维护
- 轻量化部署,支持Serverless/容器/本地三种模式
- 插件化数据源,扩展成本极低
- 内置MCP服务,打通AI大模型联网资讯能力
- MIT开源,无商用限制,可私有化二次开发
- 智能缓存+自适应爬虫,降低封禁风险
局限
- 目前完整内容仅支持中文资讯,多语言仍在开发
- 无内置Redis,缓存仅依赖内存+数据库,高并发场景需自行扩展缓存层
- 无消息推送、邮件订阅等增值功能,仅基础阅读聚合能力
十、仓库配套文件说明
README.zh-CN.md:中文部署、功能完整文档CONTRIBUTING.md:新增数据源、代码提交规范example.env.server:环境变量模板(OAuth、数据库、JWT密钥)Dockerfile:容器镜像构建脚本wrangler.toml模板:Cloudflare D1数据库配置patches/:第三方依赖补丁文件.github/workflows:CI自动构建、发布流水线
目录结构
.dockerignore
.gitignore
CONTRIBUTING.md
Dockerfile
LICENSE
README.ja-JP.md
README.md
README.zh-CN.md
docker-compose.local.yml
docker-compose.yml
eslint.config.mjs
example.env.server
example.wrangler.toml
index.html
nitro.config.ts
package.json
pnpm-lock.yaml
pwa.config.ts
test/
common.test.ts
tsconfig.app.json
tsconfig.base.json
tsconfig.json
tsconfig.node.json
uno.config.ts
vite.config.ts
vitest.config.ts
.vscode/
...
patches/
...
shared/
...
.github/
...
server/
...
src/
...
screenshots/
...
scripts/
...
public/
...
tools/
...
部署方式
基础部署
无需登录和缓存功能时,可直接进行以下操作: 1. Fork 本仓库。 2. 导入至 Cloudflare Pages 或 Vercel 等平台。
Cloudflare Page 配置
- 构建命令:
pnpm run build - 输出目录:
dist/output/public
GitHub OAuth 设置
- 创建 GitHub App
- 无需特殊权限
- 设置回调 URL 为:
https://your-domain.com/api/oauth/github(将your-domain替换为实际域名) - 获取 Client ID 和 Client Secret
环境变量配置
参考 example.env.server 文件,本地开发时将其重命名为 .env.server 并进行如下配置:
# Github Client ID
G_CLIENT_ID=
# Github Client Secret
G_CLIENT_SECRET=
# JWT Secret, usually the same as Client Secret
JWT_SECRET=
# Initialize database, must be set to true on first run, can be turned off afterward
INIT_TABLE=true
# Whether to enable cache
ENABLE_CACHE=true
数据库支持
支持的数据库连接器可参考:https://db0.unjs.io/connectors ,推荐使用 Cloudflare D1 数据库,具体操作步骤如下: 1. 在 Cloudflare Worker 控制台创建 D1 数据库。 2. 在 wrangler.toml 中配置 database_id 和 database_name。 3. 若 wrangler.toml 不存在,将 example.wrangler.toml 重命名并修改配置。 4. 下次部署时配置生效。
Docker 部署
在项目根目录下执行以下命令:
docker compose up
也可在 docker-compose.yml 中设置环境变量。
开发相关
数据来源添加
可参考 shared/sources 和 server/sources 目录,项目提供了完整的类型定义和清晰的架构。具体添加新数据源的详细说明可查看 CONTRIBUTING.md。
依赖版本
pnpm-lock.yaml 文件记录了项目所依赖的各个包及其版本信息,例如 archiver@7.0.1、unstorage@1.16.0 等。
代码片段示例
仓库中包含多个代码文件,以下是部分示例:
- 数据源处理:server/sources 目录下有多个数据源处理文件,如 github.ts、producthunt.ts 等,通过抓取网页信息并解析,将新闻数据整理成特定格式返回。
// server/sources/github.ts
import * as cheerio from "cheerio"
import type { NewsItem } from "@shared/types"
const trending = defineSource(async () => {
const baseURL = "https://github.com"
const html: any = await myFetch("https://github.com/trending?spoken_language_code=")
const $ = cheerio.load(html)
const $main = $("main .Box div[data-hpc] > article")
const news: NewsItem[] = []
$main.each((_, el) => {
const a = $(el).find(">h2 a")
const title = a.text().replace(/\n+/g, "").trim()
const url = a.attr("href")
const star = $(el).find("[href$=stargazers]").text().replace(/\s+/g, "").trim()
const desc = $(el).find(">p").text().replace(/\n+/g, "").trim()
if (url && title) {
news.push({
url: `${baseURL}${url}`,
title,
id: url,
extra: {
info: `✰ ${star}`,
hover: desc,
},
})
}
})
return news
})
export default defineSource({
"github": trending,
"github-trending-today": trending,
})
- OAuth 登录处理:
server/api/oauth/github.ts文件处理 GitHub OAuth 登录流程,包括获取访问令牌、用户信息,生成 JWT 令牌并进行重定向。
// server/api/oauth/github.ts
import process from "node:process"
import { SignJWT } from "jose"
import { UserTable } from "#/database/user"
export default defineEventHandler(async (event) => {
const db = useDatabase()
const userTable = db ? new UserTable(db) : undefined
if (!userTable) throw new Error("db is not defined")
if (process.env.INIT_TABLE !== "false") await userTable.init()
const response: {
access_token: string
token_type: string
scope: string
} = await myFetch(
`https://github.com/login/oauth/access_token`,
{
method: "POST",
body: {
client_id: process.env.G_CLIENT_ID,
client_secret: process.env.G_CLIENT_SECRET,
code: getQuery(event).code,
},
headers: {
accept: "application/json",
},
},
)
const userInfo: {
id: number
name: string
avatar_url: string
email: string
notification_email: string
} = await myFetch(`https://api.github.com/user`, {
headers: {
"Accept": "application/vnd.github+json",
"Authorization": `token ${response.access_token}`,
// 必须有 user-agent,在 cloudflare worker 会报错
"User-Agent": "NewsNow App",
},
})
const userID = String(userInfo.id)
await userTable.addUser(userID, userInfo.notification_email || userInfo.email, "github")
const jwtToken = await new SignJWT({
id: userID,
type: "github",
})
.setExpirationTime("60d")
.setProtectedHeader({ alg: "HS256" })
.sign(new TextEncoder().encode(process.env.JWT_SECRET!))
// nitro 有 bug,在 cloudflare 里没法 set cookie
// seconds
// const maxAge = 60 * 24 * 60 * 60
// setCookie(event, "user_jwt", jwtToken, { maxAge })
// setCookie(event, "user_avatar", userInfo.avatar_url, { maxAge })
// setCookie(event, "user_name", userInfo.name, { maxAge })
const params = new URLSearchParams({
login: "github",
jwt: jwtToken,
user: JSON.stringify({
avatar: userInfo.avatar_url,
name: userInfo.name,
}),
})
return sendRedirect(event, `/?${params.toString()}`)
})