OpenStock 消费者边界与数据源迁移路线
核心决策:mystocks_spec 是 OpenStock 的纯消费者,不承担数据提供角色。本文记录这条边界的固化规则、退役清单与分批迁移路线。
一、边界定位(不可变更)
来自 architecture/standards/openstock-consumer-boundary-sot.md V1.0(2026-06-29 生效):
| 维度 | 规则 |
|---|---|
| 数据获取 | 所有市场/参考/衍生数据必须通过 OpenStockClient 调 OpenStock 中台。禁止业务代码直接 import akshare/baostock/zzshare/eltdx |
| 数据提供边界 | MyStocks 的 FastAPI 仅服务自身前端,不承担数据中台角色 |
| 本地缓存边界 | TDengine/PostgreSQL 仅作 OpenStock 数据的消费侧缓存与业务落地,不承担独立数据源 |
| 唯一例外目录 | web/backend/app/services/openstock_client.py(客户端本身)+ tests/(兼容回归测试) |
例外目录清单由 scripts/linting/forbidden_imports.py 维护,新增例外必须用户书面授权。
二、为什么要收口(历史教训)
仓库历史上存在三代 akshare 适配器并行,没有任何文档说明退役边界,导致每次"清理自主数据源"尝试都演变成代码继续堆叠:
| 代次 | 路径 | 规模 | 调用链 |
|---|---|---|---|
| Gen 1 扁平文件 | src/adapters/akshare/*.py(非子目录) |
~1,500 LOC | 链 B(后台/数据服务) |
| Gen 2 modules 子目录 | src/adapters/akshare/modules/ |
~500 LOC | 部分被 Gen 1 re-export |
| Gen 3 market_adapter 子包 | src/adapters/akshare/market_adapter/ |
2,075 LOC, 7 文件, 85 方法 | 链 A(Web API) |
加上 AkshareDataSource(483 LOC)、AkshareMarketDataAdapter(52 LOC)、akshare_market API 目录(3,088 LOC, 8 文件, ~50 端点),合计 ~5,525 LOC 适配器 + ~3,088 LOC API 路由待退役。
SOT 的目的不是描述现状,而是固化退役规则——后续所有迁移、清理、白名单扩展、CI 校验都按文件执行,不再每次 PR 重新讨论。
三、退役阶段(不可变更的执行顺序)
Phase 0 (边界声明 + CI Lint)
→ Phase 1 (Web API 域分批, 6 批)
→ Phase 2 (后台与数据服务)
→ Phase 3 (清理收口, 删除 ~8,600 LOC)
Phase 1:链 A(Web API)按业务域分批
| 批次 | 域 | 方法数 | 前端消费者 | OpenStock 类别 |
|---|---|---|---|---|
| 1.1 | Fund Flow(HSGT、北向、大单) | 18 | FundFlow.vue + dashboardService.ts |
FUND_FLOW / SECTOR_FUND_FLOW / NORTHBOUND_HOLDING / NORTHBOUND_FLOW / BLOCK_TRADE |
| 1.2 | Market overview(SSE/SZSE 聚合) | 10 | 无 | INDEX_QUOTES / ETF_SPOT |
| 1.3 | Boards / sectors | 16 | 无 | SECTOR_QUOTES / HOT_RANK / TOPIC_HEAT |
| 1.4 | Stock profile / holders | 8 | 无 | SHAREHOLDER_COUNT / INSTITUTION_HOLDING / STOCK_PROFILE |
| 1.5 | Forecast / chip dist | 8 | 无 | FORECAST_DATA / CONSENSUS_FORECAST |
| 1.6 | Sentiment / dragon-tiger | 22 | 无 | MARKET_SENTIMENT / SENTIMENT_TREND / DRAGON_TIGER* |
Phase 1 准入条件:
- 每个 PR 按 docs/guides/openstock-migration/DOMAIN_MIGRATION_PLAYBOOK.md 执行
- 每个域名 PR 独立提交、独立 review、独立浏览器验收
- 路由路径方案:保留 /api/akshare/market/* 路径不变,仅替换内部实现(方案 1)
- 每端点必须三项齐备:白名单扩展 + 单元解析测试 + API 端到端测试
完成标志:web/backend/app/api/akshare_market/ 下所有端点内部全部走 OpenStockClient,无任何方法直接 import akshare。
Phase 2:链 B(后台与数据服务)
准入:Phase 1 六批全部合入并稳定运行至少一个工作周。
切换内容:
- web/backend/app/tasks/data_sync.py → OpenStock
- web/backend/app/services/data_service*.py → OpenStock
- src/database/service/adapter_queries.py → OpenStock
- src/adapters/data_source_manager.py 退役 AkshareDataSource 注册
- TDengine/PostgreSQL 写入逻辑按 §四策略调整
完成标志:全仓库无业务代码/后台任务/数据服务直接 import akshare/baostock/zzshare/eltdx(CI Lint 自动验证)。
Phase 3:清理收口
准入:Phase 2 完成且 CI Lint 在主分支连续 14 天无违规。
- 删除
src/adapters/akshare/整个目录(~5,525 LOC) - 删除
web/backend/app/api/akshare_market/整个目录(~3,088 LOC)—— 路由统一切到/api/v1/market/*,前端一次性集中改造 - 从
requirements.txt/pyproject.toml移除akshare、baostock等数据源依赖
四、数据策略(TDengine 与 PostgreSQL)
4.1 默认:On-Demand Fetch
不再主动定时全量落库。除明确划定为"缓存"的表,所有数据走 on-demand:前端请求 → API 调用 → OpenStockClient 实时拉取 → 返回。存量数据不强制清理,但禁止继续写入。
4.2 TDengine 缓存边界
| 维度 | 规则 |
|---|---|
| 唯一允许的缓存用途 | OpenStock 中台整体不可用时的降级读取缓存(fallback cache) |
| 缓存写入触发 | 仅当 OpenStock 调用成功时同步写入(用于未来降级读取),禁止后台任务独立刷新 |
| 缓存失效 | TTL 由 OpenStockClient 配置统一管理(当前默认 5 秒,OpenStock 中台内置) |
| 缓存清理 | Phase 3 完成后由独立 PR 评估保留或删除 |
4.3 降级触发阈值
- OpenStock 连续失败 N 次(N 由熔断器配置,当前 3 次)→ 进入降级模式
- 降级行为:读 TDengine 缓存(命中且未过期)→ 返回
cache_state: degraded;不命中返回业务异常(不自动切换本地 akshare) - 熔断恢复:由 OpenStock 中台统一管控,MyStocks 侧不重复实现
五、迁移路径(契约层架构)
目标路径(来自 externalize-data-source-provider-to-openstock design.md):
1. 前端调用既有 MyStocks service API
2. MyStocks 后端兼容路由校验 auth、参数、响应形状、业务归一化
3. MyStocks OpenStock 消费者客户端把 provider-backed 请求发给 OpenStock
4. OpenStock 执行:provider 选择、健康检查、fetch、timeout、cache、fallback、circuit breaker、错误映射
5. MyStocks 把 OpenStock 运行时 envelope 映射回既有路由响应形状
关键设计决策: - 不把前端或业务代码直接挪到 OpenStock,MyStocks 后端仍是兼容与业务边界 - 已就绪映射先做(quotes、K-line/OHLCV) - fund-flow、sector-flow、LHB、block-trade、ETF provider refresh 视为 OpenStock 契约缺口,先在 OpenStock 补 - 本地持久化数据库 read model(业务/应用数据)归 MyStocks 所有,不随 provider 迁移
六、当前进展(2026-07-03)
分支 feat/b4-014-fundflow-mixin-openspec-proposal,主线 B4.014:
已合入:
- M1k/M1m:/quotes + /kline 路由通过 OpenStock factory,含单元测试 + 集成测试
- M1m-7/8:真实 OpenStock smoke + CodeWhale 第三方审计修复
- M1n:生产 smoke 修复,X-API-Key header 注入
- M1o:OpenStock API Key 通过 X-API-Key header 注入
- market-batch-01/02:data 模块前端修复(双嵌套 + LHB 错误 UI)
- myweb-audit P2:aria-label + 回归修复
- data-fund-flow-003:401/empty banner UX
- data-indicator:redirect 根因修复
- C3 Layer 1:add-extra-source-adapter-contract(ExtraSourceRouter 契约 + registry)
- Phase 2.1-2.3:ExtraSourceRouter + dispatch + lifespan adapter loading + 集成测试
- Phase 3:CI TEMP_OVERRIDE 过期检查
- D3:21 个 consumer workflow 加 submodule recursive + SUBMODULE_PAT
- D4:从 submodule 加载 OpenStock categories
进行中: - P0 gate:13/13 categories verified,P1 blocked on sibling Wave 1 dependency - task #11:non-fund-flow Mixin 迁移 proposal(AkshareMarketDataAdapter) - fundflow-mixin 迁移方案(proposal/design/tasks/spec 四件套已就位)
OpenSpec active changes(非 archive):
- externalize-data-source-provider-to-openstock(总纲)
- migrate-akshare-fundflow-mixin-to-openstock(当前批次)
- add-extra-source-adapter-contract(C3 契约层)
- optimize-data-source-v2(repo-local 已闭环,剩外部部署/观测/归档)
七、配套机制
7.1 Git Submodule 同步
deps/openstock/ submodule(git@github.com:chengjon/openstock.git)用于同步 OpenStock 的 data category 清单,避免契约漂移。21 个 consumer workflow 已配置 submodule recursive + SUBMODULE_PAT。
7.2 CI 强制校验
scripts/linting/forbidden_imports.py 维护例外目录清单,禁止业务代码直接 import 数据源 SDK。Phase 2 完成后 CI Lint 自动验证全仓库无违规。
7.3 迁移 Playbook
docs/guides/openstock-migration/DOMAIN_MIGRATION_PLAYBOOK.md(Phase 1.1 同步产出)规定每个域名 PR 的标准流程:白名单扩展 + 单元解析测试 + API 端到端测试三项齐备。
八、与用户架构原则的对齐
本迁移路线完全符合用户长期坚持的架构原则:
- 数据消费者直接通过 HTTP 消费数据提供者,不需要中间存储层(ClickHouse)—— mystocks_spec 直连 OpenStock,TDengine 仅作降级缓存
- MCP 是 agent/tool 协议,不适合做运行时数据源主通道 —— 运行时走 REST client(OpenStockClient),不走 MCP
- REST 直连优先,具备错误模型/重试/timeout/cache/health check —— OpenStockClient 承担所有这些职责