先说结论:OpenStock 是我最近翻到的、最值得花半小时读一遍代码的开源项目之一。但它离「能替你盯盘的成品」还有一段距离——不是因为它做得差,而是因为它是个「从教程里长出来的产品」,边跑边修,好几处功能还停在「先占个位」的状态。这篇我把三件事一次讲清:它是什么、凭什么一年涨到 1.8 万 star、以及我读完源码后翻到的三处没接完的线。

项目名片信息
项目地址github.com/Open-Dev-Society/OpenStock
主要语言 / LicenseTypeScript(约 93%)/ AGPL-3.0
Stars(发文时实查)约 18.4k(2026-09-23 实查,会变)
适合谁想学一套能读通的现代全栈(Next.js 15 + Auth + MongoDB + 定时任务 + AI),或者想要一块免费、可自托管的行情看板的开发者

它到底在解决什么问题

付费行情平台把三样东西锁在订阅里:实时报价、价格提醒、公司基本面。你要用就得按月掏钱,而且数据归平台所有。

OpenStock 走的是反方向:整个应用开源,你自己 clone、自己 host。行情走 Finnhub,图表用 TradingView 组件,用户、自选列表、价格警报都落在你自己的 MongoDB 里。它反复强调一件事——不是券商,不构成投资建议,免费档的数据会有延迟。这点它没藏。

所以它给的不是「一个更便宜的 Bloomberg」,而是「一套你可以自己拥有、自己改的行情看板代码」。这个定位差别很关键,后面判断该不该用,全靠它。

一个 YouTube 教程,怎么长成了 1.8 万 star

这点我觉得比技术栈更有意思。OpenStock 的 README 里有一节 Special thanks,直接写着它脱胎于 Adrian Hajdin(JavaScript Mastery)的 Stock Market App 教程。很多人跟着那个视频敲过一个雏形,而 OpenStock 是主程 ravixalgorithm 把它做成了一件完整作品——补上邮箱密码登录、自选列表落库、定时任务、AI 邮件、Docker 编排,再挂到 Open Dev Society 名下,以「永久免费、拆掉付费墙」的名义发出来。

项目建于 2025 年 9 月,到 2026 年 9 月已经 18.4k star、2.2k fork。我没凭感觉写「为什么火」,而是联网交叉核对了一圈:PyShine 的拆解文提到它上过 GitHub trending,并把它归为「一个完整、现代的、可读的全栈示例」;GENZ TECH 的自托管实测点出了这波热度的具体推手——README 在 9 月中连着刷了两版、Open Dev Society 正拿新项目 kitbash 给它交叉导流,而「自托管的个人金融工具」恰好是个拥挤却供给不足的品类。几条来源对得上,我才敢这么写。

归纳下来,星标来自三件事的叠加:教学价值(一套能读通的现代全栈)、情绪价值(反订阅制的宣言),以及真实可用(它不是 demo,是能自建的看板)。

拆开看,它是怎么搭起来的

技术栈没什么冷门货,难得的是它把很多块拼得挺顺:Next.js 15(App Router)+ React 19,TypeScript 占约 93%;Better Auth 做邮箱密码登录,MongoDB + Mongoose 持久化;Finnhub 提供搜索、公司资料、新闻,TradingView 提供图表(且是浏览器直连);Inngest 管事件与定时,AI 默认用 Gemini,可切 MiniMax 或 Siray。版本我对着 package.json 核过:Next 15.5.7、React 19.1.0、mongoose 8.19、inngest 3.47。

路由保护做得很薄。一个中间件只检查 Better Auth 的会话 cookie 在不在,不在就跳登录页——我读到这里有点意外,它没在这里做任何权限细分。

// middleware/index.ts:5,17 —— 只看 cookie 是否存在的轻量门禁
export async function middleware(request: NextRequest) {
  const sessionCookie = getSessionCookie(request);
  if (!sessionCookie) {
    return NextResponse.redirect(new URL('/sign-in', request.url));
  }
  return NextResponse.next();
}

数据读写没有单独的 API 层,全走 Next 的 Server Actions(lib/actions/ 下按 Finnhub、Watchlist、Alert、User 分文件)。自选列表在 Mongoose 里加了个复合唯一索引,从数据库层挡住重复收藏:

// database/models/watchlist.model.ts:21
// 同一用户下 symbol 唯一,重复收藏在写库那一步就被拒
WatchlistSchema.index({ userId: 1, symbol: 1 }, { unique: true });

把这几块画成一张图,大概是这样:

