文档用途:指导后续实现与评审;实现阶段再拆任务。
非目标(本文不展开):交易所 API Key / Secret 的加解密与密钥轮换(保持与现有LoadConfig行为一致即可,或后续单独立项)。
- 已实现:主配置权威在 主库
app_config(JSON);Web 与资金/AI 等路径经SaveAppConfigSnapshot/SaveAppConfigSnapshotFromJSON持久化。可选 一次性 YAML 文件 仅用于导入/迁移(--migrate-app-config、命令行首参),不再以固定磁盘config.yaml为 SSOT。 - 仍保留
LoadConfig/SaveConfig供 CLI、工具链与迁移读取 任意路径 YAML。 - 配置体为大型嵌套结构
config.Config,含 map(如exchanges)、切片(如SymbolConfig.Strategies、RocketTieredGridConfig.Tiers)、不定长列表等。 - 已有 SQLite
system_settings:key / value / type,支持 JSON 读写(见storage/system_settings.go)。 - 已有 「数据库覆盖 YAML」 模式:价差监控等用 JSON blob 存 key,运行时合并(见
web/api_basis_config.go中basis_monitor_config)。
- 去掉对磁盘
config.yaml的依赖(生产环境):应用配置以 数据库为权威来源(SSOT)。 - 连接信息(数据库路径/URL 等)放在
.env或环境变量,保证进程能先连库再读配置。 - 每次配置变更 表现为对库中 受版本约束的配置文档 的更新(见第 4 节),而非散落无数无关联的字符串 key。
- 迁移:首次切换时能将 既有
config.yaml全量导入 数据库,且 可重复执行、幂等(见第 7 节)。 - Bot 配置:与主配置同期 全部入库(见第 4.4 节);不再将「Bot 级 YAML」推迟到后续阶段。
- 本特性在 独立功能分支(例如
feature/config-db-ssot)上开发与评审,避免在main上直接堆叠大改。 - 合并前:迁移脚本幂等性、回滚路径、双写/读切换策略需评审通过后再合入。
| 原则 | 说明 |
|---|---|
| SSOT | 运行时仅信任一份「主配置文档」;避免 YAML 与 DB 长期双写双读。 |
| 结构保持 | 仍以现有 config.Config(JSON 标签)为序列化形状,减少业务层分叉。 |
| 数组与不定长 | 不要用「每个叶子一行」的纯 KV 表达深层数组;用 JSON 子树或整文档(见第 4 节)。 |
与 system_settings 共存 |
现有按 key 存的小块 JSON(如 basis_monitor_config)可 逐步收敛 到主文档,或过渡期 读时合并(见第 6 节)。 |
flowchart LR
subgraph env [Env]
DotEnv[".env / ENV"]
end
subgraph boot [Startup]
DotEnv --> OpenDB["Open DB"]
OpenDB --> LoadDoc["Load app_config row"]
LoadDoc --> Unmarshal["json.Unmarshal to Config"]
end
subgraph runtime [Runtime]
Unmarshal --> App["Trading / Web / Workers"]
API["Config API"] --> WriteDoc["UPDATE app_config + version"]
WriteDoc --> App
end
.env:仅承载 连接与启动必需项(例如 SQLite 文件路径、或未来 Postgres DSN;与现有main.go存储初始化方式对齐)。- 数据库:存 主配置 JSON 文档、每 Bot 一行配置,以及 首版即上线的历史表(主配置历史 + Bot 配置历史),用于审计与回滚。
flowchart TB
subgraph mainCfg [MainConfig]
AppConfig[app_config]
AppHist[app_config_history]
AppConfig -->|each write| AppHist
end
subgraph botCfg [BotConfig]
BotRow[bot_configs per bot_id]
BotHist[bot_config_history]
BotRow -->|each write| BotHist
end
建议单列大 JSON(与现有 Config 对齐),避免把 symbols[]、tiers[]、strategies[] 拆成关系表(除非未来有多租户报表需求)。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
INTEGER PK | 固定 1 单例行,或 UUID |
schema_version |
INTEGER | 配置模式版本,用于迁移与兼容 |
content |
TEXT/JSON | 整份 config.Config 的 JSON 序列化 |
revision |
INTEGER | 乐观锁:每次成功写入 +1 |
content_hash |
TEXT 可选 | SHA-256,便于比对与备份校验 |
updated_at |
TIMESTAMP | 更新时间 |
数组 / 不定长:全部放在 content 的 JSON 内(例如 SymbolConfig 的 Strategies []StrategyInstance、RocketTieredGrid.Tiers)。
「每次变更一个 key」的语义:实现层应支持两类操作(可同时提供):
- 整包替换:PUT 整个
content(前端或运维最省事,需带revision防并发覆盖)。 - 子路径 PATCH:对 JSON Pointer 或点路径如
basis_monitor/symbols更新 一个子树;数组字段以 整段数组 为值原子替换,避免symbols[0]、symbols[1]拆行。
每次 app_config 成功提交新版本 时,追加一行历史(或先插入历史再更新当前行,同一事务内完成)。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
INTEGER PK AUTO | 自增 |
revision |
INTEGER | 与当时 app_config.revision 一致,便于对应 |
content |
TEXT/JSON | 该版本的完整 JSON 快照 |
content_hash |
TEXT 可选 | 便于去重与对账 |
operator |
TEXT 可选 | 操作者标识(用户 ID、API、CLI migrate 等) |
source |
TEXT 可选 | 如 api、cli_migrate、rollback |
created_at |
TIMESTAMP | 写入时间 |
回滚语义:从 app_config_history 选取某一 revision 的 content,写入 app_config(revision 递增),并 再次 向历史表追加一条(保留「谁把配置恢复到了哪一版」的审计链)。
结论:每个 Bot 单独一条当前记录 + 独立修改历史,与「所有 Bot 塞进主配置一个 JSON」相比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 每 Bot 一行(推荐) | 按 bot_id 乐观锁;单 Bot 变更写入面小;历史按 Bot 查询清晰;列表/权限可扩展 |
需与主配置中「Bot 列表」字段保持引用一致(见下) |
| 仅主配置 JSON 内含 bots[] | 单表简单 | 大单行竞争、历史全量快照笨重、难以按 Bot 审计 |
表 bot_configs(当前态,一行一 Bot)
| 字段 | 类型 | 说明 |
|---|---|---|
bot_id |
TEXT PK | 与 BotConfig.ID 及目录名一致 |
schema_version |
INTEGER | Bot 配置 JSON 形状版本(可与主配置 schema 独立演进) |
content |
TEXT/JSON | 单份 BotConfig 的 JSON |
revision |
INTEGER | 乐观锁,每次成功写入 +1 |
content_hash |
TEXT 可选 | SHA-256 |
updated_at |
TIMESTAMP | 更新时间 |
表 bot_config_history(追加式历史)
| 字段 | 类型 | 说明 |
|---|---|---|
id |
INTEGER PK AUTO | 自增 |
bot_id |
TEXT FK/索引 | 对应 bot_configs.bot_id |
revision |
INTEGER | 该快照对应的 revision |
content |
TEXT/JSON | 完整 BotConfig 快照 |
operator / source / created_at |
同主配置历史 | — |
与主配置的关系:主配置 config.Config 里若仍包含 Bot 清单/引用(例如启用哪些 Bot、全局顺序),主配置 app_config.content 负责 编排;每个 Bot 的参数字段以 bot_configs 为 SSOT。迁移时需定义清晰规则:从旧 YAML 拆出 per-bot 行 + 主配置中仅保留 ID 列表或等价结构,避免两处重复存储同一套策略参数(实现阶段在代码层做单一写入 API,禁止手改两处)。
- 过渡期:读配置时顺序可为:
content主文档 → 再应用system_settings中仍存在的遗留 key(与当前GetEffectiveConfig思路一致)。 - 终态:将
basis_monitor_config等迁入主文档字段,删除重复 key,避免两处修改。
- 与 应用版本 解耦:仅表示 JSON 形状 演进。
- 升级步骤:读库 → 若
schema_version < N,在内存中补默认值或跑迁移函数 → 写回schema_version=N。 - 与
cfg.Validate()配合:加载后仍走现有校验,保证业务规则一致。
- 解析环境变量,打开存储(与当前
storage一致)。 - 读取
app_config;若 无行或为空,进入 迁移或默认(第 7 节)。 json.Unmarshal→*config.Config;必要时执行 解密占位(非本文范围时仍可调现有LoadConfig内解密逻辑,或加载后单独对Exchanges走同一套函数)。- 按需加载
bot_configs(按bot_id列表或全表),组装内存中的 Bot 运行时视图(与现有BotManager生命周期对齐)。 Validate()(主配置 + 各 Bot 配置)。- 若仍存在
system_settings覆盖项,按模块 合并(过渡期),与BasisMonitorController.GetEffectiveConfig同模式。
触发条件(示例):显式执行 ./quantmesh --migrate-app-config,且 YAML 路径来自 命令行第一参数、环境变量 QUANTMESH_IMPORT_YAML,或工作目录下存在 config.yaml(与 main.go 一致)。
步骤:
yaml.ReadFile→ 已有逻辑可用 与LoadConfig相同 的解析路径得到*Config(含解密若启用)。json.Marshal(cfg)→ 写入app_config(schema_version初始为 1,revision=1)。- 幂等:若
app_config已有revision>0且非FORCE,则跳过或仅校验 hash(需QUANTMESH_MIGRATE_APP_CONFIG_FORCE=1覆盖)。 - 归档:将原 YAML 重命名为带时间戳的备份名(与
config.RenameConfigYAMLToBackup一致),避免双源。
Bot 目录 bots/{bot_id}/config.yaml(首版即迁移):
- 扫描
bots/下子目录(或从主配置已知的bot_id列表),对每个config.yaml执行与LoadConfig一致的解析(若 Bot 级与主配置解密策略一致则复用)。 - 每个文件写入
bot_configs一行(revision=1),并 可选在bot_config_history写入一条初始快照(source=cli_migrate),便于与后续变更统一审计。 - 幂等:若某
bot_id在bot_configs已存在且非FORCE,则跳过或仅校验 hash。 - 归档各
bots/{id}/config.yaml(同主配置策略),避免双源。
主配置与 Bot 编排:若迁移后主 config.Config 内仍含「Bot ID 列表」类字段,迁移脚本应 去重:策略参数以 bot_configs.content 为准,主配置中只保留引用/顺序,避免同一参数在 app_config 与 bot_configs 两处重复(实现时由单一写入路径保证)。
目标:用户无需手动执行 --migrate-app-config,在「仍使用 YAML 单机部署」的典型场景下,首次启动自动完成:可选生成 .env、自动入库、自动归档 YAML。
二次启动(未再传入 YAML 首参):main 在未指定命令行 YAML 路径时,按 QUANTMESH_SQLITE_PATH(默认 ./data/quantmesh.db)调用 LoadConfigFromAppConfigDBIfExists;若库中有有效 app_config 快照则直接加载,否则在内存中使用 CreateMinimalConfig(不强制在磁盘写入 config.yaml)。
- 主库已能打开(
storage已初始化且GetStorage()非空)。 app_config无有效快照(无行、或revision=0、或content为空)。- 磁盘上存在
config.yaml(或 CLI 指定的配置文件路径),且LoadConfig成功(能通过Validate())。 - 未设置
QUANTMESH_SKIP_AUTO_MIGRATE=1(供高级用户、CI、只读容器显式关闭)。 - (可选收紧)仅当配置「可用于交易」或「非最小化向导态」时再自动迁移,避免把不完整的
CreateMinimalConfig写进库;若选择「任何合法 Validate 都迁」,需在文档中说明。
- 不要把
config.yaml里的 API Key、Secret、Passphrase 自动抄进.env(避免双份明文、误提交、与加密配置策略冲突)。 - 可以写入 非密钥、可重复推导 的项,便于后续「无 YAML 启动」与运维对齐,例如:
QUANTMESH_DATA_DIR或QUANTMESH_SQLITE_PATH:与当前cfg.Storage.Path/cfg.Database.DSN解析结果一致(SQLite 文件绝对路径或约定相对路径)。QUANTMESH_BOTS_DIR:默认./bots。- 注释块说明:
# 主配置已迁移至数据库,见 app_config;本文件仅作连接与路径提示。
- 若用户 已有
.env,绝不覆盖,只追加缺失键(可选)或完全跳过。 - 生成后建议将
.env加入.gitignore(若尚未忽略),并在日志中提示「已创建 .env,请勿提交密钥」。
- 调用与现有一致的
MigrateYAMLToAppConfigDB(source记为auto_startup/auto_migrate)。 - 仅当第 1 步提交成功 后,将 源 YAML 重命名为例如:
config.yaml.migrated.<UTC时间戳>.bak(与RenameConfigYAMLToBackup一致)。 - 可选:对每个已导入的
bots/<id>/config.yaml同样重命名为.migrated.<ts>.bak,避免双源;若希望保留文件副本供人类编辑,可只改主配置、Bot 仅入库不删文件(产品二选一,须在发行说明写清)。
- 下一次启动:
app_config已有内容 →ApplyAppConfigFromDBIfPresent生效;磁盘上已无config.yaml时,进程应 只依赖 DB(或最小 bootstrap 读.env连库),不再要求config.yaml存在(与「去掉 YAML 依赖」终态一致)。
| 因素 | 处理建议 |
|---|---|
| 并发双实例 | 两个进程同时「见库空」同时迁移:依赖 DB 事务 + 唯一约束/SELECT FOR UPDATE 或迁移后二次校验;失败方重试读库。 |
| 只读文件系统 / 权限 | .env 或重命名 config.yaml 失败时:库已写入则不回滚库(或文档约定先 rename 再 migrate);日志告警并提示手动处理。 |
| Docker / 挂载 | config.yaml 在只读 volume 上:无法 rename → 仅入库 + 日志说明。 |
| MySQL | 自动生成 .env 时应写入 DATABASE_DSN(或项目约定变量名),与 NewStorage 一致;勿在 .env 写明文密码若用户不希望(可只写占位 + 注释)。 |
| Git 跟踪的 config.yaml | rename 后工作区出现「删除+未跟踪备份」;文档说明:备份文件应 gitignore,或用户改用 config.local.yaml。 |
| 迁移失败 | 事务回滚;不 rename YAML;下次启动可重试。 |
| 用户只想暂时用 YAML | QUANTMESH_SKIP_AUTO_MIGRATE=1 或 QUANTMESH_USE_APP_CONFIG=0 保留现有行为。 |
CLI / 环境变量(已实现 Phase A)
| 变量 / 参数 | 作用 |
|---|---|
--migrate-app-config |
将当前 config.yaml 与 bots/*/config.yaml 写入主库;已存在时需 QUANTMESH_MIGRATE_APP_CONFIG_FORCE=1 |
QUANTMESH_BOTS_DIR |
迁移时 Bot 目录,默认 ./bots |
QUANTMESH_USE_APP_CONFIG=0 |
禁用启动时从 app_config 覆盖内存配置 |
QUANTMESH_MIGRATE_APP_CONFIG_FORCE=1 |
允许迁移覆盖已有 app_config |
- 主配置 Web/API:保存路径以 主库
app_config/app_config_history为准(见SaveAppConfigSnapshot);不匹配revision时返回409 Conflict(按实现为准)。 - Bot 配置 API:按
bot_id更新bot_configs,同一事务内INSERT bot_config_history,携带客户端revision乐观锁。 - 导出/下载:可将主配置与各 Bot 序列化为 YAML 仅供人类阅读/灾备,不作为 SSOT。
- 热更新:保存成功后广播内部事件或依赖现有配置重载钩子(实现时对照
main与 web 层已有 reload 点)。
- 本地无 DB:可选
CONFIG_JSON_FILE指向单文件 JSON,仅开发用;生产禁用或只读。 - 备份:定期导出
app_configJSON;Bot 侧导出bot_configs全表或按bot_id;历史表可按保留策略归档(例如仅保留最近 N 条或按时间分区)。与运维侧数据库/文件备份策略对齐(不再依赖已移除的独立配置备份模块)。
| 风险 | 缓解 |
|---|---|
| 大 JSON 单次写入失败 | 事务 + 先写 history 再更新当前行 |
| 双源(YAML+DB) | 迁移后明确禁用从 YAML 加载(除 migrate 命令) |
与现有 system_settings 重复 |
过渡期合并规则文档化;终态收敛 key |
主配置与 bot_configs 参数重复 |
编排 vs 参数 SSOT 边界写清;代码层单一写入 API |
| 历史表膨胀 | 保留策略(条数/天数)、可选 VACUUM/离线归档 |
分支:全程在 功能分支(见第 1.3 节)开发,合并前完成迁移与回滚演练。
- Phase A(核心):
app_config+app_config_history;bot_configs+bot_config_history;storage迁移;启动从 DB 组装配置;主配置 + Bot YAML 迁移命令(幂等);历史写入与主表同一事务。 - Phase B:收敛仍写本地文件的遗留路径(若有);前端主配置与 Bot 编辑对接
revision;必要时提供「按 Bot 历史回滚」API。 - Phase C:收敛
system_settings中与主配置重复的 JSON key;清理文档与示例;历史表保留策略与运维说明。
本文档路径:docs/config-database-design.md(与 docs/examples/ 下的示例配置并列)。