背景
- HEAD 长期停在 08-03 的安全审计提交,此后六周的工作全部只在工作区。
- 更严重的是部分源码从未进入过版本控制(非"改了没提交"):
· Services/findatahub-core/ 索引 0 条 —— 18 个核心模块(pipeline/gate/cleaner/
db/cross_validator/health/retry…)全新 clone 拿不到
· Services/findatahub-client/ 索引 0 条
· App/Sources 磁盘 74 个 .swift、索引 54 —— 20 个未入库(AutoRecoveryManager、
全套 Settings*Section、PowerSaver、MySQLOnDemandManager、DependencyScanner…)
一次 `git clean -fd`、误删目录或磁盘故障即可不可恢复地丢失这些文件。
本提交内容
1. 补齐上述未跟踪源码、测试、文档与报告(Services/App/Docs/Tests/根报告)。
2. 纳入 .workbuddy/memory(项目知识)与 .github CI 配置。
3. 本次代码与架构复查的修复(详见下一段说明):
- Scripts/bin/common.py PidManager.status 改 split(":", 1),修复时间戳被截断到整点
- Scripts/bin/db_pool.py 取连接加超时、归还前 rollback、失败归还池位、双检锁初始化
- Scripts/bin/newsflash_backend.py _query_all 改 try/finally 无条件归还连接
- Scripts/bin/newsflash_collect.py 新增 _install_crash_handlers(faulthandler + excepthook)
- tushare_fetcher/core/fetcher.py _fetch_static 增量走 upsert,仅全量快照才 replace
- tushare_fetcher/scripts/tushare_sync.sh 银层迁移补 stock_weekly/stock_monthly;
月度增加分红按报告期全量刷新
- kuaixun-service/extend/content_enricher.py 禁用自动重定向 + 逐跳 SSRF 校验
- App/.../AutoRecoveryManager.swift 反复故障改告警不熔断(避免拉长数据空窗)
- 新增 Scripts/bin/repair_dividend_history.py(分红历史修复,按报告期分页幂等 upsert)
- 新增 Docs/repro/*(两个可运行的缺陷复现脚本)
- 补齐 6 个文件共 20 条回归用例(均做过证伪:旧实现下必然失败)
安全
- 已确认未纳入任何凭据:Services/tushare_fetcher/.env 被其 .gitignore 覆盖;
~/.config/findatahub/env(0600)在仓库外。
验证
- Python 测试 1074 passed(Scripts/bin 207 / core 105 / tushare 174 / kuaixun 405 / akshare 183)
- Swift 经 `swift build --disable-sandbox` 编译通过,产物 v1.5.2 已部署
811 lines
37 KiB
HTML
811 lines
37 KiB
HTML
<!-- Generated by Trae Work -->
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>FinDataHub 数据使用文档</title>
|
||
<style>
|
||
@font-face {
|
||
font-family: 'Inter';
|
||
src: url('./_shared/fonts/Inter-Regular.ttf') format('truetype');
|
||
font-weight: 400;
|
||
}
|
||
@font-face {
|
||
font-family: 'Inter';
|
||
src: url('./_shared/fonts/Inter-SemiBold.ttf') format('truetype');
|
||
font-weight: 600;
|
||
}
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
src: url('./_shared/fonts/JetBrainsMono-Regular.ttf') format('truetype');
|
||
font-weight: 400;
|
||
}
|
||
|
||
:root {
|
||
--bg: #f8f9fa;
|
||
--bg2: #ffffff;
|
||
--ink: #1a1a2e;
|
||
--muted: #6b7280;
|
||
--rule: #e5e7eb;
|
||
--accent: #2563eb;
|
||
--accent2: #059669;
|
||
}
|
||
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
body {
|
||
font-family: 'Inter', -apple-system, 'Noto Sans CJK SC', sans-serif;
|
||
font-size: 15px;
|
||
line-height: 1.7;
|
||
color: var(--ink);
|
||
background: var(--bg);
|
||
}
|
||
|
||
/* Header */
|
||
header.hero {
|
||
background: linear-gradient(135deg, #1e3a5f 0%, #1a1a2e 50%, #0f172a 100%);
|
||
color: #fff;
|
||
padding: 4rem 2rem 3rem;
|
||
text-align: center;
|
||
}
|
||
header.hero h1 {
|
||
font-size: 2.2rem;
|
||
font-weight: 600;
|
||
letter-spacing: -0.02em;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
header.hero p {
|
||
font-size: 1.05rem;
|
||
opacity: 0.75;
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
line-height: 1.6;
|
||
}
|
||
header.hero .meta {
|
||
margin-top: 1.5rem;
|
||
font-size: 0.85rem;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* Layout */
|
||
.container {
|
||
max-width: 960px;
|
||
margin: 0 auto;
|
||
padding: 0 1.5rem;
|
||
}
|
||
main { padding: 2.5rem 0 4rem; }
|
||
|
||
/* Sections */
|
||
section { margin-bottom: 3rem; }
|
||
h2 {
|
||
font-size: 1.4rem;
|
||
font-weight: 600;
|
||
color: var(--ink);
|
||
padding-bottom: 0.5rem;
|
||
border-bottom: 2px solid var(--accent);
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
h3 {
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
color: var(--ink);
|
||
margin: 1.75rem 0 0.75rem;
|
||
}
|
||
h4 {
|
||
font-size: 0.95rem;
|
||
font-weight: 600;
|
||
color: var(--muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
margin: 1.5rem 0 0.5rem;
|
||
}
|
||
|
||
p { margin-bottom: 0.75rem; color: var(--ink); }
|
||
p.muted { color: var(--muted); font-size: 0.9rem; }
|
||
|
||
/* Cards */
|
||
.card {
|
||
background: var(--bg2);
|
||
border: 1px solid var(--rule);
|
||
border-radius: 8px;
|
||
padding: 1.25rem 1.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
/* Service grid */
|
||
.service-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: 1rem;
|
||
margin: 1.25rem 0;
|
||
}
|
||
.service-card {
|
||
background: var(--bg2);
|
||
border: 1px solid var(--rule);
|
||
border-radius: 8px;
|
||
padding: 1.25rem 1.5rem;
|
||
}
|
||
.service-card .port {
|
||
font-size: 0.8rem;
|
||
color: var(--accent);
|
||
font-weight: 600;
|
||
margin-bottom: 0.35rem;
|
||
}
|
||
.service-card .name {
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
color: var(--ink);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
.service-card .desc {
|
||
font-size: 0.85rem;
|
||
color: var(--muted);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* Tables */
|
||
.table-wrap {
|
||
overflow-x: auto;
|
||
overflow-y: auto;
|
||
max-height: 600px;
|
||
margin: 1rem 0 1.5rem;
|
||
border: 1px solid var(--rule);
|
||
border-radius: 6px;
|
||
}
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.88rem;
|
||
min-width: 700px;
|
||
}
|
||
thead {
|
||
background: #f1f5f9;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1;
|
||
}
|
||
th {
|
||
text-align: left;
|
||
padding: 0.65rem 0.85rem;
|
||
font-weight: 600;
|
||
font-size: 0.8rem;
|
||
color: var(--muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
border-bottom: 2px solid var(--rule);
|
||
white-space: nowrap;
|
||
}
|
||
td {
|
||
padding: 0.55rem 0.85rem;
|
||
border-bottom: 1px solid var(--rule);
|
||
vertical-align: top;
|
||
}
|
||
tr:last-child td { border-bottom: none; }
|
||
td code, th code {
|
||
background: #eef2ff;
|
||
color: var(--accent);
|
||
padding: 1px 6px;
|
||
border-radius: 3px;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.82em;
|
||
}
|
||
td .tag {
|
||
display: inline-block;
|
||
padding: 1px 8px;
|
||
border-radius: 10px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
}
|
||
td .tag.get { background: #dbeafe; color: #1d4ed8; }
|
||
td .tag.post { background: #d1fae5; color: #047857; }
|
||
td .tag.ws { background: #fef3c7; color: #b45309; }
|
||
|
||
/* Code blocks */
|
||
pre.code-block {
|
||
background: #1e293b;
|
||
color: #e2e8f0;
|
||
padding: 1rem 1.25rem;
|
||
border-radius: 6px;
|
||
overflow-x: auto;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.82rem;
|
||
line-height: 1.55;
|
||
margin: 0.75rem 0 1.25rem;
|
||
}
|
||
pre.code-block .cmt { color: #64748b; }
|
||
pre.code-block .key { color: #7dd3fc; }
|
||
pre.code-block .str { color: #a5d6a7; }
|
||
pre.code-block .num { color: #fbbf24; }
|
||
|
||
/* Architecture diagram */
|
||
.arch-diagram {
|
||
background: var(--bg2);
|
||
border: 1px solid var(--rule);
|
||
border-radius: 8px;
|
||
padding: 1.5rem;
|
||
margin: 1.25rem 0;
|
||
overflow-x: auto;
|
||
}
|
||
.arch-diagram svg { max-width: 100%; }
|
||
|
||
/* Callout */
|
||
.callout {
|
||
background: #eff6ff;
|
||
border-left: 4px solid var(--accent);
|
||
padding: 1rem 1.25rem;
|
||
border-radius: 0 6px 6px 0;
|
||
margin: 1.25rem 0;
|
||
font-size: 0.9rem;
|
||
}
|
||
.callout strong { color: var(--accent); }
|
||
|
||
.callout.warn {
|
||
background: #fff7ed;
|
||
border-left-color: #ea580c;
|
||
}
|
||
.callout.warn strong { color: #ea580c; }
|
||
|
||
/* Field list */
|
||
.field-grid {
|
||
display: grid;
|
||
grid-template-columns: 140px 100px 1fr;
|
||
gap: 0.5rem 1rem;
|
||
font-size: 0.85rem;
|
||
margin: 0.75rem 0 1.25rem;
|
||
}
|
||
.field-grid .f-name { font-weight: 600; color: var(--ink); font-family: 'JetBrains Mono', monospace; font-size: 0.82rem; }
|
||
.field-grid .f-type { color: var(--muted); font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; }
|
||
.field-grid .f-desc { color: var(--muted); }
|
||
|
||
/* Footer */
|
||
footer {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
color: var(--muted);
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
/* Responsive */
|
||
@media (max-width: 768px) {
|
||
header.hero { padding: 2.5rem 1rem 2rem; }
|
||
header.hero h1 { font-size: 1.6rem; }
|
||
.container { padding: 0 1rem; }
|
||
.service-grid { grid-template-columns: 1fr; }
|
||
.field-grid { grid-template-columns: 1fr; gap: 0.25rem; }
|
||
.field-grid .f-type { display: none; }
|
||
}
|
||
|
||
@media print {
|
||
header.hero { background: #fff; color: #000; }
|
||
.callout { border: 1px solid var(--rule); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="hero">
|
||
<h1>FinDataHub 数据使用文档</h1>
|
||
<p>金融数据平台 API 参考手册 — 涵盖快讯推送、行情查询、MCP 工具和数据库表结构</p>
|
||
<div class="meta">v2.0 · 2026-08-09 · 4 活跃数据源</div>
|
||
</header>
|
||
|
||
<main class="container">
|
||
|
||
<!-- ═══════════════ 系统概览 ═══════════════ -->
|
||
<section>
|
||
<h2>系统概览</h2>
|
||
|
||
<div class="arch-diagram">
|
||
<svg viewBox="0 0 900 340" xmlns="http://www.w3.org/2000/svg">
|
||
<defs>
|
||
<filter id="shadow"><feDropShadow dx="0" dy="1" stdDeviation="2" flood-opacity="0.08"/></filter>
|
||
<marker id="arr" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#94a3b8"/></marker>
|
||
</defs>
|
||
|
||
<!-- Clients -->
|
||
<rect x="20" y="40" width="140" height="50" rx="6" fill="#dbeafe" stroke="#2563eb" stroke-width="1.2" filter="url(#shadow)"/>
|
||
<text x="90" y="69" text-anchor="middle" font-size="13" font-weight="600" fill="#1d4ed8">Web 前端</text>
|
||
|
||
<rect x="20" y="110" width="140" height="50" rx="6" fill="#fef3c7" stroke="#b45309" stroke-width="1.2" filter="url(#shadow)"/>
|
||
<text x="90" y="139" text-anchor="middle" font-size="13" font-weight="600" fill="#b45309">MCP Client</text>
|
||
|
||
<rect x="20" y="180" width="140" height="50" rx="6" fill="#d1fae5" stroke="#059669" stroke-width="1.2" filter="url(#shadow)"/>
|
||
<text x="90" y="209" text-anchor="middle" font-size="13" font-weight="600" fill="#047857">Python SDK</text>
|
||
|
||
<rect x="20" y="250" width="140" height="50" rx="6" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1.2" filter="url(#shadow)"/>
|
||
<text x="90" y="279" text-anchor="middle" font-size="13" font-weight="600" fill="#6d28d9">外部 API 调用方</text>
|
||
|
||
<!-- Arrows clients → services -->
|
||
<line x1="160" y1="65" x2="230" y2="65" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arr)"/>
|
||
<line x1="160" y1="135" x2="230" y2="135" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arr)"/>
|
||
<line x1="160" y1="205" x2="230" y2="205" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arr)"/>
|
||
<line x1="160" y1="275" x2="230" y2="275" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arr)"/>
|
||
|
||
<!-- kuaixun-service -->
|
||
<rect x="240" y="30" width="180" height="100" rx="8" fill="#fff" stroke="#2563eb" stroke-width="2" filter="url(#shadow)"/>
|
||
<text x="330" y="65" text-anchor="middle" font-size="14" font-weight="700" fill="#1a1a2e">kuaixun-service</text>
|
||
<text x="330" y="85" text-anchor="middle" font-size="11" fill="#6b7280">快讯推送 · 端口 8100</text>
|
||
<text x="330" y="105" text-anchor="middle" font-size="10" fill="#2563eb">ws:// /api/v1 /api/v2</text>
|
||
|
||
<rect x="240" y="145" width="180" height="100" rx="8" fill="#fff" stroke="#059669" stroke-width="2" filter="url(#shadow)"/>
|
||
<text x="330" y="180" text-anchor="middle" font-size="14" font-weight="700" fill="#1a1a2e">findatahub-client</text>
|
||
<text x="330" y="200" text-anchor="middle" font-size="11" fill="#6b7280">行情查询 · 端口 8000</text>
|
||
<text x="330" y="220" text-anchor="middle" font-size="10" fill="#059669">REST / MCP / SDK</text>
|
||
|
||
<!-- Arrows services → DB -->
|
||
<line x1="420" y1="80" x2="500" y2="60" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arr)"/>
|
||
<line x1="420" y1="195" x2="500" y2="200" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arr)"/>
|
||
|
||
<!-- DB layer -->
|
||
<rect x="510" y="20" width="170" height="60" rx="6" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1" filter="url(#shadow)"/>
|
||
<text x="595" y="46" text-anchor="middle" font-size="12" font-weight="600" fill="#334155">news_flash</text>
|
||
<text x="595" y="64" text-anchor="middle" font-size="10" fill="#94a3b8">快讯 + 统一整合表</text>
|
||
|
||
<rect x="510" y="95" width="170" height="60" rx="6" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1" filter="url(#shadow)"/>
|
||
<text x="595" y="121" text-anchor="middle" font-size="12" font-weight="600" fill="#334155">fin_data_clean</text>
|
||
<text x="595" y="139" text-anchor="middle" font-size="10" fill="#94a3b8">清洗后统一 A 股行情</text>
|
||
|
||
<rect x="510" y="170" width="170" height="60" rx="6" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1" filter="url(#shadow)"/>
|
||
<text x="595" y="196" text-anchor="middle" font-size="12" font-weight="600" fill="#334155">tushare_data</text>
|
||
<text x="595" y="214" text-anchor="middle" font-size="10" fill="#94a3b8">TuShare 原始采集 (79表)</text>
|
||
|
||
<rect x="510" y="245" width="170" height="60" rx="6" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1" filter="url(#shadow)"/>
|
||
<text x="595" y="271" text-anchor="middle" font-size="12" font-weight="600" fill="#334155">akshare_data</text>
|
||
<text x="595" y="289" text-anchor="middle" font-size="10" fill="#94a3b8">AKShare 原始采集 (31表)</text>
|
||
|
||
<!-- Fetchers vertical -->
|
||
<rect x="420" y="260" width="90" height="40" rx="4" fill="#e2e8f0" stroke="#cbd5e1" stroke-width="1"/>
|
||
<text x="465" y="285" text-anchor="middle" font-size="10" fill="#475569">fetch → clean</text>
|
||
|
||
<line x1="465" y1="260" x2="465" y2="245" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4,2"/>
|
||
<line x1="465" y1="300" x2="595" y2="300" stroke="#94a3b8" stroke-width="1" marker-end="url(#arr)"/>
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="service-grid">
|
||
<div class="service-card">
|
||
<div class="port">端口 8100</div>
|
||
<div class="name">kuaixun-service</div>
|
||
<div class="desc">多源财经快讯推送服务。4 活跃数据源 (财联社/华尔街见闻/新浪/格隆汇),每分钟轮询,WebSocket 实时推送,四层去重。</div>
|
||
</div>
|
||
<div class="service-card">
|
||
<div class="port">端口 8000</div>
|
||
<div class="name">findatahub-client</div>
|
||
<div class="desc">A 股行情数据查询服务。REST API + Python SDK + MCP Server 三种接入方式,直连清洗后数据库。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<strong>推荐路径:</strong>快讯推送用 <code>/api/v2/news/live</code>(单表,已去重),行情查询用 Python SDK <code>Client.daily()</code> 或 MCP <code>get_daily_data</code>。
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════ 快讯 API ═══════════════ -->
|
||
<section>
|
||
<h2>快讯数据 API</h2>
|
||
<p class="muted">Base URL: <code>http://127.0.0.1:8100</code></p>
|
||
|
||
<h3>V2 统一接口(推荐)</h3>
|
||
<p>从 <code>news_unified</code> 单表读取已去重数据,性能优于 V1 的 12 分表 UNION。</p>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr><th>方法</th><th>路径</th><th>参数</th><th>说明</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span class="tag get">GET</span></td>
|
||
<td><code>/api/v2/news/live</code></td>
|
||
<td><code>limit</code>(1-200,50) <code>offset</code>(≥0) <code>source</code> <code>start</code> <code>end</code> <code>tag</code> <code>importance</code></td>
|
||
<td>分页实时快讯</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tag get">GET</span></td>
|
||
<td><code>/api/v2/news/search</code></td>
|
||
<td><code>q</code>(必填) <code>limit</code>(1-50,20)</td>
|
||
<td>全文搜索</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tag get">GET</span></td>
|
||
<td><code>/api/v2/news/stats</code></td>
|
||
<td>—</td>
|
||
<td>统计概览</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tag get">GET</span></td>
|
||
<td><code>/api/v2/news/heat</code></td>
|
||
<td>—</td>
|
||
<td>重要性分布</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tag get">GET</span></td>
|
||
<td><code>/api/v2/news/tags</code></td>
|
||
<td><code>limit</code>(1-100,50)</td>
|
||
<td>热门标签</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tag get">GET</span></td>
|
||
<td><code>/api/v2/news/summary</code></td>
|
||
<td><code>date</code>(YYYY-MM-DD)</td>
|
||
<td>当日 AI 摘要</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h4>V2 响应字段 (news_unified)</h4>
|
||
<div class="field-grid">
|
||
<div class="f-name">id</div><div class="f-type">BIGINT</div><div class="f-desc">统一记录 ID</div>
|
||
<div class="f-name">title</div><div class="f-type">VARCHAR(512)</div><div class="f-desc">快讯标题</div>
|
||
<div class="f-name">content</div><div class="f-type">TEXT</div><div class="f-desc">正文内容</div>
|
||
<div class="f-name">summary</div><div class="f-type">VARCHAR(256)</div><div class="f-desc">AI 摘要</div>
|
||
<div class="f-name">source_list</div><div class="f-type">JSON</div><div class="f-desc">多源信息 <code>{"sources":[{"source","source_news_id","url"}]}</code></div>
|
||
<div class="f-name">source_count</div><div class="f-type">INT</div><div class="f-desc">来源数量(≥2 即被多源交叉验证)</div>
|
||
<div class="f-name">tags</div><div class="f-type">JSON</div><div class="f-desc">AI 标签数组,跨源合并去重</div>
|
||
<div class="f-name">importance</div><div class="f-type">INT(0-5)</div><div class="f-desc">AI 重要性评分</div>
|
||
<div class="f-name">published_at</div><div class="f-type">DATETIME</div><div class="f-desc">发布时间</div>
|
||
<div class="f-name">first_seen_at</div><div class="f-type">DATETIME</div><div class="f-desc">首次采集时间</div>
|
||
<div class="f-name">content_hash</div><div class="f-type">CHAR(32)</div><div class="f-desc">MD5 精确指纹</div>
|
||
<div class="f-name">simhash</div><div class="f-type">BIGINT</div><div class="f-desc">64-bit SimHash 指纹</div>
|
||
</div>
|
||
|
||
<h3>V1 兼容接口(无需鉴权)</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>方法</th><th>路径</th><th>参数</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/news/live</code></td><td><code>limit offset source start end tag importance</code></td><td>分页快讯 (UNION 分表)</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/news/search</code></td><td><code>q limit</code></td><td>全文搜索 (60 req/min)</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/news/stats</code></td><td>—</td><td>统计概览</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/news/tags</code></td><td><code>limit</code></td><td>热门标签</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/news/heat</code></td><td>—</td><td>热度分布</td></tr>
|
||
<tr><td><span class="tag post">POST</span></td><td><code>/api/v1/news/favorite</code></td><td>Body: <code>{"source_code","news_id"}</code></td><td>切换收藏</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/news/favorites</code></td><td><code>limit offset</code></td><td>收藏列表</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>WebSocket 实时推送</h3>
|
||
<p class="muted">连接: <code>ws://127.0.0.1:8100/ws/live?key={API_KEY}</code></p>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>方向</th><th>消息</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>服务端 → 客户端</td><td><code>{"type":"new_news","inserted":N,"pushed":N}</code></td><td>有新快讯入库</td></tr>
|
||
<tr><td>服务端 → 客户端</td><td><code>{"type":"status","connections":N}</code></td><td>当前连接数</td></tr>
|
||
<tr><td>客户端 → 服务端</td><td><code>"ping"</code></td><td>心跳 → 响应 <code>"pong"</code></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<strong>鉴权:</strong>WebSocket 通过 URL query <code>?key=</code> 参数鉴权,与 HTTP <code>X-API-Key</code> 共用同一密钥。前端通过 <code>import.meta.env.VITE_WS_KEY</code> 注入。
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════ 行情 API ═══════════════ -->
|
||
<section>
|
||
<h2>行情数据 API</h2>
|
||
<p class="muted">Base URL: <code>http://127.0.0.1:8000</code></p>
|
||
|
||
<h3>REST API</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>方法</th><th>路径</th><th>参数</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/daily/{ts_code}</code></td><td><code>start_date</code>(必填) <code>end_date</code> <code>fields</code>(csv) <code>with_returns</code> <code>limit</code>(1-50000)</td><td>单股票日线 OHLCV</td></tr>
|
||
<tr><td><span class="tag post">POST</span></td><td><code>/api/v1/daily/batch</code></td><td>Body: <code>{"ts_codes":[...],"start_date","end_date","fields":null}</code></td><td>批量日线</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/tables</code></td><td>—</td><td>列出所有数据表</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/tables/{name}/schema</code></td><td>—</td><td>表结构</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/tables/{name}/meta</code></td><td>—</td><td>表元数据</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/quality/{table_name}</code></td><td>—</td><td>数据质量分</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/stats</code></td><td>—</td><td>数据库统计</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/health</code></td><td>—</td><td>健康检查</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/health/dashboard</code></td><td><code>source</code>(tushare/akshare/sina)</td><td>数据源健康仪表盘</td></tr>
|
||
<tr><td><span class="tag get">GET</span></td><td><code>/api/v1/health/history</code></td><td><code>hours</code>(默认168)</td><td>7天健康趋势</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>MCP 工具 (Stdio)</h3>
|
||
<p class="muted">协议: MCP 2024-11-05 · 鉴权: <code>MCP_TOKEN</code> 环境变量 (可选)</p>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>工具名</th><th>必填参数</th><th>可选参数</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>get_daily_data</code></td><td><code>ts_code</code></td><td><code>start_date end_date with_returns data_source limit</code></td><td>A 股日线 OHLCV</td></tr>
|
||
<tr><td><code>get_weekly_data</code></td><td><code>ts_code</code></td><td><code>start_date end_date</code></td><td>A 股周线</td></tr>
|
||
<tr><td><code>get_monthly_data</code></td><td><code>ts_code</code></td><td><code>start_date end_date</code></td><td>A 股月线</td></tr>
|
||
<tr><td><code>get_financial_indicator</code></td><td><code>ts_code</code></td><td><code>start_date end_date limit</code></td><td>财务指标 (ROE/ROA/EPS)</td></tr>
|
||
<tr><td><code>search_stock</code></td><td><code>keyword</code></td><td><code>limit</code></td><td>按名称/代码搜索股票</td></tr>
|
||
<tr><td><code>get_data_quality</code></td><td>—</td><td><code>table_name show_issues</code></td><td>数据质量查询</td></tr>
|
||
<tr><td><code>get_stats</code></td><td>—</td><td>—</td><td>数据库统计概览</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Python SDK</h3>
|
||
<pre class="code-block"><span class="cmt"># pip install findatahub-client (或本地 pip install -e .)</span>
|
||
<span class="key">from</span> findatahub <span class="key">import</span> Client
|
||
|
||
client = Client(database=<span class="str">"fin_data_clean"</span>)
|
||
|
||
<span class="cmt"># A 股日线行情</span>
|
||
df = client.daily(<span class="str">"000001.SZ"</span>, start_date=<span class="str">"20260101"</span>)
|
||
<span class="key">print</span>(df.columns) <span class="cmt"># ['ts_code','trade_date','open','high','low','close','pre_close','vol','amount','pct_chg']</span>
|
||
|
||
<span class="cmt"># 批量查询</span>
|
||
df = client.daily_batch(
|
||
[<span class="str">"000001.SZ"</span>, <span class="str">"600519.SH"</span>],
|
||
start_date=<span class="str">"20260101"</span>, end_date=<span class="str">"20260801"</span>
|
||
)
|
||
|
||
<span class="cmt"># 周线/月线</span>
|
||
df_w = client.weekly(<span class="str">"000001.SZ"</span>, start_date=<span class="str">"20250101"</span>)
|
||
df_m = client.monthly(<span class="str">"000001.SZ"</span>)
|
||
|
||
<span class="cmt"># 财务指标</span>
|
||
df = client.fina_indicator(<span class="str">"000001.SZ"</span>, limit=<span class="num">10</span>)
|
||
|
||
<span class="cmt"># 搜索股票</span>
|
||
df = client.search_stock(<span class="str">"平安"</span>)
|
||
|
||
<span class="cmt"># 数据质量</span>
|
||
score = client.quality_score(<span class="str">"stock_daily"</span>)
|
||
stats = client.stats() <span class="cmt"># 各表行数 + 质量分</span>
|
||
|
||
<span class="cmt"># 导出</span>
|
||
path = client.export(<span class="str">"stock_daily"</span>, fmt=<span class="str">"csv"</span>) <span class="cmt"># → ~/Downloads/findatahub_stock_daily.csv</span></pre>
|
||
</section>
|
||
|
||
<!-- ═══════════════ 数据库表结构 ═══════════════ -->
|
||
<section>
|
||
<h2>数据库表结构</h2>
|
||
|
||
<h3>快讯系统 (news_flash)</h3>
|
||
|
||
<h4>news_unified — 统一快讯整合表 (Silver 层)</h4>
|
||
<p class="muted">所有活跃源的快讯经四层去重后写入此表。V2 API 直接从此表读取。</p>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>字段</th><th>类型</th><th>约束</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>id</code></td><td>BIGINT</td><td>PK AI</td><td>主键</td></tr>
|
||
<tr><td><code>content_hash</code></td><td>CHAR(32)</td><td>UNIQUE</td><td>MD5(title+前200字) — L1 精确去重</td></tr>
|
||
<tr><td><code>simhash</code></td><td>BIGINT UNSIGNED</td><td>INDEX</td><td>64-bit SimHash — L2 近似去重</td></tr>
|
||
<tr><td><code>title</code></td><td>VARCHAR(512)</td><td>NOT NULL</td><td>快讯标题</td></tr>
|
||
<tr><td><code>content</code></td><td>TEXT</td><td></td><td>正文</td></tr>
|
||
<tr><td><code>summary</code></td><td>VARCHAR(256)</td><td></td><td>AI 摘要</td></tr>
|
||
<tr><td><code>source_list</code></td><td>JSON</td><td>NOT NULL</td><td>多源信息数组</td></tr>
|
||
<tr><td><code>source_count</code></td><td>INT</td><td></td><td>来源数</td></tr>
|
||
<tr><td><code>tags</code></td><td>JSON</td><td></td><td>AI 标签 (跨源合并)</td></tr>
|
||
<tr><td><code>importance</code></td><td>INT</td><td></td><td>AI 评分 0-5</td></tr>
|
||
<tr><td><code>published_at</code></td><td>DATETIME</td><td></td><td>发布时间</td></tr>
|
||
<tr><td><code>first_seen_at</code></td><td>DATETIME</td><td></td><td>首次采集时间</td></tr>
|
||
<tr><td><code>last_seen_at</code></td><td>DATETIME</td><td></td><td>最近采集时间</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h4>快讯分表 (Bronze 层)</h4>
|
||
<p class="muted">每个数据源一张分表,结构相同。活跃源: <code>news_cls</code>, <code>news_wallstreetcn</code>, <code>news_sina</code>, <code>news_gelonghui</code>, <code>news_10jqka</code>, <code>news_hotlist</code></p>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>字段</th><th>类型</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>id</code></td><td>BIGINT PK AI</td><td>主键</td></tr>
|
||
<tr><td><code>source_news_id</code></td><td>VARCHAR(50) UNIQUE</td><td>来源新闻 ID — L4 去重键</td></tr>
|
||
<tr><td><code>title</code></td><td>VARCHAR(500)</td><td>标题</td></tr>
|
||
<tr><td><code>content</code></td><td>TEXT</td><td>正文</td></tr>
|
||
<tr><td><code>tags</code></td><td>JSON</td><td>AI 标签</td></tr>
|
||
<tr><td><code>importance</code></td><td>INT DEFAULT 0</td><td>AI 评分</td></tr>
|
||
<tr><td><code>url</code></td><td>VARCHAR(500)</td><td>原文链接</td></tr>
|
||
<tr><td><code>published_at</code></td><td>BIGINT</td><td>发布时间 (ms 时间戳)</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>行情系统 (fin_data_clean)</h3>
|
||
|
||
<h4>stock_daily — A 股日线 (统一清洗 Schema)</h4>
|
||
<p class="muted">TuShare + AKShare 双源合并清洗后的标准日线数据。</p>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>字段</th><th>类型</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>ts_code</code></td><td>VARCHAR(16) PK</td><td>股票代码 (TuShare 格式,如 000001.SZ)</td></tr>
|
||
<tr><td><code>trade_date</code></td><td>VARCHAR(10) PK</td><td>交易日 YYYYMMDD</td></tr>
|
||
<tr><td><code>open</code></td><td>DOUBLE</td><td>开盘价</td></tr>
|
||
<tr><td><code>high</code></td><td>DOUBLE</td><td>最高价</td></tr>
|
||
<tr><td><code>low</code></td><td>DOUBLE</td><td>最低价</td></tr>
|
||
<tr><td><code>close</code></td><td>DOUBLE</td><td>收盘价</td></tr>
|
||
<tr><td><code>pre_close</code></td><td>DOUBLE</td><td>昨收价</td></tr>
|
||
<tr><td><code>vol</code></td><td>DOUBLE</td><td>成交量 (手)</td></tr>
|
||
<tr><td><code>amount</code></td><td>DOUBLE</td><td>成交额 (元)</td></tr>
|
||
<tr><td><code>pct_chg</code></td><td>DOUBLE</td><td>涨跌幅 (%)</td></tr>
|
||
<tr><td><code>data_source</code></td><td>VARCHAR(16)</td><td>来源: tushare / akshare / merged</td></tr>
|
||
<tr><td><code>is_validated</code></td><td>TINYINT(1)</td><td>是否通过双源交叉验证</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<strong>raw dbs:</strong><code>tushare_data</code> (79 表) 和 <code>akshare_data</code> (31 表) 存原始采集数据,含 A 股日/周/月线、财务指标、资金流、指数、板块行情、宏观指标、基金、期货等。完整清单见 <code>tables_registry.json</code>。
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════ 使用示例 ═══════════════ -->
|
||
<section>
|
||
<h2>使用示例</h2>
|
||
|
||
<h3>Curl — 获取最新快讯</h3>
|
||
<pre class="code-block"><span class="cmt"># 最新 20 条 (V2)</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8100/api/v2/news/live?limit=20"</span> | python3 -m json.tool
|
||
|
||
<span class="cmt"># 筛选高重要性快讯</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8100/api/v2/news/live?limit=20&importance=4"</span>
|
||
|
||
<span class="cmt"># 按来源筛选</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8100/api/v2/news/live?limit=20&source=cls"</span>
|
||
|
||
<span class="cmt"># 关键词搜索</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8100/api/v2/news/search?q=降准&limit=10"</span>
|
||
|
||
<span class="cmt"># 统计概览</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8100/api/v2/news/stats"</span></pre>
|
||
|
||
<h3>Curl — 查询行情数据</h3>
|
||
<pre class="code-block"><span class="cmt"># 平安银行 2026 年日线</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8000/api/v1/daily/000001.SZ?start_date=20260101"</span> | python3 -m json.tool
|
||
|
||
<span class="cmt"># 批量查询</span>
|
||
curl -s -X POST <span class="str">"http://127.0.0.1:8000/api/v1/daily/batch"</span> \
|
||
-H <span class="str">"Content-Type: application/json"</span> \
|
||
-d <span class="str">'{"ts_codes":["000001.SZ","600519.SH"],"start_date":"20260701","end_date":"20260801"}'</span>
|
||
|
||
<span class="cmt"># 数据质量</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8000/api/v1/quality/stock_daily"</span>
|
||
|
||
<span class="cmt"># 健康检查</span>
|
||
curl -s <span class="str">"http://127.0.0.1:8000/api/v1/health"</span></pre>
|
||
|
||
<h3>Python — 快讯 + 行情联动</h3>
|
||
<pre class="code-block"><span class="key">import</span> httpx
|
||
<span class="key">import</span> pandas <span class="key">as</span> pd
|
||
|
||
<span class="cmt"># 1. 获取高重要性快讯</span>
|
||
resp = httpx.get(<span class="str">"http://127.0.0.1:8100/api/v2/news/live"</span>,
|
||
params={<span class="str">"importance"</span>: <span class="num">4</span>, <span class="str">"limit"</span>: <span class="num">50</span>})
|
||
news = resp.json()[<span class="str">"data"</span>]
|
||
<span class="key">print</span>(<span class="str">f"高重要性快讯: {len(news)} 条"</span>)
|
||
|
||
<span class="cmt"># 2. 从快讯中提取股票代码</span>
|
||
<span class="key">import</span> re
|
||
codes = set()
|
||
<span class="key">for</span> n <span class="key">in</span> news:
|
||
found = re.findall(<span class="str">r'\b(\d{6}\.(?:SZ|SH))\b'</span>, n[<span class="str">"title"</span>])
|
||
codes.update(found)
|
||
|
||
<span class="cmt"># 3. 批量查询这些股票的行情</span>
|
||
<span class="key">if</span> codes:
|
||
resp2 = httpx.post(<span class="str">"http://127.0.0.1:8000/api/v1/daily/batch"</span>,
|
||
json={<span class="str">"ts_codes"</span>: list(codes),
|
||
<span class="str">"start_date"</span>: <span class="str">"20260801"</span>})
|
||
df = pd.DataFrame(resp2.json()[<span class="str">"data"</span>])
|
||
<span class="key">print</span>(<span class="str">f"行情数据: {len(df)} 行"</span>)
|
||
<span class="key">print</span>(df[[<span class="str">"ts_code"</span>, <span class="str">"close"</span>, <span class="str">"pct_chg"</span>]].head())</pre>
|
||
|
||
<h3>JavaScript (前端) — WebSocket 实时推送</h3>
|
||
<pre class="code-block"><span class="cmt">// 连接 WebSocket</span>
|
||
<span class="key">const</span> wsKey = import.meta.env.VITE_WS_KEY || <span class="str">''</span>;
|
||
<span class="key">const</span> ws = <span class="key">new</span> WebSocket(<span class="str">`ws://127.0.0.1:8100/ws/live?key=${encodeURIComponent(wsKey)}`</span>);
|
||
|
||
ws.onmessage = (e) => {
|
||
<span class="key">const</span> { type, inserted, pushed } = JSON.parse(e.data);
|
||
<span class="key">if</span> (type === <span class="str">'new_news'</span>) {
|
||
console.log(<span class="str">`新快讯: ${inserted} 条入库, ${pushed} 条推送`</span>);
|
||
<span class="cmt">// 拉取最新数据</span>
|
||
fetch(<span class="str">'/api/v2/news/live?limit=10'</span>).then(r => r.json()).then(render);
|
||
}
|
||
};
|
||
|
||
<span class="cmt">// 心跳保持</span>
|
||
setInterval(() => ws.send(<span class="str">'ping'</span>), <span class="num">30000</span>);</pre>
|
||
</section>
|
||
|
||
<!-- ═══════════════ 数据管道 ═══════════════ -->
|
||
<section>
|
||
<h2>数据管道流程</h2>
|
||
|
||
<div class="arch-diagram">
|
||
<svg viewBox="0 0 880 140" xmlns="http://www.w3.org/2000/svg">
|
||
<defs>
|
||
<filter id="sh2"><feDropShadow dx="0" dy="1" stdDeviation="2" flood-opacity="0.06"/></filter>
|
||
<marker id="a2" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#2563eb"/></marker>
|
||
</defs>
|
||
|
||
<!-- Step 1 -->
|
||
<rect x="10" y="35" width="120" height="70" rx="6" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5" filter="url(#sh2)"/>
|
||
<text x="70" y="62" text-anchor="middle" font-size="13" font-weight="700" fill="#1d4ed8">Fetch</text>
|
||
<text x="70" y="82" text-anchor="middle" font-size="10" fill="#4b5563">4 活跃源 60s 轮询</text>
|
||
|
||
<line x1="130" y1="70" x2="170" y2="70" stroke="#2563eb" stroke-width="2" marker-end="url(#a2)"/>
|
||
|
||
<!-- Step 2 -->
|
||
<rect x="175" y="35" width="120" height="70" rx="6" fill="#fef3c7" stroke="#b45309" stroke-width="1.5" filter="url(#sh2)"/>
|
||
<text x="235" y="62" text-anchor="middle" font-size="13" font-weight="700" fill="#b45309">Dedup</text>
|
||
<text x="235" y="82" text-anchor="middle" font-size="10" fill="#4b5563">L1 exact → L2 simhash → L3 jaccard</text>
|
||
|
||
<line x1="295" y1="70" x2="335" y2="70" stroke="#2563eb" stroke-width="2" marker-end="url(#a2)"/>
|
||
|
||
<!-- Step 3 -->
|
||
<rect x="340" y="35" width="120" height="70" rx="6" fill="#d1fae5" stroke="#059669" stroke-width="1.5" filter="url(#sh2)"/>
|
||
<text x="400" y="62" text-anchor="middle" font-size="13" font-weight="700" fill="#047857">Unify</text>
|
||
<text x="400" y="82" text-anchor="middle" font-size="10" fill="#4b5563">同源合并 → news_unified</text>
|
||
|
||
<line x1="460" y1="70" x2="500" y2="70" stroke="#2563eb" stroke-width="2" marker-end="url(#a2)"/>
|
||
|
||
<!-- Step 4 -->
|
||
<rect x="505" y="35" width="120" height="70" rx="6" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1.5" filter="url(#sh2)"/>
|
||
<text x="565" y="62" text-anchor="middle" font-size="13" font-weight="700" fill="#6d28d9">AI Score</text>
|
||
<text x="565" y="82" text-anchor="middle" font-size="10" fill="#4b5563">重要度 0-5 + 标签</text>
|
||
|
||
<line x1="625" y1="70" x2="665" y2="70" stroke="#2563eb" stroke-width="2" marker-end="url(#a2)"/>
|
||
|
||
<!-- Step 5 -->
|
||
<rect x="670" y="35" width="120" height="70" rx="6" fill="#ffe4e6" stroke="#e11d48" stroke-width="1.5" filter="url(#sh2)"/>
|
||
<text x="730" y="62" text-anchor="middle" font-size="13" font-weight="700" fill="#be123c">Push</text>
|
||
<text x="730" y="82" text-anchor="middle" font-size="10" fill="#4b5563">WebSocket 广播</text>
|
||
|
||
<line x1="790" y1="70" x2="830" y2="70" stroke="#2563eb" stroke-width="2" marker-end="url(#a2)"/>
|
||
|
||
<!-- Consumers -->
|
||
<rect x="835" y="35" width="45" height="70" rx="4" fill="#e2e8f0" stroke="#94a3b8" stroke-width="1"/>
|
||
<text x="857" y="75" text-anchor="middle" font-size="9" fill="#64748b">前端</text>
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<strong>行情管道:</strong>TuShare / AKShare 双源采集 → Cleaner 清洗 → QualityGate 四级校验 → CrossValidator 交叉验证 → <code>fin_data_clean.stock_daily</code> upsert。
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════ 鉴权 ═══════════════ -->
|
||
<section>
|
||
<h2>鉴权体系</h2>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>接口层</th><th>鉴权方式</th><th>适用场景</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>公开 API (v1/v2)</td><td>无需鉴权</td><td>前端页面、公开查询</td></tr>
|
||
<tr><td>内部 API</td><td><code>X-API-Key</code> Header</td><td>采集器管线、内部服务间调用</td></tr>
|
||
<tr><td>外部 API</td><td><code>X-API-Key</code> + <code>.api_keys.json</code> 验证</td><td>第三方客户端接入</td></tr>
|
||
<tr><td>WebSocket</td><td>URL query <code>?key=</code></td><td>浏览器实时推送</td></tr>
|
||
<tr><td>MCP Server</td><td><code>MCP_TOKEN</code> 环境变量 (可选)</td><td>AI Agent 工具调用</td></tr>
|
||
<tr><td>采集器控制</td><td>仅本地 (127.0.0.1) + <code>X-API-Key</code></td><td>/api/v1/collector/*</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout warn">
|
||
<strong>API_KEY 持久化:</strong>首次启动自动生成并保存至 <code>~/Library/Application Support/findatahub/.api_key</code> (chmod 600),重启后复用。可通过 <code>API_KEY</code> 环境变量覆盖。
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<footer>
|
||
<p>FinDataHub · 金融数据平台 · 生成于 2026-08-09</p>
|
||
</footer>
|
||
|
||
</body>
|
||
</html>
|