mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
feat: 数据源插件化架构 + stock-sdk 首个插件
将可选数据源改为插件化架构: 插件代码放 backend/app/plugins/<name>/, 用户在设置页点击安装依赖, 主仓库不背运行时依赖(nodejs 等)。 架构: - loader.py 新增 _load_builtin_plugins() 扫描 plugins/ 目录, 通过 plugin.yaml 清单动态发现并注册插件(委托自检 + 优雅降级) - runtime 字段支持 node/python, 安装/卸载分别用 npm/pip - 现有 tickflow + YAML 自定义源逻辑 100% 保留, 插件是叠加层 stock-sdk 插件 (plugins/stocksdk/): - 原始实现来自 @forrany 的 PR #57, 迁移到插件化架构, 署名保留 - Node 桥接 (bridge.py/mjs) + Python provider, 覆盖日K/除权/分钟/实时 - 设置页主列表直接显示: 未装灰显+安装按钮, 装完可切换/卸载 配套: - instrument_sync: 通用增强, 任何 provider 都能提供标的维表 - install/uninstall: uv 优先回退 pip, 容错坏 uv.toml + 国内镜像 - 10 例单测全过, tsc 零错误
This commit is contained in:
@@ -413,10 +413,11 @@ def get_preferences() -> dict:
|
||||
|
||||
@router.get("/data-sources")
|
||||
def list_data_sources() -> dict:
|
||||
"""列出已加载的自定义数据源。"""
|
||||
"""列出已加载的数据源 (内置 / 插件 / 用户自定义)。"""
|
||||
from app.data_providers import custom as custom_sources
|
||||
return {
|
||||
"builtin": [{"name": "tickflow", "display_name": "TickFlow", "datasets": ["daily", "adj_factor", "realtime"]}],
|
||||
"builtin": [{"name": "tickflow", "display_name": "TickFlow", "datasets": ["daily", "adj_factor", "realtime", "minute"]}],
|
||||
"plugins": custom_sources.list_plugins(),
|
||||
"custom": custom_sources.list_sources(),
|
||||
"errors": custom_sources.errors(),
|
||||
"config_dir": str(custom_sources.data_sources_dir()),
|
||||
@@ -431,6 +432,52 @@ def reload_data_sources() -> dict:
|
||||
return list_data_sources()
|
||||
|
||||
|
||||
@router.post("/plugins/{name}/install")
|
||||
def install_plugin(name: str) -> dict:
|
||||
"""安装指定插件的依赖 (npm install / pip install), 完成后重新扫描。
|
||||
|
||||
根据 plugin.yaml 的 runtime 字段决定安装方式。安装可能耗时较长 (网络下载),
|
||||
客户端需设较长超时。
|
||||
"""
|
||||
from app.data_providers import custom as custom_sources
|
||||
if not custom_sources.is_builtin(name):
|
||||
raise HTTPException(status_code=404, detail=f"插件 '{name}' 不存在")
|
||||
ok, message = custom_sources.install_plugin(name)
|
||||
# 无论成功失败都重新扫描, 刷新插件状态 (安装可能部分成功)
|
||||
custom_sources.load_all()
|
||||
result = list_data_sources()
|
||||
result["install_ok"] = ok
|
||||
result["install_message"] = message
|
||||
return result
|
||||
|
||||
|
||||
@router.delete("/plugins/{name}/install")
|
||||
def uninstall_plugin(name: str) -> dict:
|
||||
"""卸载指定插件的依赖 (删除 node_modules / pip uninstall), 完成后重新扫描。
|
||||
|
||||
如果该插件当前正被使用, 自动回退到 tickflow。
|
||||
"""
|
||||
from app.data_providers import custom as custom_sources
|
||||
from app.services import preferences
|
||||
if not custom_sources.is_builtin(name):
|
||||
raise HTTPException(status_code=404, detail=f"插件 '{name}' 不存在")
|
||||
ok, message = custom_sources.uninstall_plugin(name)
|
||||
# 卸载后若该插件正被使用, 回退 tickflow
|
||||
for getter, key, default in [
|
||||
(preferences.get_daily_data_provider, "daily_data_provider", "tickflow"),
|
||||
(preferences.get_minute_data_provider, "minute_data_provider", "tickflow"),
|
||||
(preferences.get_realtime_data_provider, "realtime_data_provider", "tickflow"),
|
||||
(preferences.get_financial_provider, "financial_data_provider", "tickflow"),
|
||||
]:
|
||||
if getter() == name:
|
||||
preferences.save({key: default})
|
||||
custom_sources.load_all()
|
||||
result = list_data_sources()
|
||||
result["uninstall_ok"] = ok
|
||||
result["uninstall_message"] = message
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/data-sources/{name}")
|
||||
def get_data_source(name: str) -> dict:
|
||||
"""读取一个自定义数据源的完整配置(用于前端编辑回填)。"""
|
||||
|
||||
Reference in New Issue
Block a user