OpenStock 架构与数据流图:浏览器与 TradingView 直连,Next.js 15 经中间件校验会话后调用 Server Actions 访问 Finnhub 与 MongoDB,并把事件与定时任务交给 Inngest,后者再调用 AI 与邮件服务
OpenStock 的组件与数据流(据源码整理;方框里标了对应文件与行号)

代码里三处「先占个位」

如果只读 README,你会以为它已经是个能用的盯盘工具。我把 Inngest 那几个定时函数(lib/inngest/functions.ts)整个读完,发现有三处功能,管道是通的,最后一步却没接上。这不是黑它——恰恰相反,源码里留着作者自己写的注释,坦白得很。

一、价格警报:会被触发,但不会通知你

它有一个每 5 分钟跑一次的定时任务,拉出所有「激活且未触发」的警报,按代码分组去 Finnhub 查现价,满足「高于 / 低于目标价」就判定命中。逻辑没问题。问题在判定之后:

// lib/inngest/functions.ts:281 —— 命中后只打日志、标记状态,没有推送动作
console.log(`ALERT FIRED: ${alert.symbol} is ${currentPrice} ...`);
// 作者在这段上方自己写了:
// "In a real app we would import 'kit' here and use kit.sendBroadcast"
await Alert.findByIdAndUpdate(alert._id, { triggered: true, active: false });

也就是说,你设的警报在后台确实会被检测、被标记为已触发,但你本人收不到任何邮件或推送。就目前的代码而言,「价格警报」是一个半成品。

二、召回邮件:只对维护者的测试邮箱真发

还有一个每天上午 10 点跑的定时任务,筛出 30 天没活跃的用户(每次最多 50 个,防刷),发一封「我们想你了」的召回信。模板写得很完整,但发送逻辑是这样的:

// lib/inngest/functions.ts:443 —— 只有这一个邮箱会真收到信
if (user.email === '11aravipratapsingh@gmail.com') {
  await kit.sendBroadcast(subject, content);                        // 真发
} else {
  console.log(`[Re-engagement Mock] Would send to ${user.email}`);  // 其余只打日志
}

那串地址是维护者的测试账号。其他人走到这里只会留下一行 mock 日志。作者在注释里解释过原因:用 Kit 的广播接口给单个用户发事务邮件并不顺手,所以他先「跑通流程、留好接口」,真发等以后接上。

三、行情 Key 直接暴露给浏览器

这一处最实际。Finnhub 的 Key 用的是 NEXT_PUBLIC_ 前缀的环境变量:

// lib/actions/finnhub.actions.ts:8 —— NEXT_PUBLIC_ 前缀会被打进前端产物
const NEXT_PUBLIC_FINNHUB_API_KEY = process.env.NEXT_PUBLIC_FINNHUB_API_KEY ?? '';

在 Next.js 里,NEXT_PUBLIC_ 开头的变量会被打包进浏览器端,任何人打开开发者工具都能看到你的 Finnhub Key。README 的「Environment Variables」一节其实自己提醒过「NEXT_PUBLIC_ 变量会暴露给浏览器」,但代码用的就是这个变量。自建的话,要么接受这个风险,要么把行情请求收到服务端再转发。顺带一提,它在取自选列表时把市盈率硬编码成 0,注释写着「为了省 Finnhub 的调用配额,先跳过」——免费档一分钟 60 次调用,省着用是能理解的取舍。

两个我在意的小细节

顺手记两件小事,不影响使用,但能看出项目还在「边长边修」。一是 README 最顶上第一行,是一串 Solana 合约地址(Ca: B6F3…pump),出现在一个开源行情项目里相当突兀;二是周报邮件的模板里,Logo 引用的是维护者个人 fork 的地址(ravixalgorithm/OpenStock),而不是组织仓库。都不影响功能,但说明它离「打磨完」还有点距离。

那到底该不该用

场景结论
想学现代全栈很值。App Router、Server Actions、Better Auth、Mongoose、Inngest、AI 降级策略都在一个仓库里,注释密度也够高,是很好的阅读材料
想要免费自托管看板可以,但先想好数据源:免费档 Finnhub 一分钟 60 次、非美股票延迟 15 分钟以上,TradingView 对部分新兴市场还有限制
需要可靠的推送提醒暂时不行。警报只会被检测和标记,不会推给你(见上文第一处)
想直接当生产 SaaS 用不建议。没有交易能力,无邮箱验证与权限细分,AI 与邮件依赖多个第三方密钥,且 AGPL-3.0 要求你改动后开源

我的判断是:把 OpenStock 当成「一套可以拆开、读懂、再照着自己需求改的开源看板」,它非常值;但如果你要的是打开就能稳定替你盯盘的产品,那三处没接完的线,得你自己接。它至少很诚实——把「还没做完」明明白白写在了源码注释里。