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 移除 aksharebaostock 等数据源依赖

四、数据策略(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 承担所有这些职责

相关页面见 mystocks-spec-项目全景mystocks-spec-治理体系