ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenStock 从零到部署:Next.js 15 + Better Auth + Inngest 开源股票应用的完整技术实践

OpenStock 从零到部署:Next.js 15 + Better Auth + Inngest 开源股票应用的完整技术实践 OpenStock 从零到部署Next.js 15 Better Auth Inngest 开源股票应用的完整技术实践【免费下载链接】OpenStockOpenStock is an open-source alternative to expensive market platforms. Track real-time prices, set personalized alerts, and explore detailed company insights — built openly, for everyone, forever free.项目地址: https://gitcode.com/GitHub_Trending/ope/OpenStock本文基于 OpenStock 仓库的 README 官方文档并结合database/、lib/、middleware/等实际源码实现系统讲解这个开源股票跟踪平台的定位与功能边界、技术栈选型、本地与 Docker 部署流程、全量环境变量说明、项目目录结构、Finnhub/Inngest/AI Provider 等数据集成链路。读完你可以独立把 OpenStock 跑起来本地或 Docker Compose理解每个环境变量的消费位置并能沿源码定位搜索、行情、告警与邮件自动化等核心功能的具体实现。一、项目定位OpenStock 是什么OpenStock 是一个面向大众市场的开源股票行情应用定位为昂贵行情平台的开源替代品跟踪实时价格、设置个性化价格告警、查看公司详情——完全开源构建面向所有人永久免费。项目由 Open Dev SocietyODS维护采用 AGPL-3.0 协议如果你修改、再分发或以 Web 服务形式部署它必须以相同协议开源你的代码并保留原作者署名见 LICENSE。README 中同时给出了明确的边界声明OpenStock 是社区构建的产品不是券商brokerage行情数据是否延迟取决于数据供应商的规则与你的配置项目内容不构成任何财务建议。ODS 组织 manifesto 部分阐述了项目的价值取向技术应属于每个人、知识应开放免费、社区应欢迎新人而非设门槛组织承诺永不通过锁定知识或售卖访问权限获利而是依靠透明、捐赠和社区力量运转。这部分决定了项目在功能上刻意避免付费墙设计例如欢迎邮件、新闻摘要等增值能力全部通过用户自行配置的免费层 API Key 实现。二、技术栈全景README 将技术栈分为三组并与 package.json 中的依赖版本相互印证核心层Next.js 15App Router React 19package.json中锁定next: 15.5.7、react: 19.1.0TypeScriptstrict 模式Tailwind CSS v4通过tailwindcss/postcss接入无需独立的tailwind.configshadcn/ui Radix UI 基础组件 Lucide 图标认证与数据层Better Auth邮件/密码认证 MongoDB 适配器MongoDB Mongoosemongoose: ^8.19.0Finnhub API提供股票代码搜索、公司档案与市场新闻TradingView 可嵌入组件图表与市场视图自动化与通信层Inngest事件、cron 定时任务、AI 推理NodemailerGmail 传输next-themes、cmdk命令面板、react-hook-formREADME 给出的语言构成参考TypeScript 约 93.4%、CSS 约 6%、JavaScript 约 0.6%。三、功能清单及其源码对应关系README 列出的功能矩阵如下括号内为可在仓库中直接验证的实现位置功能说明源码依据认证Better Auth 邮件/密码登录 MongoDB 适配器受保护路由由 Next.js middleware 强制lib/better-auth/auth.ts、middleware/index.ts全局搜索 / CmdK 面板Finnhub 驱动的股票搜索、空闲时展示热门股、查询防抖lib/actions/finnhub.actions.ts、components/SearchCommand.tsx自选股Watchlist按用户存储于 MongoDB每用户每 symbol 唯一database/models/watchlist.model.ts股票详情页TradingView 符号信息、K 线/高级图表、基准线、技术指标、公司概况与财务组件、可选的多源情绪洞察app/(root)/stocks/[symbol]/page.tsx市场总览热力图、报价、头条新闻TradingView 组件components/TradingViewWidget.tsx个性化引导onboarding收集国家、投资目标、风险偏好、偏好行业注册流程表单组件components/forms/邮件与自动化AI 个性化欢迎邮件Inngest 调用 Gemini、基于自选股的新闻摘要邮件cronlib/inngest/functions.ts、lib/nodemailer/打磨的 UIshadcn/ui、Radix 原语、Tailwind v4 设计令牌默认深色主题components/ui/、app/globals.css键盘快捷键Cmd/Ctrl K 快速操作/搜索components/SearchCommand.tsx几个实现细节值得注意1. 会话保护由 middleware 实现而非逐页判断。middleware/index.ts 通过getSessionCookie(request)检查 Better Auth 的会话 Cookie缺失则重定向到/sign-in其matcher使用负向前瞻排除api、_next/static、_next/image、favicon.ico、sign-in、sign-up、forgot-password、reset-password、assets等公共路径这与 README 描述的大多数路由受保护sign-in/sign-up、静态资源与 Next 内部路径为公共例外完全一致。2. Watchlist 的唯一性由数据库索引保证。database/models/watchlist.model.ts 中WatchlistSchema.index({ userId: 1, symbol: 1 }, { unique: true })在 Mongoose 层强制同一用户不能重复添加同一 symbolsymbol字段同时配置了uppercase: true, trim: true规范化处理。3. 认证实例是懒加载的单例。lib/better-auth/auth.ts 中getAuth()先调用connectToDatabase()拿到 Mongoose 连接再以其db实例构造mongodbAdapter密码策略为最小 8 位、最大 128 位sendResetPassword钩子会调用 Nodemailer 发送重置邮件失败仅记录日志不阻断流程。四、Quick Start本地开发快速上手4.1 前置条件Node.js 20以及 pnpm 或 npmMongoDB 连接串MongoDB Atlas 或 Docker Compose 本地实例Finnhub API Key支持免费层实时行情可能需付费Gmail 账户用于发信或自行修改 Nodemailer 传输配置可选Google Gemini API Key用于 AI 生成的欢迎语4.2 克隆与安装git clone https://gitcode.com/GitHub_Trending/ope/OpenStock.git cd OpenStock # 二选一 pnpm install # 或 npm install4.3 配置环境变量并验证数据库在项目根目录创建.env文件完整变量说明见第六节然后验证数据库连通性pnpm test:db # 或 npm run test:db该脚本对应 scripts/test-db.mjs由 package.json 中test:db: node scripts/test-db.mjs定义。4.4 启动开发服务# Next.js devTurbopack pnpm dev # 或 npm run devdev脚本实际执行为next dev --turbopack。4.5 本地运行 Inngest工作流、cron、AInpx inngest-clilatest dev4.6 构建与生产启动pnpm build pnpm start # 或 npm run build npm startbuild同样走 Turbopacknext build --turbopack。启动后访问http://localhost:3000即可看到应用。五、Docker Compose 部署OpenStock 提供了完整的 docker-compose.yml包含两个服务openstock应用本体与mongodb带持久化卷的 MongoDB 7。步骤确认已安装 Docker 与 Docker Compose创建.env文件见下节变量说明。Docker 场景下使用指向 Compose 网络内 MongoDB 服务的本地连接串MONGODB_URImongodb://root:examplemongodb:27017/openstock?authSourceadmin启动整个栈# 在仓库根目录执行 docker compose up -d mongodb docker compose up -d --build访问入口应用http://localhost:3000MongoDB 在 Docker 网络内以主机名mongodb:27017可达关键配置说明对照实际 docker-compose.ymlopenstock服务声明了extra_hosts: [mongodb:host-gateway]并通过env_file: .env注入环境变量——README 中的示例摘录未包含该字段实际文件以仓库版本为准depends_on: [mongodb]保证应用服务在数据库之后启动MongoDB 的 root 凭据由 Compose 的MONGO_INITDB_ROOT_USERNAME/PASSWORD定义因此 root 用户的连接串必须带authSourceadmin数据通过命名卷mongo-data:/data/db在重启后持久保留MongoDB 容器配置了基于mongosh --eval db.adminCommand(ping)的健康检查interval 10s / timeout 5s / retries 5。README 中给出的 MongoDB 服务定义示例mongo:7镜像、root/example 凭据、27017:27017端口映射、健康检查与实际文件一致可放心参照。六、环境变量全解与源码消费位置在项目根目录创建.env数据库 URI 二选一托管MongoDB Atlas或本地Docker Compose。以下变量清单继承自 README 官方模板并补充了每个变量在源码中的实际消费位置。6.1 托管 MongoDBAtlas示例# Core NODE_ENVdevelopment # Database (Atlas) MONGODB_URImongodbsrv://user:passcluster/db?retryWritestruewmajority # Better Auth BETTER_AUTH_SECRETyour_better_auth_secret BETTER_AUTH_URLhttp://localhost:3000 # Finnhub # Note: NEXT_PUBLIC_FINNHUB_API_KEY is required for Vercel deployment NEXT_PUBLIC_FINNHUB_API_KEYyour_finnhub_key FINNHUB_BASE_URLhttps://finnhub.io/api/v1 # Sentiment insights (optional) ADANOS_API_KEYyour_adanos_api_key # ADANOS_API_BASE_URLhttps://api.adanos.org # AI Provider (optional, default: gemini) # Supported: gemini, minimax, siray # AI_PROVIDERgemini # Gemini GEMINI_API_KEYyour_gemini_api_key # MiniMax (optional, used when AI_PROVIDERminimax or as fallback) # Get your key at https://platform.minimaxi.com # MINIMAX_API_KEYyour_minimax_api_key # Inngest Signing Key (required for Vercel deployment) # Get this from your Inngest dashboard: https://app.inngest.com/env/settings/keys INNGEST_SIGNING_KEYyour_inngest_signing_key # Email (Nodemailer via Gmail; consider App Passwords if 2FA) NODEMAILER_EMAILyouraddressgmail.com NODEMAILER_PASSWORDyour_gmail_app_password6.2 本地 Docker Compose MongoDB 示例与上文唯一实质差异是数据库连接串# Database (Docker) MONGODB_URImongodb://root:examplemongodb:27017/openstock?authSourceadmin其余变量Better Auth、Finnhub、情绪洞察、AI Provider、Inngest、邮件完全一致。6.3 变量在源码中的消费位置变量用途源码消费位置MONGODB_URIMongoose 连接串database/mongoose.ts缺失时直接抛MongoDB URI is missingBETTER_AUTH_SECRET/BETTER_AUTH_URLBetter Auth 密钥与站点地址lib/better-auth/auth.tsNEXT_PUBLIC_FINNHUB_API_KEYFinnhub 所有接口鉴权 tokenlib/actions/finnhub.actions.tsADANOS_API_KEY/ADANOS_API_BASE_URL多源情绪快照可选lib/actions/adanos.actions.tsAI_PROVIDER选择 AI 后端默认geminilib/ai-provider.tsGEMINI_API_KEY/GEMINI_MODELGemini REST 调用模型默认gemini-2.5-flash-litelib/ai-provider.tsMINIMAX_API_KEY等MiniMaxOpenAI 兼容可作回退lib/ai-provider.tsINNGEST_SIGNING_KEYInngest 签名验证Vercel 部署必需lib/inngest/client.tsNODEMAILER_EMAIL/NODEMAILER_PASSWORDGmail 传输凭据lib/nodemailer/index.ts安全注意事项继承 README 原文尽可能把私钥留在服务端带NEXT_PUBLIC_前缀的变量会暴露给浏览器需明确知悉其含义生产环境建议使用专用 SMTP 服务而非个人 Gmail不要把密钥硬编码进 Dockerfile使用.env与 Compose。Mongoose 连接层的两个工程细节database/mongoose.ts、L31-L52连接前设置dns.setDefaultResultOrder(ipv4first)并把 DNS 服务器指向8.8.8.8注释说明这是为规避 Node 17 下querySrv ECONNREFUSEDIPv6 优先解析的问题连接时同时传family: 4与bufferCommands: false通过global.mongooseCache缓存连接与 Promise避免 Next.js 开发模式下热更新导致重复连接。七、项目结构README 给出的目录骨架已按仓库现状核对app/ (auth)/ layout.tsx sign-in/page.tsx sign-up/page.tsx (root)/ layout.tsx page.tsx help/page.tsx stocks/[symbol]/page.tsx api/inngest/route.ts globals.css layout.tsx components/ ui/… # shadcn/radix primitives (button, dialog, command, input, etc.) forms/… # InputField, SelectField, CountrySelectField, FooterLink Header.tsx, Footer.tsx, SearchCommand.tsx, WatchlistButton.tsx, … database/ models/watchlist.model.ts mongoose.ts lib/ actions/… # server actions (auth, finnhub, user, watchlist) better-auth/… inngest/… # client, functions, prompts nodemailer/… # transporter, email templates constants.ts, utils.ts scripts/ test-db.mjs types/ global.d.ts next.config.ts # i.ibb.co image domain allowlist postcss.config.mjs # Tailwind v4 postcss setup components.json # shadcn config public/assets/images/ # logos and screenshots实际仓库中该骨架还有若干补充app/(auth)/下另有forgot-password/与reset-password/路由对应密码重置流程app/(root)/下还有about/、api-docs/、terms/、watchlist/页面database/models/ 中除watchlist.model.ts外还有alert.model.ts价格告警模型lib/actions/ 实际包含auth.actions.ts、finnhub.actions.ts、user.actions.ts、watchlist.actions.ts、alert.actions.ts、adanos.actions.ts等lib/inngest/ 含client.ts、functions.ts、prompts.ts三个文件测试用例位于tests/ai-provider.test.ts、adanos.actions.test.ts、reset-password-email.test.ts、utils.test.ts等由 vitest 驱动。next.config.ts 的关键配置Turbopack 根目录、images.remotePatterns允许i.ibb.coTradingView 外部图片与static2.finnhub.ioFinnhub logo两个远程图片域名以及构建期忽略 ESLint 与 TypeScript 错误的开关。八、数据与集成详解8.1 Finnhub搜索、档案、新闻与缓存策略配置NEXT_PUBLIC_FINNHUB_API_KEY与FINNHUB_BASE_URL默认https://finnhub.io/api/v1。免费层可能返回延迟报价应遵守其速率限制与服务条款。从 lib/actions/finnhub.actions.ts 的实现看项目对不同接口的数据时效性采取了分级缓存策略通过 Next.jsfetch的revalidate实现实时报价getQuoteno-store不缓存保证价格新鲜度L66-L76公司档案getCompanyProfile/stock/profile2缓存 24 小时86400秒搜索searchStocks缓存 30 分钟1800秒且被cache()包裹做请求级去重空查询时改为拉取POPULAR_STOCK_SYMBOLS前 10 个热门股的档案缓存 1 小时新闻getNews缓存 5 分钟300秒给定 symbols 时按 symbol 并发拉取/company-news并轮询round-robin挑选最多 6 篇、按时间倒序返回无结果时回退到/news?categorygeneral通用新闻。此外源码内置了一个交易所后缀集合FINNHUB_EXCHANGE_SUFFIXESDE、L、T、NS、V等见 L29-L34用于在缺少exchange字段时从 symbol 后缀推断交易所标签识别不了的统一回退为US——这是支撑30 国际交易所搜索体验的一个关键细节。8.2 Adanos 情绪洞察可选在股票详情页提供跨 Reddit、X.com、新闻与 Polymarket 的结构化情绪快照。设置ADANOS_API_KEY启用可用ADANOS_API_BASE_URL覆盖 API 主机它仅用于详情页情绪卡片components/stocks/StockSentimentCard.tsx不替代 Finnhub 或 TradingView 的任何数据。8.3 TradingView 可嵌入组件行情页的 K 线/高级图表、市场总览的热力图、报价与时间线均由 TradingView embeddable widgets 渲染其外部图片域名i.ibb.co已在 next.config.ts 的images.remotePatterns中白名单放行。8.4 Better Auth MongoDB 会话链邮件/密码认证 MongoDB 适配器会话校验走 middleware见第三节。密码重置链路Better Auth 的sendResetPassword钩子 → lib/nodemailer/reset-password.ts → 对应app/(auth)/reset-password/页面完成重置。8.5 Inngest 工作流README 声明与当前源码的对照README 声明了两条工作流app/user.created→ AI 个性化欢迎邮件cron0 12 * * *→ 按用户发送每日新闻摘要本地开发通过npx inngest-clilatest dev运行入口路由为 app/api/inngest/route.ts。从当前 lib/inngest/functions.ts 源码结构看实际注册了四个函数值得逐一对应sign-up-emailL10-L53监听app/user.created事件。将事件携带的 onboarding 数据国家、投资目标、风险偏好、偏好行业拼装进PERSONALIZED_WELCOME_EMAIL_PROMPT调用callAIProviderWithFallback生成个性化欢迎语所有 AI 后端均失败时使用固定兜底文案再通过sendWelcomeEmail发送。weekly-news-summaryL56-L57触发器为app/send.weekly.news事件加 cron0 9 * * 1每周一 9 点。流程为拉取最近 10 条市场新闻 → AI 生成周度摘要 → 通过 Kit 订阅者广播发送带内联样式的 HTML 邮件。可以推断README 中的每日新闻摘要0 12 * * *描述对应更早的按用户逐发方案而当前实现已演进为面向 Kit 订阅者的周期性广播。check-stock-alertsL205-L206cron*/5 * * * *每 5 分钟运行。取出active: true triggered: false且未过期的Alert记录 → 按 symbol 分组 → 逐个调用 FinnhubgetQuote取现价 → 判断ABOVE/BELOW条件是否触及targetPrice→ 命中的告警标记triggered: true, active: false。这是个性化价格告警功能的核心调度器。check-inactive-usersL296-L297cron0 10 * * *每天 10 点扫描 30 天未活跃且最近 30 天未发送过再激活邮件的用户单次上限 50 条并尝试发送再激活邮件。8.6 邮件层Nodemailerlib/nodemailer/index.ts 采用 Gmail 传输templates.ts提供欢迎与新闻摘要两套 HTML 模板。实现上有两个值得注意的稳健性设计优雅降级若未配置NODEMAILER_EMAIL/NODEMAILER_PASSWORDtransporter 置为null并在启动时打印警告sendWelcomeEmail/sendNewsSummaryEmail返回{ status: skipped }而非抛错——即邮件能力未配置时不影响主流程保守的连接池pool: true, maxConnections: 1, maxMessages: 3注释说明原因是本应用邮件量低避免 Gmail 传输被打满。8.7 AI Provider 抽象层lib/ai-provider.ts 是 README 中AI_PROVIDER变量的实现载体支持三个后端gemini默认Google Gemini REST API默认模型gemini-2.5-flash-lite可用GEMINI_MODEL覆盖minimaxOpenAI 兼容端点默认https://api.minimax.io/v1模型MiniMax-M2.7sirayOpenAI 兼容端点https://api.siray.ai/v1模型siray-1.0-ultra。其容错设计由callAIProviderWithFallbackL168-L184实现先调用主 Provider失败后按getFallbackProviderName的逻辑切换——主 Provider 为 Gemini 时优先回退到 MiniMax若其 Key 存在否则 Siray主 Provider 非 Gemini 时回退到 Gemini。Inngest 的欢迎邮件与新闻摘要函数均经由callAIProviderWithFallback调用并在其之上还有第二层固定文案兜底见 8.5 节形成主 Provider → 回退 Provider → 静态文案的三级降级链。该模块配有单元测试tests/ai-provider.test.ts 与集成测试tests/ai-provider.integration.test.ts 覆盖行为。九、市场支持Market SupportOpenStock 支持30 个国际交易所包括 NSE、LSE、TSX 等。但数据供应商的能力差异带来明确限制Finnhub 支持大多数全球交易所TradingView 免费层对新兴市场印度 NSE、越南等有限制免费层下非美股的实时数据延迟在 15 分钟以上。完整的交易所清单、已知限制与规避方法、This symbol is only available on TradingView 提示的成因、扩大市场覆盖的升级路径与未来改进计划均收录在 MARKET_SUPPORT.md 中建议部署前通读。十、Scripts 与开发工具链package.json 定义的脚本与 README 一致并含补充脚本命令说明devnext dev --turbopackNext.js 开发服务器Turbopackbuildnext build --turbopack生产构建Turbopackstartnext start运行生产服务器linteslintESLint 检查test:dbnode scripts/test-db.mjs验证数据库连通性test/test:watchvitest run/vitest单元测试vitestvitest.config.ts配置开发体验要点TypeScript strict 模式Tailwind CSS v4 无需独立tailwind.configshadcn/ui 组件配合 Radix 原语cmdk 命令面板、next-themes 主题切换、lucide-react 图标。scripts/ 目录下另有一批运维辅助脚本check-env.mjs、inspect-user.mjs、migrate-users-to-kit.mjs、verify-watchlist.mjs等用于环境变量检查、用户数据检查与 Kit 迁移等场景。十一、安全、许可与致谢漏洞披露发现安全漏洞时不要开公开 issue应邮件至opendevsocietycc.cc项目方将协调负责任披露并快速修复。许可项目采用 AGPL-3.0 协议LICENSE将保持对所有人永久免费开源修改、再分发或以 Web 服务形式部署时须以相同协议开源并署名原作者。致谢Finnhub可及的行情数据、TradingView可嵌入行情组件、shadcn/ui / Radix UI / Tailwind CSS / Next.js 社区、Inngest可靠的后置任务与工作流、Better Auth简洁安全的认证以及全部贡献者。十二、关键文件速查关注点入口文件部署编排docker-compose.yml、Dockerfile数据库连接database/mongoose.ts认证lib/better-auth/auth.ts、middleware/index.ts行情/搜索/新闻lib/actions/finnhub.actions.ts价格告警调度lib/inngest/functions.tsAI 多后端抽象lib/ai-provider.ts邮件发送lib/nodemailer/index.ts、lib/nodemailer/templates.ts市场限制说明MARKET_SUPPORT.md测试tests/、vitest.config.ts从 README 骨架到上述源码证据OpenStock 展示了一条完整且可复现的落地路径Next.js 15 App Router 承载界面与 Server ActionsBetter Auth Mongoose 解决认证与持久化Finnub 与 TradingView 分别供给数据与可视化Inngest 以 cron/事件驱动告警、摘要与再激活等异步流程AI Provider 抽象层则让个性化文案具备多后端容错能力。所有外部依赖均以环境变量显式注入配合 Docker Compose 与test:db脚本从克隆到生产启动只需数分钟。【免费下载链接】OpenStockOpenStock is an open-source alternative to expensive market platforms. Track real-time prices, set personalized alerts, and explore detailed company insights — built openly, for everyone, forever free.项目地址: https://gitcode.com/GitHub_Trending/ope/OpenStock创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进