newsnow-新闻聚合平台-开源项目


这个仓库 newsnow 是一个专注于提供实时和热门新闻优雅阅读体验的项目。以下是对该仓库的详细介绍:

项目概述

NewsNow 目前是一个仅支持中文的演示版本,后续会推出功能更丰富、支持更好定制化和英文内容的完整版本。其目标是为用户提供简洁优雅的界面,以实现对实时和热门新闻的高效阅读。

主要特性

  1. 界面设计:拥有简洁优雅的 UI 设计,优化阅读体验。
  2. 实时更新:能够实时更新热门新闻。
  3. 登录与同步:支持 GitHub OAuth 登录,并实现数据同步。
  4. 缓存机制:默认缓存时长为 30 分钟,登录用户可强制刷新。
  5. 自适应抓取:根据数据源更新频率,采用自适应抓取间隔(最小 2 分钟),优化资源使用并防止 IP 被封禁。
  6. 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        # 全局类型NewsItemSource定义
   └── 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 数据同步

  1. 登录鉴权流程
  2. 创建GitHub App,配置回调地址/api/oauth/github
  3. OAuth获取用户信息,签发JWT令牌存储登录态
  4. 用户持久化数据
  5. 收藏新闻、自定义资讯列布局、阅读偏好全部存入数据库
  6. 多端部署同一域名时,登录账号自动同步全部配置

模块3:分层缓存与持久化存储

  1. 内存短期缓存:Nitro运行时内存缓存,30分钟过期,减少重复爬虫请求
  2. 数据库持久层
  3. 推荐:Cloudflare D1(Serverless零运维)
  4. 兼容:SQLite、MySQL等db0支持的关系型数据库
  5. 存储内容:用户信息、收藏记录、抓取历史、新闻快照
  6. 初始化开关: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能力

  1. 多列自由拖拽布局:可自定义展示哪些资讯源、调整列顺序
  2. 暗黑模式(默认关闭浅色模式,文档说明浅色UI观感较差)
  3. PWA支持:添加到桌面、离线缓存页面
  4. 多语言页面:README提供英文/简体中文/日语版本,页面国际化正在开发
  5. 极简无广告阅读,仅展示标题、热度、发布时间、来源链接

五、完整部署架构(三种主流方案)

方案1:Serverless无服务器部署(推荐,Cloudflare Pages/Vercel)

  1. Fork仓库,导入平台
  2. 构建命令:pnpm run build
  3. 输出目录:dist/output/public
  4. 可选配置GitHub OAuth、绑定D1数据库实现登录同步

方案2:Docker容器本地/服务器部署

项目根目录执行一键启动:

docker compose up

可在docker-compose.yml注入环境变量(GitHub密钥、JWT密钥、数据库开关),开箱即用。

方案3:本地开发调试

corepack enable
pnpm i
pnpm dev

启动前后端一体热更新服务,本地调试新增数据源、前端页面。

六、核心设计模式与架构优势

  1. 适配器模式:数据源统一标准化,新增资讯源仅需新增独立ts文件,核心业务无侵入
  2. 策略模式:抓取、缓存策略动态适配不同资讯源,平衡实时性与IP安全
  3. 前后端共享类型:shared目录统一TS类型,杜绝接口字段不一致问题
  4. 微内核插件化:爬虫、MCP、用户系统、前端UI完全解耦,可单独裁剪功能(无登录简易部署可关闭数据库/OAuth)
  5. 跨运行时兼容:Nitro底层屏蔽Node、Workers、容器差异,一套代码多平台部署

七、开发路线图(Roadmap)

  1. 多语言完整支持(英文、多语种资讯源)
  2. 个性化推荐、资讯分类订阅
  3. 扩充全球海外资讯数据源
  4. 完善自定义主题、布局偏好配置

八、扩展开发指南(新增资讯源流程)

  1. server/sources/新建xxx.ts,实现defineSource标准接口
  2. shared/sources.json注册该数据源元信息
  3. 本地pnpm dev调试抓取逻辑
  4. 提交PR,参考CONTRIBUTING.md规范完成贡献

九、项目优缺点总结

优势

  1. 全栈TS类型安全,代码结构清晰易维护
  2. 轻量化部署,支持Serverless/容器/本地三种模式
  3. 插件化数据源,扩展成本极低
  4. 内置MCP服务,打通AI大模型联网资讯能力
  5. MIT开源,无商用限制,可私有化二次开发
  6. 智能缓存+自适应爬虫,降低封禁风险

局限

  1. 目前完整内容仅支持中文资讯,多语言仍在开发
  2. 无内置Redis,缓存仅依赖内存+数据库,高并发场景需自行扩展缓存层
  3. 无消息推送、邮件订阅等增值功能,仅基础阅读聚合能力

十、仓库配套文件说明

  1. README.zh-CN.md:中文部署、功能完整文档
  2. CONTRIBUTING.md:新增数据源、代码提交规范
  3. example.env.server:环境变量模板(OAuth、数据库、JWT密钥)
  4. Dockerfile:容器镜像构建脚本
  5. wrangler.toml模板:Cloudflare D1数据库配置
  6. patches/:第三方依赖补丁文件
  7. .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 设置

  1. 创建 GitHub App
  2. 无需特殊权限
  3. 设置回调 URL 为:https://your-domain.com/api/oauth/github(将 your-domain 替换为实际域名)
  4. 获取 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/sourcesserver/sources 目录,项目提供了完整的类型定义和清晰的架构。具体添加新数据源的详细说明可查看 CONTRIBUTING.md

依赖版本

pnpm-lock.yaml 文件记录了项目所依赖的各个包及其版本信息,例如 archiver@7.0.1unstorage@1.16.0 等。

代码片段示例

仓库中包含多个代码文件,以下是部分示例: - 数据源处理server/sources 目录下有多个数据源处理文件,如 github.tsproducthunt.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()}`)
})

github