/** * 服务端 HTTP API 入口(2026-08-31 从 api.js 按领域拆分) * * 结构: * src/api/ * ├── index.js # 路由入口:合并各领域 + 公共处理(405/404/500) * ├── common.js # 公共工具(writeJson/readJsonBody/resolveActiveBaseUrl) * ├── positions.js # 持仓域(positions) * ├── strategies.js # 策略/份额/tabs 域 * ├── qmt-connections.js # QMT 连接配置域(含热切换编排) * └── market.js # 行情域(market-snapshot) * * 端点(POST /odl/api/,body = { args: {...} }): * positions → 全量持仓 * strategies → 策略列表(含 visible) * strategies/update → 更新策略列表(整表) * strategies/add → 新增策略 { name } * strategies/remove → 删除策略 { strategyId },联动清份额 * strategies/move → 排序 { strategyId, dir } * strategy-positions → 某策略持仓 { strategyId } * strategy-holdings/history → 某策略已清仓历史持仓 { strategyId, range }(R-016) * unallocated → 未分配持仓 * summary → 分仓摘要 * add-shares → 添加份额 { code, strategyId, shares } * move-all-shares → 全部移入 { code, strategyId } * remove-shares → 移出份额 { code, strategyId, shares } * tabs / tabs/update → 通用 tab 显隐配置 * qmt-connections → QMT 连接配置(R-004) * qmt-connections/* → 增删改/激活/默认/测试/active-host/订阅代理 * market-snapshot → 按 code 查询行情缓存(R-005) */ import { API_PREFIX, writeJson, readJsonBody } from './common.js'; import { POSITION_METHODS, handlePosition } from './positions.js'; import { STRATEGY_METHODS, handleStrategy } from './strategies.js'; import { QMT_METHODS, handleQmt } from './qmt-connections.js'; import { MARKET_METHODS, handleMarket } from './market.js'; import { TRADE_METHODS, handleTrade } from './trades.js'; /** 全部端点方法表(合并各领域) */ const METHODS = new Set([ ...POSITION_METHODS, ...STRATEGY_METHODS, ...QMT_METHODS, ...MARKET_METHODS, ...TRADE_METHODS, ]); /** 领域分发:method → handler */ const HANDLERS = [ { set: POSITION_METHODS, handle: handlePosition }, { set: STRATEGY_METHODS, handle: handleStrategy }, { set: QMT_METHODS, handle: handleQmt }, { set: MARKET_METHODS, handle: handleMarket }, { set: TRADE_METHODS, handle: handleTrade }, ]; /** * 注册 HTTP API * @param {import('@deepseek-ai/cordis').Context} ctx * @param {object} runtime { manager, settings, dataSource, marketCache } */ export function registerApi(ctx, { manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync }) { const runtime = { ctx, manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync }; ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: '/odl/api', handler: async (req, res) => { if (req.method !== 'POST') { writeJson(res, 405, { ok: false, error: { code: 'method-error', message: 'method not allowed' } }); return; } const pathname = new URL(req.url ?? '/', 'http://dsh.internal').pathname; const method = pathname.startsWith(API_PREFIX) ? pathname.slice(API_PREFIX.length) : void 0; if (!method || !METHODS.has(method)) { writeJson(res, 404, { ok: false, error: { code: 'not-found', message: 'unknown method: ' + method } }); return; } try { const body = await readJsonBody(req); const args = body?.args ?? {}; // 按领域分发 let value; for (const { set, handle } of HANDLERS) { if (set.has(method)) { value = await handle(method, args, runtime); break; } } writeJson(res, 200, { ok: true, value }); } catch (error) { writeJson(res, 500, { ok: false, error: { code: error?.code ?? 'internal', message: error?.message ?? String(error), details: {} }, }); } }, }), 'one-divine-lot.api'); ctx.logger?.info('[one-divine-lot] HTTP API 已注册 (/odl/api/*)'); }