
























OpenStock 是一个开源的股票市场交易平台替代品,旨在打破昂贵金融数据平台的垄断。该项目由 Open Dev Society 社区驱动,基于 AGPL-3.0 许可证发布,承诺永久免费且代码开放。
核心价值:
技术亮点:
OpenStock 采用 Next.js App Router 的现代路由架构,充分利用服务器组件(RSC)与服务端操作(Server Actions)的优势:
app/
├── (auth)/ # 认证相关页面(登录、注册)
│ ├── layout.tsx
│ ├── sign-in/
│ └── sign-up/
├── (root)/ # 主应用区域
│ ├── layout.tsx
│ ├── page.tsx # 仪表盘首页
│ ├── help/ # 帮助页面
│ └── stocks/
│ └── [symbol]/ # 动态股票详情页
├── api/
│ └── inngest/ # Inngest 事件接收端点
├── globals.css # 全局样式
└── layout.tsx # 根布局
关键设计决策:
(auth) 和 (root) 进行逻辑隔离,便于布局管理和中间件保护stocks/[symbol] 实现股票详情页的动态生成/api/inngest 作为 Inngest 的事件接收器,处理异步任务Better Auth + MongoDB Adapter 集成:
// lib/better-auth/...
// 认证配置示例(基于项目结构推断)
import { betterAuth } from "better-auth";
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import mongoose from "mongoose";
export const auth = betterAuth({
database: mongodbAdapter(mongoose.connection.db),
emailAndPassword: {
enabled: true,
},
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60, // 5 minutes
},
},
});
中间件保护策略:
/sign-in/sign-in、/sign-up、/api/inngest、/_next/static用户观察列表数据模型:
// database/models/watchlist.model.ts
import mongoose, { Schema, Document } from "mongoose";
export interface IWatchlist extends Document {
userId: string;
symbol: string;
addedAt: Date;
}
const WatchlistSchema = new Schema<IWatchlist>({
userId: { type: String, required: true },
symbol: { type: String, required: true },
addedAt: { type: Date, default: Date.now },
});
// 复合唯一索引:防止同一用户重复添加相同股票
WatchlistSchema.index({ userId: 1, symbol: 1 }, { unique: true });
export default mongoose.models.Watchlist
|| mongoose.model<IWatchlist>("Watchlist", WatchlistSchema);
Finnhub API 集成模式:
// lib/actions/finnhub.ts (推断实现)
const FINNHUB_BASE_URL = process.env.FINNHUB_BASE_URL || "https://finnhub.io/api/v1";
export async function searchSymbols(query: string) {
const res = await fetch(
`${FINNHUB_BASE_URL}/search?q=${encodeURIComponent(query)}&token=${process.env.NEXT_PUBLIC_FINNHUB_API_KEY}`
);
return res.json();
}
export async function getCompanyProfile(symbol: string) {
const res = await fetch(
`${FINNHUB_BASE_URL}/stock/profile2?symbol=${symbol}&token=${process.env.NEXT_PUBLIC_FINNHUB_API_KEY}`
);
return res.json();
}
TradingView Widget 嵌入:
next.config.ts 的 images.remotePatterns 配置允许外部图片域名i.ibb.co 托管的部分静态资源同样需要配置域名白名单Inngest 事件驱动架构:
// lib/inngest/functions.ts (推断)
import { inngest } from "./client";
// 用户注册事件 → 触发 AI 个性化欢迎邮件
export const sendWelcomeEmail = inngest.createFunction(
{ id: "send-welcome-email" },
{ event: "app/user.created" },
async ({ event, step }) => {
const { userId, email, name, preferences } = event.data;
// 调用 Gemini API 生成个性化介绍
const personalizedIntro = await step.run("generate-ai-intro", async () => {
// 基于用户偏好(国家、投资目标、风险承受能力)生成文案
return await generateGeminiContent(preferences);
});
// 发送邮件
await step.run("send-email", async () => {
return await sendEmail({
to: email,
subject: `Welcome to OpenStock, ${name}!`,
html: `<p>${personalizedIntro}</p>`,
});
});
}
);
// 定时任务:每日财经摘要邮件(Cron 0 12 * * *)
export const dailyNewsSummary = inngest.createFunction(
{ id: "daily-news-summary" },
{ cron: "0 12 * * *" },
async ({ step }) => {
// 1. 获取所有用户
// 2. 遍历用户观察列表
// 3. 获取相关新闻
// 4. 发送个性化摘要
}
);
AI 提供商抽象:
AI_PROVIDER 切换lib/inngest/prompts)Cmd/Cmd+K 全局搜索实现:
// components/SearchCommand.tsx (基于 cmdk 库)
import { Command } from "cmdk";
import { useDebounce } from "@/lib/utils";
export function SearchCommand() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 300);
const [results, setResults] = useState([]);
useEffect(() => {
if (debouncedQuery.length < 2) return;
// 调用 Finnhub 搜索 API
fetch(`/api/search?q=${debouncedQuery}`)
.then(res => res.json())
.then(data => setResults(data.result || []));
}, [debouncedQuery]);
return (
<Command>
<Command.Input
placeholder="搜索股票代码或公司名称..."
value={query}
onValueChange={setQuery}
/>
<Command.List>
{results.map(stock => (
<Command.Item
key={stock.symbol}
onSelect={() => router.push(`/stocks/${stock.symbol}`)}
>
{stock.description} ({stock.symbol})
</Command.Item>
))}
</Command.List>
</Command>
);
}
必需:
可选:
1. 克隆仓库:
git clone https://github.com/Open-Dev-Society/OpenStock.git
cd OpenStock
2. 安装依赖:
# 使用 pnpm(推荐)
pnpm install
# 或使用 npm
npm install
3. 环境配置:
创建 .env 文件(参考 Environment Variables 章节):
# 基础配置
NODE_ENV=development
# 数据库(本地 Docker)
MONGODB_URI=mongodb://root:example@mongodb:27017/openstock?authSource=admin
# 或 MongoDB Atlas
# MONGODB_URI=mongodb+srv://<user>:<pass>@<cluster>/<db>?retryWrites=true&w=majority
# Better Auth
BETTER_AUTH_SECRET=your_random_secret_string
BETTER_AUTH_URL=http://localhost:3000
# Finnhub API
NEXT_PUBLIC_FINNHUB_API_KEY=your_finnhub_key
FINNHUB_BASE_URL=https://finnhub.io/api/v1
# Gemini AI(可选)
GEMINI_API_KEY=your_gemini_api_key
# Inngest(Vercel 部署必需)
INNGEST_SIGNING_KEY=your_inngest_signing_key
# 邮件服务(Gmail)
NODEMAILER_EMAIL=[email protected]
NODEMAILER_PASSWORD=your_gmail_app_password
4. 验证数据库连接:
pnpm test:db
# 或
npm run test:db
本地开发(Turbopack):
pnpm dev
# 访问 http://localhost:3000
启动 Inngest 本地开发服务器(新终端):
npx inngest-cli@latest dev
# Inngest 开发服务器运行在 http://localhost:8288
生产构建:
一键启动(推荐):
docker compose up -d mongodb && docker compose up -d --build
访问:
Docker Compose 配置要点:
mongodb 服务使用官方 mongo:7 镜像mongosh --eval "db.adminCommand('ping')"mongo-datamongodb,通过 depend_on 控制启动顺序authSource=admin(root 用户认证)1. 用户注册与登录:
/sign-up 创建账号2. 搜索股票:
Cmd/Ctrl + K 打开命令面板3. 管理观察列表:
1. 深度公司分析:
/stocks/AAPL(以苹果为例)2. 市场概览:
3. 情绪分析(可选):
ADANOS_API_KEY场景:构建个人股票监控面板
// 自定义组件:用户观察列表实时监控
// components/WatchlistMonitor.tsx
import { useEffect, useState } from "react";
import { getWatchlist, getQuote } from "@/lib/actions";
export function WatchlistMonitor() {
const [watchlist, setWatchlist] = useState([]);
const [quotes, setQuotes] = useState({});
useEffect(() => {
async function fetchData() {
// 1. 获取用户观察列表
const symbols = await getWatchlist();
setWatchlist(symbols);
// 2. 批量获取实时报价
const quotesData = {};
for (const item of symbols) {
const quote = await getQuote(item.symbol);
quotesData[item.symbol] = quote;
}
setQuotes(quotesData);
}
fetchData();
// 3. 定时刷新(每 60 秒)
const interval = setInterval(fetchData, 60000);
return () => clearInterval(interval);
}, []);
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{watchlist.map(item => (
<Card key={item.symbol}>
<CardHeader>
<CardTitle>{item.symbol}</CardTitle>
</CardHeader>
<CardContent>
<p>当前价: ${quotes[item.symbol]?.c}</p>
<p className={quotes[item.symbol]?.d > 0 ? "text-green-500" : "text-red-500"}>
涨跌: {quotes[item.symbol]?.d} ({quotes[item.symbol]?.dp}%)
</p>
</CardContent>
</Card>
))}
</div>
);
}
性能优化建议:
<Suspense> 和流式 SSR问题:pnpm install 报错 “ENOMEM: not enough memory”
解决方案:
# 增加 Node.js 内存限制
export NODE_OPTIONS="--max-old-space-size=4096"
pnpm install
问题:MongoDB 连接失败(Docker Compose 环境)
解决方案:
docker ps
docker logs mongodb
.env 中连接字符串正确:MONGODB_URI=mongodb://root:example@mongodb:27017/openstock?authSource=admin
mongodb(Docker Compose 网络内)authSource=admin(root 用户认证)问题:Finnhub API 返回 401 Unauthorized
解决方案:
.env 中 NEXT_PUBLIC_FINNHUB_API_KEY 已设置NEXT_PUBLIC_ 前缀会暴露给浏览器,建议仅用于公开数据FINNHUB_API_KEY(无 NEXT_PUBLIC_ 前缀)问题:TradingView Widget 无法加载(跨域错误)
解决方案:
next.config.ts 的 images.remotePatterns 配置:images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'i.ibb.co',
port: '',
pathname: '/**',
},
// 如果 Widget 使用其他 CDN,也需要添加
],
},
问题:Inngest 函数未触发
解决方案:
npx inngest-cli@latest dev).env 中 INNGEST_SIGNING_KEY 配置curl -X POST http://localhost:3000/api/inngest \
-H "Content-Type: application/json" \
-d '{"event": "app/user.created", "data": {...}}'
问题:搜索输入卡顿(Cmd+K 命令面板)
解决方案:
<Suspense> 分包加载搜索结果问题:MongoDB 查询慢(观察列表加载延迟)
解决方案:
userId 和 symbol 字段已建立索引:WatchlistSchema.index({ userId: 1, symbol: 1 }, { unique: true });
mongoose.connect() 的连接池配置:mongoose.connect(uri, {
maxPoolSize: 10, // 调整连接池大小
serverSelectionTimeoutMS: 5000,
});
问题:Tailwind CSS v4 类名不生效
解决方案:
postcss.config.mjs 配置正确:const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;
text-opacity-*)问题:Better Auth 与 MongoDB Adapter 版本不兼容
解决方案:
{
"dependencies": {
"better-auth": "1.3.25",
"@better-auth/mongodb-adapter": "1.3.25"
}
}
OpenStock 是一个技术栈现代、架构清晰、可扩展性强的开源股票交易平台。其核心价值在于:
技术亮点:
部署灵活性:
社区与生态:
适用场景:
注意事项:
未来展望:
OpenStock 证明了高质量金融工具可以是开源且免费的。对于开发者,它是学习现代 Next.js 全栈架构的优秀参考;对于用户,它是摆脱昂贵订阅的真正选择。
项目链接:
参考资料:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。