# Config to MySQL - 验证报告 - **Change**: config-to-mysql - **日期**: 2026-07-04 - **验证人**: AI Agent - **分支**: feature/20260704/config-to-mysql ## 验证项 | # | 检查项 | 结果 | 证据 | |---|--------|------|------| | 1 | tasks.md 全部完成 | ✅ | 所有 18 个子任务已勾选 | | 2 | 实现符合 Design Doc | ✅ | 单表 `app_config`、统一 `ConfigStore`、JSON fallback | | 3 | 实现符合 proposal | ✅ | symbols + ai 配置迁移到 MySQL,保留 JSON fallback | | 4 | 测试通过 | ✅ | `python -m pytest tests/` 110 passed, 2 xfailed | | 5 | 新增测试覆盖核心场景 | ✅ | `tests/test_config_store.py` 8 个用例覆盖命中/未命中/降级/迁移 | | 6 | 无硬编码密钥 | ✅ | 代码审查未发现 | | 7 | 无 SQL 注入风险 | ✅ | 使用 SQLAlchemy ORM 参数化查询 | ## 代码审查 **审查方式**: general_purpose_task 子代理审查 ### 已修复问题 | 级别 | 问题 | 修复方式 | |------|------|----------| | CRITICAL | MySQL 写入失败时 JSON 已更新但 MySQL 仍为旧值 | `set_config` 改为先写 MySQL,成功后再写 JSON;失败返回 False 并保留 JSON 旧值 | | IMPORTANT | JSON 文件直接覆盖写入 | 使用临时文件 + `replace` 原子写入 | | IMPORTANT | JSON 损坏时回填 MySQL 默认值 | 新增 `_load_json_from_file`,损坏时返回 None,get_config 使用 fallback 但不回填 | | IMPORTANT | `_save_to_mysql` 返回 False 时未处理 | `set_config` 检查 `_save_to_mysql` 返回值 | | MINOR | 多处未使用导入/变量 | 已清理 | ### 接受的风险 | 级别 | 问题 | 接受原因 | |------|------|----------| | IMPORTANT | AI 配置中的 `api_key` 以明文存入 MySQL JSON 字段 | 超出本次 change 范围;JSON 文件原本也是明文存储,风险未扩大;建议后续单独 change 增加加密或敏感字段隔离 | ## 分支处理 - [ ] 本地合并到主分支 - [ ] 推送并创建 PR - [ ] 保持分支 - [ ] 丢弃工作 ## 结论 ### 重新验证发现的问题 **问题**: 应用启动时配置迁移被跳过,MySQL app_config 表为空。 **根因**: `app.mysql_database.MySQLSessionLocal` 在 `init_mysql()` 执行前被 `app.config_store` 模块通过 `from app.mysql_database import MySQLSessionLocal` 导入,捕获了初始值 `None`。后续 `init_mysql()` 重新赋值模块变量后,`app.config_store` 中的名称仍指向 `None`,导致 `ConfigStore` 无法获取有效的 MySQL session,迁移被跳过。 **修复**: - `app/config_store.py`: 改为通过 `sys.modules` 动态读取 `app.mysql_database.MySQLSessionLocal`,始终获取最新值 - `app/config_migration.py`: 使用 `store.session_maker` 检查 MySQL session 是否初始化 **修复验证**: - 清理 app_config 表后启动应用,日志显示成功迁移 symbols 和 ai 配置 - 数据库查询确认 `symbols` 配置包含 38 个品种(与 JSON 文件一致) - 全部测试套件 110 passed, 2 xfailed ## 结论 验证通过。实现符合设计,测试覆盖核心场景,代码审查和手动验证发现的问题均已修复。