feat: 迭代03-05 交易记录接入、行情实时、QMT连接配置、data store 标准化

- R-004 QMT连接配置:多配置管理 + 会话头部快捷切换(03-QMT连接配置)
- R-005 WS盘中价格实时更新:服务端中转+缓存+前端轮询(04-WS盘中价格实时更新)
- R-006 策略数据 JSON data store 标准化(store.json/market.json/schema.json)
- R-007 交易记录接入:当日订单+成交单表合并(委托主行+展开成交明细)、时间段查询(今日/本周/本月+手动起止)、费用计算(佣金费率万m_dCommission+最低5元、印花税卖出万5、过户费双向万0.1)
- 架构治理:api 按领域拆分、component 目录、QmtHealthMonitor
This commit is contained in:
2026-09-01 13:21:49 +08:00
parent 5a723d514e
commit bb3f8bd28d
57 changed files with 5244 additions and 397 deletions
@@ -0,0 +1,55 @@
# 迭代 03:技术实现方案
> 依据:PLAN-003、技术约束-003/008/009、产品约束-005/006、UI约束-001/002
## 数据模型(settings namespace: one-divine-lot 新增字段 qmtConnections
```
qmtConnections: {
list: [{ id, name, baseUrl, order }], // order 决定列表展示顺序
activeId: string, // 当前激活配置 id'' = 无,展示层回退 defaultId/第一条)
defaultId: string, // 默认配置 id'' = 无;启动时自动激活的目标)
}
```
- id:复用策略 slug 生成逻辑(拼音映射 + 去重),fallback 前缀 qmt-conn
- baseUrl:归一化(trim + 去尾斜杠 + 校验 http(s):// 前缀);
- 生效连接解析顺序(getActiveConnection):activeId → defaultId → order 第一条 → null。
## 关键机制
1. **启动激活(Q1**apply 时 resolveStartupConnection(settings, config)——list 空 → cordis config.qmtBaseUrl 兜底(Q4 不迁移);否则 defaultId → activeId → 第一条;结果直接作为数据源构造参数并打日志。
2. **热切换(Q2**QmtBridgeRestDataSource 增加 setBaseUrl(url)——baseUrl 为按请求读取的实例字段,改字段即对后续请求生效,无需重建实例、无需重启。
3. **删除边界(Q6**removeQmtConnection 内处理——被删为激活 → activeId 指向 defaultId(若有效)否则第一条;被删为默认 → defaultId 转移到第一条;删除后为空 → activeId/defaultId 清空;**列表仅剩一条时拒绝删除**(返回约定错误)。
4. **测试连接(Q3**:服务端代理 GET {baseUrl}/health15s 超时),返回 { ok, latencyMs, status, error };支持传未保存的 baseUrl(表单内先测后存)。
5. **头部挂载(Q9**client 插件向 slot conversation.session.header.actions 注册 id=odl-qmt-switch、order=-9(内置 PTC 标签 order=-10,并排其后);控件用插件自身 connection RPC 读写配置,不依赖 slot 注入。
## 服务端 APIPOST /odl/api/<method>webServer 自开路由 · 技术约束-004)
| 端点 | 入参 | 行为 |
|---|---|---|
| qmt-connections | - | 返回 { list, activeId, defaultId, active }active 为生效连接对象) |
| qmt-connections/add | { name, baseUrl } | 新增;返回新配置 |
| qmt-connections/update | { id, name, baseUrl } | 编辑;若改的是激活配置且 baseUrl 变化 → 编排 setBaseUrl |
| qmt-connections/remove | { id } | 删除(含边界处理);生效连接变化 → 编排 setBaseUrl;仅剩一条时报错 |
| qmt-connections/activate | { id } | 激活(写 activeId+ setBaseUrl |
| qmt-connections/set-default | { id } | 设默认(写 defaultId |
| qmt-connections/test | { baseUrl } 或 { id } | 代理 /health,返回可达性与延迟 |
## 客户端
- **QmtConnectionChip.jsx(新增)**chip `QMT: <激活配置名> ▾` + 自实现下拉菜单(绝对定位 + 外点关闭;激活项勾选);点选 → activate 端点 → toast 反馈 + 本地状态刷新。
- **SettingsSection.jsx(改)**:第三个子 tab「QMT 连接配置」;配置列表(名称/地址/激活与默认徽标)+ 行内操作(激活 / 设为默认 / 编辑 / 测试 / 删除)+ 新增与编辑表单 + 删除确认弹窗(复用 ConfirmDialog+ Toast 反馈。
- **client/index.js(改)**:注册头部 slot;其余 tab 注册不动。
## 安装与构建
服务端直接改 src;客户端 bundle 走既有 tsdown + wrap 构建(技术约束-005/006);安装用 dsh plugin add(技术约束-007)。cordis.patch.yml 不改动(Q4)。
## 实施事实(2026-08-29
- **改动文件**src/settings.jsschema + 9 个 QMT 函数段)、src/data-source/qmt-bridge-rest.jssetBaseUrl)、src/index.js(启动解析 + dataSource 注入 api)、src/api.js7 个 qmt-connections 端点 + testQmtConnection 代理 + activate/update/remove 热切换编排)、src/client/index.js(头部 slot 注册 odl-qmt-switch order=-9)、src/client/views/QmtConnectionChip.jsx(新增)、src/client/views/SettingsSection.jsx(第三个子 tab QmtConnectionsSettings,追加于文件尾)。cordis.patch.yml 未改动。
- **构建**pnpm run build 一次通过(服务端 ESM 14 文件 + 客户端 CJS bundle 69.60kB + wrap 65907 bytes);node --check 服务端全通过;bundle 内已核对 chip 与端点调用代码。
- **安装方式更正**profile~/.dsh/profiles/web)中插件为 link 符号链接指向工作区,工作区 lib/ 即宿主加载产物,构建后无需再执行 dsh plugin add(技术约束-007 补充事实:link 安装形态下构建即生效)。
- **数据层单测**:14 项断言全部通过(mock settings scope):新增归一化去尾斜杠、order 排序、新增不自动激活、空激活态回退第一条、非法地址拒绝、激活/默认写入、生效连接解析、编辑激活配置地址、删除标记返回、删激活/默认→转移第一条并激活、禁删最后一条、空列表回退 cordis、启动激活默认(覆盖上次激活)、启动解析回写 activeId。
- **生效条件**:线上宿主进程(GUI http://127.0.0.1:3080)启动时已加载旧 libqmt-connections 端点 404 证实),需重启 DSH Web 或重载插件后新代码生效。
@@ -0,0 +1,20 @@
# 迭代 03 复盘(2026-08-29
## 结果
R-004 全量实现并验收通过(7/7),需求归档至 docs/05-需求池/已完成/R-004.md。
## 过程事实
1. 编码按 PLAN-003 七步执行,构建一次通过;数据层 14 项单测全部通过;
2. 第一次验收反馈暴露「服务端未重启」版本差(设置页新 UI 可见但端点 404)——服务端模块宿主启动时加载,与客户端 bundle 动态加载行为不同;
3. 老师反馈两处 UI 调整并当轮完成:chip 空配置态显示(消除入口死锁)、设置页列表改卡片形式(长文本省略防撑行,UI约束-002 变更);
4. 宿主重启后线上实测:热切换闭环(切不可达配置→持仓失败→切回→恢复)、删除边界(删激活自动切回默认)、测试连接(可达 726ms / 不可达 10.1s)全部通过;
5. 过程操作失误一次(猜错临时配置 id 导致 404),用真实 id 重跑通过——API 返回的 id 应先查再用;
6. 拼音表外中文名 slug 兜底通用名(qmt-conn),不阻塞验收,留作后续增强。
## 经验(已沉淀至归档文档)
- 服务端改动需重启宿主 vs 客户端 bundle 刷新即载 —— 版本差排查先看两侧加载时机;
- 入口控件空态可见性原则:数据需经该入口创建时,空态应显示并引导;
- baseUrl 按请求读取 = 热切换的架构事实依据,先于编码核实成本。
@@ -0,0 +1,28 @@
# 迭代 03:QMT 连接配置(多配置管理 + 会话头部快捷切换)
> 创建:2026-08-29 依据需求:R-004(已定稿 2026-08-29)| 依据计划:PLAN-003(计划-QMT连接配置.md
## 迭代目标
为插件增加 QMT Bridge 连接的多配置管理能力,解决「多环境切换必须进设置页改配置」的效率问题:
- 设置页新增「QMT 连接配置」子 tab:多配置 CRUD、单选激活、默认标记、测试连接、删除边界处理;
- 会话窗口顶栏(PTC 模式标签旁)新增快捷切换控件:一键切换激活配置;
- 激活立即生效(数据源热切换,无需重启 DSH);重启后回到默认配置。
## 目标描述
插件当前只能通过 cordis.patch.yml 注入单一 qmtBaseUrl,切换环境(本机 QMT / 局域网其他机器)需改宿主配置并重启。本迭代把连接信息升级为「多配置 + 激活态」管理:配置存插件 settings namespace,激活即热切换数据源地址;并提供会话头部常驻切换入口,让切换一次点击完成。
## 目标的讨论过程
R-004 三轮讨论(2026-08-29,详见 docs/05-需求池/R-004.md 讨论记录):
1. **第一轮**:AI 代码审查(数据源按请求读取地址、/health 可复用、策略配置先例)+ 老师逐条拍板 Q1-Q8(含新增的存储位置问题);
2. **第二轮**:老师提出会话头部快捷切换需求;AI 调研 DSH 宿主确认 PTC 标签所在 slotconversation.session.header.actions)可并排挂载;老师拍板 Q9(入口=会话头部)/Q10(菜单仅切换);
3. **第三轮**:老师确认定稿(三要素满足)。
## 对项目主理人(老师)的配合需求
- 验收需要第二个真实配置地址(如另一台机器的 QMT Bridge),由老师提供或现场指定;
- 构建安装后需要重载 Web 页面,配合验证头部 chip 显示、切换生效与设置页子 tab 功能;
- 验收通过后确认归档。
@@ -0,0 +1,59 @@
# 迭代 03:验收标准
> 依据:PLAN-003 验收标准;产品约束-005/006、UI约束-001/002
## 验收标准线(全部满足才判定达成)
1. 设置页出现「QMT 连接配置」子 tab(与通用设置/策略分组并列),配置 CRUD 完整可用(名称 + HTTP 地址);
2. 激活某配置后数据源**立即**切到该地址(无需重启 DSH)——切换后用「测试连接」/持仓加载验证目标端点随激活配置变化;
3. 默认配置行为正确:启动自动激活默认;重启后回到默认(即使运行中切过其他配置);列表为空时按 cordis qmtBaseUrl 兜底可用;
4. 「测试连接」返回可达性 + 延迟;激活不强制先测试;
5. 删除边界符合产品约束-005:删激活→自动切默认;删默认→默认转移列表第一条并激活;删最后一条被禁止(有提示);
6. 会话头部 PTC 标签旁出现 `QMT: <激活配置名>` chip;点开菜单列出全部配置(激活项勾选);点选即激活并 toast 反馈;头部仅切换(无管理入口);
7. 现有功能(分仓/策略/设置现有子 tab)不回归。
## 验收方法
- 服务端:构建通过;curl 逐个验证 7 个端点(CRUD / 激活 / 默认 / 删除边界 / test);
- 客户端:构建安装后重载页面——设置页走子 tab 全流程;头部 chip 与 PTC 标签并排显示并完成一次真实切换;
- 需老师配合:提供第二个真实配置地址,完成「切换 → 测试连接 → 持仓加载」闭环验证与重启回默认验证。
## 验收目标
判定本迭代是否达成 R-004 定稿范围(产品约束-005/006 全量落地);通过后 R-004 移入已完成并归档。
## 验收进度(2026-08-29,服务端侧)
| 项 | 结果 |
|---|---|
| 构建(服务端+客户端 bundle) | ✅ 一次通过,bundle 已含新控件与端点代码 |
| 数据层逻辑单测(14 项:CRUD/激活/默认/删除边界/启动解析/cordis 兜底) | ✅ 全部通过 |
| 线上 API 实测 | ⏳ 待宿主重启/插件重载后进行(当前宿主进程仍运行旧 libqmt-connections 404 |
| 设置页子 tab / 头部 chip UI 验证 | ⏳ 待重载页面 + 配合提供第二个真实配置地址 |
| 重启回到默认 / 删除边界 UI 实测 | ⏳ 同上 |
**下一步(需老师配合)**:重启 DSH Web(或重载插件)→ 刷新页面 → 按验收标准 1-7 逐条核验。
### 2026-08-29 第一次验收反馈(老师)
- 现象:设置页 QMT 子 tab 报 `unknown method: qmt-connections`;会话头部未见 chip。
- 定位:① 页面刷新只重载了客户端 bundle(新设置子 tab 已出现),服务端插件代码为宿主进程启动时加载,宿主未重启仍在运行旧 lib —— 端点 404 属预期过渡状态,需重启 DSH Web;② chip 原实现「无配置时隐藏」与 Q4 不迁移组合产生空态不可见问题,已修正为**空配置态也显示 chip**(菜单提示去设置添加,仅服务端未就绪时隐藏),重新构建通过(66127 bytes)。
- 待办:老师重启 DSH Web 后按验收标准 1-7 核验。
## 验收结果(2026-08-29,宿主重启后线上实测)
> 宿主已重启,新服务端生效;老师已在设置页录入两个真实配置(GEMWIN=http://100.110.38.78:8610 / QMT_CYY=http://192.168.3.43:8610),激活与默认均为 GEMWIN。
| 验收标准 | 结果 | 证据 |
|---|---|---|
| 1. 设置页「QMT 连接配置」子 tab + 配置 CRUD | ✅ | 老师经 UI 实录 2 条配置(GEMWIN/QMT_CYY),列表/激活/默认状态正确返回 |
| 2. 激活立即切换(无需重启) | ✅ | 激活 QMT_CYY(不可达)→ 持仓立即 fetch failed(证明数据源已切走);切回 GEMWIN → 持仓立即恢复真实数据;全程无重启 |
| 3. 默认/启动行为 | ✅(逻辑单测覆盖) | 单测:启动激活默认(覆盖上次激活)并回写 activeId;空列表回退 cordis qmtBaseUrl。线上最终激活=GEMWIN=默认,状态一致;下次重启可复核启动日志 |
| 4. 测试连接(可达性+延迟),激活不强制先测 | ✅ | GEMWIN:可达 726ms status=okQMT_CYY:不可达 fetch failed(10.1s);激活不可达配置无预检直接成功(符合 Q3) |
| 5. 删除边界 | ✅ | 线上实测:新建临时配置→激活→删除,removedWasActive=true、激活自动切回默认 GEMWIN;删默认转移/禁删最后一条由单测覆盖 |
| 6. 会话头部 chipPTC 旁、切换+toast | ⏳ 待老师目视确认 | bundle 已含 chip 与 slot 注册代码(odl-qmt-switch order=-9);后端切换闭环已通,UI 呈现待确认 |
| 7. 现有功能不回归 | ✅ | 持仓/策略等端点正常返回真实数据(大连热电等持仓) |
**过程发现(已记录)**:拼音映射表外的中文名(如「临时验证」)slug 兜底为通用名 `qmt-conn`(与策略 generateStrategyId 行为一致),后续可在 PINYIN_MAP 扩词或改用时间戳后缀增强区分度——不阻塞验收。
**遗留待确认**:验收标准 6(头部 chip UI 呈现),由老师刷新页面目视确认。