把 OpenStock 塞进 Docker Compose:自托管行情监控平台的完整实战路线
把 OpenStock 塞进 Docker Compose自托管行情监控平台的完整实战路线【免费下载链接】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/OpenStockOpenStock 是一个以开源替代昂贵行情终端为定位的股票分析平台Next.js 15 React 19 TypeScript 构建提供实时价格追踪、个性化关注列表、TradingView 专业图表、AI 新闻摘要与价格预警在全球社区已积累超过 1.3 万注册用户。过去一年围绕它的部署教程在 CSDN 等社区反复出现从3 分钟搭建到5 分钟部署的标题屡见不鲜——但真正把一套可长期运行的自托管方案讲透的文章却不多部分教程甚至把它的数据层误写成 PostgreSQL、把数据源误写成东方财富或 Yahoo。本文不重复docker compose up 就完事的速成套路而是以仓库里的 docker-compose.yml、Dockerfile 和 lib 目录下的真实源码为线索走完一条完整的自托管路线先看清编排文件到底声明了什么再讲清多源行情接入的配置机制与限流真相最后收尾于定时简报、反向代理与数据库备份。你得到的将是一套可排查、可扩展、可长期维护的部署清单。一、先看清事实compose 文件里到底编排了什么不少社区文章声称 OpenStock 依赖 PostgreSQL这是流传最广的误解之一。打开仓库根目录的 docker-compose.yml真相一目了然——它只编排了两个服务services: openstock: build: context: . extra_hosts: - mongodb:host-gateway ports: - 3000:3000 env_file: - .env restart: unless-stopped depends_on: - mongodb mongodb: image: mongo:7 container_name: mongodb restart: unless-stopped environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example ports: - 27017:27017 volumes: - mongo-data:/data/db healthcheck: test: [CMD, mongosh, --eval, db.adminCommand(ping)] interval: 10s timeout: 5s retries: 5 volumes: mongo-data:几个值得注意的工程细节持久化靠命名卷而非 bind mountmongo-data:/data/db让数据库数据与容器生命周期解耦docker compose down不会丢数据重启后自选股、预警、用户会话全部保留。mongodb 服务自带 healthcheck用mongosh执行db.adminCommand(ping)做就绪探测。但注意openstock服务的depends_on只写了服务名没有condition: service_healthy——这意味着 compose 只保证 MongoDB 容器已启动而非已就绪。自托管线上环境建议补上条件依赖避免应用容器在数据库尚未完成鉴权初始化时就开始连接而反复崩溃depends_on: mongodb: condition: service_healthyextra_hosts: mongodb:host-gateway把容器内的mongodb主机名解析到宿主机的网关地址配合 Docker Desktop 类环境的跨容器网络保证应用容器能稳定访问数据库容器。应用镜像由 Dockerfile 构建基础镜像node:20-alpine先COPY package*.json再npm install充分利用 Docker 层缓存——只要依赖没变后续构建不必重装 node_modules随后COPY . .、npm run build产出生产包最终以CMD [npm, start]启动 Next.js 生产服务器对应 package.json 中的next start非 dev 模式。二、数据库连通.env 与启动顺序的关键细节自托管的第一步是写对.env。README 针对 Docker 场景给出了本地连接串模板MONGODB_URImongodb://root:examplemongodb:27017/openstock?authSourceadmin NODE_ENVdevelopment BETTER_AUTH_SECRETyour_better_auth_secret BETTER_AUTH_URLhttp://localhost:3000 NEXT_PUBLIC_FINNHUB_API_KEYyour_finnhub_key FINNHUB_BASE_URLhttps://finnhub.io/api/v1连接串里有三个坑值得展开必须带authSourceadmin。MongoDB root 用户的凭证存放在admin库不指定authSource时驱动默认去目标库这里是openstock找用户必然鉴权失败。主机名必须是mongodb而不是localhost。compose 内部网络中服务名即 DNS 名写成localhost会让应用去连容器自己的回环地址而这个端口并没有监听。容器与宿主机的 27017 端口映射是单向的27017:27017便于宿主机用mongosh或 GUI 工具调试但应用容器应当走内部网络不要依赖这个映射。仓库在 database/mongoose.ts 里还内置了两处对自托管环境的急救逻辑强制dns.setDefaultResultOrder(ipv4first)并把 DNS 服务器设置为8.8.8.8同时连接参数固定family: 4——这是针对某些环境里 SRV 记录解析返回 ECONNREFUSED 的已知问题打的补丁。若你的内网不允许访问 8.8.8.8这就是部署后日志里反复出现querySrv ECONNREFUSED的根因需要按你的网络环境调整 DNS 配置。启动顺序上仓库提供的命令是两步走docker compose up -d mongodb docker compose up -d --build先拉起数据库再构建并启动应用。首次启动建议观察docker compose logs -f mongodb确认 MongoDB 完成初始化后再看openstock服务的日志。三、多源行情的真实机制Finnhub 轮询池 TradingView 嵌入情报快照中那篇 CSDN 部署教程提到了东方财富/Yahoo 等多源行情接入但这与仓库源码并不相符——OpenStock 的实际数据架构是Finnhub行情/新闻/公司档案 TradingView图表嵌入 Adanos跨源情绪三件套。写作本文时以源码为准如果你在部署教程里看到东方财富字样多半是其他同名项目的内容被搜索引擎混入了 OpenStock 的检索结果。Finnhub免费 key 池 轮询 应用层限流行情层最核心的文件是 lib/actions/finnhub.actions.ts。它读FINNHUB_API_KEYS逗号分隔或NEXT_PUBLIC_FINNHUB_API_KEY把多个免费 key 组成轮询池const FINNHUB_KEYS (process.env.FINNHUB_API_KEYS || process.env.NEXT_PUBLIC_FINNHUB_API_KEY || process.env.FINNHUB_API_KEY || ) .split(,) .map((k) k.trim()) .filter(Boolean);注释里写得很直白每个免费 key 有独立的 60 次/分钟配额N 个 key 就是 N 倍配额。请求按nextKeyIndex % FINNHUB_KEYS.length轮询遇到 HTTP 429 自动换下一个 key 重试。自托管时若觉得行情刷新不够快多申请几个免费 key 填进FINNHUB_API_KEYS是最直接的扩容手段。围绕免费配额源码做了四层保护这些正是自托管踩坑时的排查地图并发槽位MAX_IN_FLIGHT_PER_KEY 2通过withSlot信号量把并发请求压到 2×key 数避免瞬时并发把单 key 配额打爆源码注释记录实测单 key 并发 4 个请求就会大面积挂起。请求级超时FETCH_TIMEOUT_MS 5000用AbortSignal.timeout兜底——否则 Finnhub 挂起时页面会拖到 Node 默认的 300 秒超时。内存 SWR 缓存fetchJSON实现 stale-while-revalidate过期数据先返回再后台刷新请求失败进入 60 秒冷却期FAILURE_TTL_MS期间直接抛RecentFailure短路避免每个访客都白等一次完整超时。Next 数据缓存TTL ≥ 60 秒的响应走next: { revalidate: ttl }让 Vercel 这类共享缓存层参与去重。行情刷新频率由 lib/market-data.ts 的数据模式开关决定export const DATA_MODE: DataMode process.env.NEXT_PUBLIC_OPENSTOCK_DATA_MODE realtime ? realtime : cached; export const QUOTE_TTL_SECONDS DATA_MODE realtime ? 15 : 3600;cached默认每小时刷新一次适合共享型免费部署realtime每 15 秒刷新同时解锁邮件价格预警alertsEnabled isRealtime。自托管用户想用预警功能必须在.env里设NEXT_PUBLIC_OPENSTOCK_DATA_MODErealtime并配好 Finnhub key。前端 hooks/useLiveQuotes.ts 只在 realtime 模式下以 15 秒间隔轮询 app/api/quotes/route.ts该路由做了登录校验、单次最多 25 个符号、符号白名单正则三重防护防止匿名请求烧掉共享配额。cached 模式下价格来自服务端渲染前端不轮询。市场覆盖的边界能显示 ≠ 能定价仓库用 lib/markets.ts 把市场抽象成 US / India / Germany / Canada / Australia / Crypto / Forex 七类每类带本地时区、交易时段与行情源类型并用 MARKET_SUPPORT.md 记录了一张实测于免费套餐的覆盖表几个关键结论Finnhub 免费层只覆盖美股与 Binance 加密对hasFinnhubQuotes同时校验BINANCE:前缀与是否国际符号TradingView 嵌入对美股/加密/外汇实时加拿大与澳大利亚延迟印度 BSE 与德国 Xetra 仅日终NSE、FTSE、DAX 等指数 ticker 在免费嵌入里被直接屏蔽。所以自托管后看到个股没有价格时先查 MARKET_SUPPORT.md 确认该市场属于无行情还是无图表不要急着怀疑环境配置。图表与情绪TradingView 嵌入 Adanos 跨源聚合图表层由 components/TradingViewWidget.tsx 统一承载按 lib/constants.ts 中定义的配置对象动态加载官方脚本支持全屏展开Esc 退出组件经memo缓存避免重渲染重建嵌入。K 线、技术指标、公司档案、财务数据、市场热力图、新闻时间线等 8 类 widget 的配置都集中在这一个文件里自托管定制配色主题时改这里即可。情绪分析层是可选的 lib/actions/adanos.helpers.ts通过ADANOS_API_KEY接入 Adanos API聚合 Reddit、X、新闻、Polymarket 四个来源的提及量、buzz 分与看涨百分比输出Bullish alignment / Wide divergence等一致性判断。该模块不参与行情主链路没配 key 时股票页只少一张情绪卡不影响其余功能。四、定时简报、价格预警与 AI 冗余Inngest 才是监控的灵魂自托管行情平台区别于静态看板的关键是无人值守的定时任务。这部分全部由 Inngest 编排集中在 lib/inngest/functions.ts共三条链路函数触发方式职责sendWeeklyNewsSummarycron0 9 * * 1每周一 9:00拉取市场新闻 → AI 生成周报 → Kit 广播checkStockAlertscron*/5 * * * *每 5 分钟遍历有效预警 → 拉行情 → 条件触发 → 邮件通知checkInactiveUserscron0 10 * * *每日 10:00召回 30 天未活跃用户每周简报AI 生成 Kit 广播sendWeeklyNewsSummary先取最近 5 天新闻、截取前 10 条用NEWS_SUMMARY_EMAIL_PROMPT模板让大模型生成摘要再经 lib/kit.ts 的sendBroadcast群发给 KitConvertKit订阅者。注意邮件主题与正文模板都内置在函数体里青色 #20c997 的品牌样式自托管要改简报样式直接改这里。价格预警并发控制 原子领取checkStockAlerts的工程含金量最高。它声明concurrency: 1保证同一时刻只有一个实例运行因为 Inngest 的并发限制只作用于 step 而非整个运行两个重叠运行可能同时持有同一条预警——源码用findOneAndUpdate做原子领取{ active: true, triggered: false }→ 置为触发态领取失败的实例直接跳过邮件发送失败则回滚状态让下一个 5 分钟周期重试保证一条预警不会重复发两封邮件也不会因为瞬时失败被永久吞掉。触发条件只有ABOVE/BELOW两种database/models/alert.model.ts预警默认 90 天过期。值得留意的是预警的可用范围受行情源约束由于告警检查需要每 5 分钟可调用的报价源目前只覆盖美股与加密对见hasFinnhubQuotes。AI 提供商的冗余策略周报与欢迎邮件的生成走 lib/ai-provider.ts它支持 Gemini / MiniMax / Siray 三种后端用AI_PROVIDER环境变量切换Gemini 走 REST 接口MiniMax 与 Siray 走 OpenAI 兼容接口。callAIProviderWithFallback实现自动降级——主提供商失败时按Gemini → MiniMax → Siray的优先级切换所有提供商都失败时周报与欢迎邮件会回退到硬编码文案保证链路不断。自托管时建议至少配两个提供商的 key这是整套监控体系里最便宜的冗余投资。邮件通道的两种选择邮件发送有两条路径事务性邮件欢迎信、预警走 lib/nodemailer/index.ts 的 Gmail transport广播类邮件周报、召回走 Kit。Nodemailer 未配置时应用照常启动仅邮件功能禁用函数内返回{ status: skipped }。自托管建议用带应用专用密码的 Gmail 账号或专用 SMTP避免把个人邮箱密钥暴露在.env。五、收尾三件事HTTPS 反向代理、数据库备份与常见坑反向代理与 HTTPSNext.js 容器默认监听 3000公开部署前应当用反向代理终结 TLS。最关键的一处配置是.env里的BETTER_AUTH_URL——它必须改为对外可达的 HTTPS 地址如https://stock.example.com否则 Better Auth 生成的会话与回调 URL 全部指向内网地址登录会莫名失效。社交登录Google/GitHub的回调地址同样以它为基准BETTER_AUTH_URL/api/auth/callback/google。路由保护由 middleware.ts 实现营销页/、/about、/help、/terms、/api-docs、/sponsor公开其余路径一律校验会话 cookie未登录重定向到/sign-in。反向代理层只需把/与/api/*转发到容器的 3000 端口即可无需额外处理 WebSocketNext.js 默认用 HTTP 长轮询。若选用 Caddy 做反向代理一个最小可用的Caddyfile长这样stock.example.com { reverse_proxy openstock:3000 }Caddy 自动签发并续期 Lets Encrypt 证书容器内部网络里openstock即应用服务名天然可被代理解析。数据库备份数据全在mongo-data命名卷里备份有两层手段卷级备份docker run --rm -v openstock_mongo-data:/data -v $(pwd):/backup alpine tar czf /backup/mongo-data-$(date %F).tar.gz -C /data .适合整体快照逻辑备份在 MongoDB 容器内执行mongodump导出指定库--authenticationDatabase admin粒度更细、可只备份openstock库。建议把备份命令写进 crontab与每周简报的 cron 错开执行避免凌晨同一时段抢占资源。恢复时先docker compose stop openstock再mongorestore最后重启应用。高频踩坑清单把社区反馈与源码交叉验证后自托管最常见的四类问题及定位方法如下应用连不上库查MONGODB_URI是否带authSourceadmin、主机名是否为mongodb日志里出现querySrv ECONNREFUSED则检查 DNS 是否被强制指向了不可达的 8.8.8.8database/mongoose.ts 中的硬编码。行情不刷新确认NEXT_PUBLIC_OPENSTOCK_DATA_MODE取值cached 模式是每小时缓存不是实时。注意NEXT_PUBLIC_前缀的变量会打进浏览器产物改完必须重新构建镜像docker compose up -d --build不能省。预警不触发先确认alertsEnabled为 true即 realtime 模式再看FINNHUB_API_KEYS是否覆盖被预警的符号——非美股/加密符号本来就不在hasFinnhubQuotes范围内。时区错乱容器默认 UTC。周报 cron0 9 * * 1与召回 cron0 10 * * *都是按 UTC 计算的若希望按本地时区触发需在 compose 里给openstock服务加TZAsia/Shanghai之类的环境变量并在 Inngest 仪表盘核对调度时区。结语从 docker-compose.yml 的两个服务到 lib/actions/finnhub.actions.ts 的四层限流再到 lib/inngest/functions.ts 的三条定时链路OpenStock 的自托管方案并不复杂但每一层都有值得理解的工程取舍免费 key 轮询池换取配额翻倍、SWR 缓存换取体验与成本平衡、原子领取换取预警不重不漏。把.env写对、把 realtime 模式打开、把反向代理与备份脚本挂上你得到的就不是一个能跑起来的 demo而是一个真正可以长期陪伴的私有行情监控终端。【免费下载链接】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),仅供参考