1
0
Files
nex/openspec/specs/config-management/spec.md
lanyuanxiaoyao 5513f0c13d feat: 区分 server 与 desktop 配置加载入口,取消自动创建配置文件
- config.go 重构:抽取 loadConfig 共享逻辑,新增 LoadServerConfig/LoadDesktopConfig/LoadDesktopConfigAtPath,LoadConfig 保持向后兼容
- setupConfigFile 移除 SafeWriteConfigAs 自动创建逻辑,文件不存在时仅使用默认值
- cmd/desktop 切换为 LoadDesktopConfig,端口/HTTP/浏览器/托盘统一使用 cfg.Server.Port
- cmd/server 显式使用 LoadServerConfig 明确入口语义
- 提取 desktop 可测 helper:desktopListenAddr/desktopURL/desktopPortMenuTitle/desktopConfigErrorMessage
- 新增测试:desktop 忽略 CLI/env/未知参数、配置快照不变、无效配置文件不静默回退、端口 helper 一致性
- README 区分 server/desktop 配置源,移除首次启动自动创建配置文件描述
- 同步 delta specs 到 openspec/specs/ 主规范
2026-05-06 11:59:19 +08:00

470 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Config Management
## Purpose
统一配置管理系统,支持 YAML 配置文件、CLI 参数、环境变量和默认值四种配置源,提供优先级管理、来源追踪和配置验证。
## Requirements
### Requirement: 使用 YAML 配置文件
系统 SHALL 使用 YAML 格式的配置文件,并按入口区分配置文件路径选择能力。
#### Scenario: Server 默认配置文件路径
- **WHEN** server 应用启动且未指定 `--config` 参数
- **THEN** SHALL 从 `~/.nex/config.yaml` 加载配置
- **THEN** SHALL 解析 YAML 格式
#### Scenario: Server 自定义配置文件路径
- **WHEN** server 应用启动且指定 `--config /path/to/custom.yaml`
- **THEN** SHALL 从指定路径加载配置文件
- **THEN** SHALL NOT 使用默认路径 `~/.nex/config.yaml`
#### Scenario: Desktop 固定配置文件路径
- **WHEN** desktop 应用启动
- **THEN** SHALL 从 `~/.nex/config.yaml` 加载配置
- **THEN** SHALL 解析 YAML 格式
- **THEN** SHALL NOT 支持通过 `--config` 指定其他配置文件路径
#### Scenario: 配置文件结构
- **WHEN** 加载配置文件
- **THEN** SHALL 包含 server、database、log 等配置节
- **THEN** SHALL 支持嵌套配置结构
### Requirement: 自动生成默认配置
系统 SHALL 在配置文件不存在时使用默认配置值,不自动创建配置文件。
#### Scenario: 配置文件不存在
- **WHEN** 应用启动且配置文件不存在
- **THEN** SHALL 使用默认配置值
- **THEN** SHALL NOT 自动创建配置文件
- **THEN** SHALL NOT 写入默认配置值到磁盘
#### Scenario: 配置文件已存在
- **WHEN** 应用启动且配置文件已存在
- **THEN** SHALL 直接加载配置文件
- **THEN** SHALL NOT 覆盖现有配置
### Requirement: 配置验证
系统 SHALL 验证配置的有效性。
#### Scenario: 必需字段验证
- **WHEN** 加载配置
- **THEN** SHALL 验证必需字段存在
- **THEN** SHALL 在字段缺失时返回错误
#### Scenario: 字段值验证
- **WHEN** 加载配置
- **THEN** SHALL 验证端口号范围1-65535
- **THEN** SHALL 验证日志级别有效性debug/info/warn/error
- **THEN** SHALL 验证路径有效性
- **THEN** SHALL 验证数值范围(如 max_idle_conns ≥ 1
#### Scenario: 配置错误处理
- **WHEN** 配置验证失败
- **THEN** SHALL 返回详细的错误信息
- **THEN** SHALL 指示哪些字段无效
- **THEN** SHALL 应用 SHALL NOT 启动
#### Scenario: 验证规则定义
- **WHEN** 定义配置结构体
- **THEN** SHALL 使用 `validate` tag 定义验证规则
- **THEN** SHALL 支持 `required` 规则
- **THEN** SHALL 支持 `min``max` 规则
- **THEN** SHALL 支持 `oneof` 规则
- **THEN** SHALL 支持 `required_if` 条件验证规则
#### Scenario: 验证执行
- **WHEN** 加载配置后
- **THEN** SHALL 自动执行结构体验证
- **THEN** SHALL 返回验证错误
- **THEN** SHALL NOT 启动应用(如果验证失败)
#### Scenario: 数据库驱动条件验证
- **WHEN** `database.driver``sqlite`
- **THEN** SHALL 验证 `database.path` 必填
- **THEN** SHALL NOT 要求 MySQL 字段host/port/user/password/dbname
- **WHEN** `database.driver``mysql`
- **THEN** SHALL 验证 `database.host` 必填
- **THEN** SHALL 验证 `database.user` 必填
- **THEN** SHALL 验证 `database.dbname` 必填
- **THEN** SHALL NOT 要求 `database.path`
### Requirement: 配置结构定义
系统 SHALL 定义清晰的配置结构。
#### Scenario: Server 配置
- **WHEN** 加载 server 配置
- **THEN** SHALL 包含 port、read_timeout、write_timeout 字段
- **THEN** SHALL 使用合理的默认值
#### Scenario: Database 配置
- **WHEN** 加载 database 配置
- **THEN** SHALL 包含 driver 字段(值为 `sqlite``mysql`,默认 `sqlite`
- **THEN** SHALL 包含 path 字段SQLite 模式下的数据库文件路径)
- **THEN** SHALL 包含 host 字段MySQL 主机地址)
- **THEN** SHALL 包含 port 字段MySQL 端口,默认 3306
- **THEN** SHALL 包含 user 字段MySQL 用户名)
- **THEN** SHALL 包含 password 字段MySQL 密码,选填)
- **THEN** SHALL 包含 dbname 字段MySQL 数据库名)
- **THEN** SHALL 包含 max_idle_conns、max_open_conns、conn_max_lifetime 字段
- **THEN** SHALL 使用合理的默认值
#### Scenario: Log 配置
- **WHEN** 加载 log 配置
- **THEN** SHALL 包含 level、path、max_size、max_backups、max_age、compress 字段
- **THEN** SHALL 使用合理的默认值
### Requirement: 默认配置值
系统 SHALL 提供合理的默认配置值。
#### Scenario: Server 默认值
- **WHEN** 使用默认配置
- **THEN** server.port SHALL 为 9826
- **THEN** server.read_timeout SHALL 为 30s
- **THEN** server.write_timeout SHALL 为 30s
#### Scenario: Database 默认值
- **WHEN** 使用默认配置
- **THEN** database.driver SHALL 为 `sqlite`
- **THEN** database.path SHALL 为 `~/.nex/config.db`
- **THEN** database.host SHALL 为空字符串
- **THEN** database.port SHALL 为 3306
- **THEN** database.user SHALL 为空字符串
- **THEN** database.password SHALL 为空字符串
- **THEN** database.dbname SHALL 为 `nex`
- **THEN** database.max_idle_conns SHALL 为 10
- **THEN** database.max_open_conns SHALL 为 100
- **THEN** database.conn_max_lifetime SHALL 为 1h
#### Scenario: Log 默认值
- **WHEN** 使用默认配置
- **THEN** log.level SHALL 为 info
- **THEN** log.path SHALL 为 `~/.nex/log`
- **THEN** log.max_size SHALL 为 100 (MB)
- **THEN** log.max_backups SHALL 为 10
- **THEN** log.max_age SHALL 为 30 (days)
- **THEN** log.compress SHALL 为 true
### Requirement: 配置加载流程
系统 SHALL 为 server 和 desktop 实现标准化且入口隔离的配置加载流程。
#### Scenario: Server 加载步骤
- **WHEN** server 应用启动
- **THEN** SHALL 按以下顺序加载配置:
1. 解析 CLI 参数(获取 --config 路径)
2. 初始化配置管理器
3. 设置默认值
4. 绑定 CLI 参数
5. 绑定环境变量
6. 读取配置文件(不存在时使用默认值)
7. 反序列化到结构体
8. 验证配置
9. 打印配置摘要
#### Scenario: Desktop 加载步骤
- **WHEN** desktop 应用启动
- **THEN** SHALL 按以下顺序加载配置:
1. 初始化配置管理器
2. 设置默认值
3. 读取默认配置文件 `~/.nex/config.yaml`(不存在时使用默认值)
4. 反序列化到结构体
5. 验证配置
6. 打印配置摘要
- **THEN** SHALL NOT 解析 CLI 参数
- **THEN** SHALL NOT 绑定环境变量
- **THEN** SHALL NOT 允许 CLI 参数覆盖配置文件路径
#### Scenario: 加载失败处理
- **WHEN** 配置加载过程中发生错误
- **THEN** SHALL 返回明确的错误信息
- **THEN** SHALL 指示失败步骤
- **THEN** SHALL NOT 启动应用
### Requirement: 配置优先级管理
系统 SHALL 为不同入口实现明确的配置优先级机制。
#### Scenario: Server 优先级顺序
- **WHEN** 同一配置项在多个 server 配置源中设置
- **THEN** SHALL 按以下优先级顺序(从高到低):
1. CLI 参数
2. 环境变量
3. 配置文件
4. 默认值
#### Scenario: Server CLI 参数最高优先级
- **WHEN** 配置文件设置 `server.port: 9826`
- **AND** 环境变量设置 `NEX_SERVER_PORT=9000`
- **AND** server CLI 参数设置 `--server-port 8080`
- **THEN** SHALL 使用 CLI 参数值 8080
#### Scenario: Server 环境变量次高优先级
- **WHEN** 配置文件设置 `server.port: 9826`
- **AND** 环境变量设置 `NEX_SERVER_PORT=9000`
- **AND** 未设置 CLI 参数
- **THEN** SHALL 使用环境变量值 9000
#### Scenario: 配置文件次低优先级
- **WHEN** 配置文件设置 `server.port: 9826`
- **AND** 未设置环境变量
- **AND** 未设置 CLI 参数
- **THEN** SHALL 使用配置文件值 9826
#### Scenario: 默认值最低优先级
- **WHEN** 配置文件中未设置某配置项
- **AND** 未设置环境变量
- **AND** 未设置 CLI 参数
- **THEN** SHALL 使用默认值
#### Scenario: Server 部分配置覆盖
- **WHEN** 配置文件设置完整配置
- **AND** server CLI 参数仅覆盖部分配置项
- **THEN** SHALL 合并所有配置源
- **THEN** SHALL 使用高优先级源覆盖指定项
- **THEN** SHALL 保留其他配置源中的未覆盖项
#### Scenario: Server 配置项独立覆盖
- **WHEN** 仅通过 server CLI 参数设置 `--server-port 9000`
- **THEN** SHALL 仅覆盖 server.port 配置项
- **THEN** SHALL NOT 影响其他配置项
- **THEN** SHALL 其他配置项使用配置文件或默认值
#### Scenario: Desktop 优先级顺序
- **WHEN** 同一配置项存在于 desktop 默认配置文件和默认值中
- **THEN** SHALL 使用 `~/.nex/config.yaml` 中的配置文件值
- **THEN** SHALL 仅在配置文件未设置该配置项时使用默认值
#### Scenario: Desktop 忽略外部覆盖源
- **WHEN** desktop 启动时存在 `--server-port 9000` 参数
- **AND** 存在 `NEX_SERVER_PORT=9001` 环境变量
- **AND** `~/.nex/config.yaml` 设置 `server.port: 9826`
- **THEN** SHALL 使用配置文件值 9826
- **THEN** SHALL NOT 使用 CLI 参数或环境变量覆盖配置
#### Scenario: 启动后配置锁定
- **WHEN** 应用启动完成
- **THEN** SHALL 锁定配置值
- **THEN** SHALL NOT 支持运行时修改配置优先级
- **THEN** SHALL NOT 支持运行时添加新配置源
### Requirement: 配置来源追踪
系统 SHALL 追踪每个配置值的来源,提供配置覆盖的透明信息。
#### Scenario: 来源记录
- **WHEN** 加载配置完成
- **THEN** SHALL 记录每个配置项的来源CLI/ENV/File/Default
- **THEN** SHALL 在配置摘要中显示来源信息
#### Scenario: 来源统计
- **WHEN** 打印配置摘要
- **THEN** SHALL 统计来自 CLI 参数的配置项数量
- **THEN** SHALL 统计来自环境变量的配置项数量
- **THEN** SHALL 统计来自配置文件的配置项数量
- **THEN** SHALL 统计使用默认值的配置项数量
#### Scenario: 覆盖提示
- **WHEN** CLI 参数覆盖配置文件值
- **THEN** SHALL 在日志中记录覆盖信息
- **THEN** SHALL 显示被覆盖的配置项名称
### Requirement: 配置文件安全
系统 SHALL 使用安全的文件权限保存配置文件。
#### Scenario: 配置文件权限
- **WHEN** 保存配置文件(`SaveConfig`
- **THEN** SHALL 使用 `0600` 权限写入文件(仅 owner 可读写)
- **THEN** SHALL 防止其他用户读取配置中的 MySQL 密码等敏感信息
### Requirement: 配置摘要输出
系统 SHALL 在启动时输出配置摘要。
#### Scenario: SQLite 模式摘要
- **WHEN** `database.driver``sqlite`
- **THEN** SHALL 打印关键配置项(端口、数据库路径、日志级别等)
- **THEN** SHALL 打印配置文件路径
- **THEN** SHALL 打印环境变量数量
- **THEN** SHALL 打印 CLI 参数数量
#### Scenario: MySQL 模式摘要
- **WHEN** `database.driver``mysql`
- **THEN** SHALL 打印关键配置项(端口、数据库类型、数据库地址、日志级别等)
- **THEN** SHALL 打印数据库地址格式为 `{host}:{port}/{dbname}`
- **THEN** SHALL 不打印密码
- **THEN** SHALL 打印配置文件路径
- **THEN** SHALL 打印环境变量数量
- **THEN** SHALL 打印 CLI 参数数量
#### Scenario: 摘要格式
- **WHEN** 打印配置摘要
- **THEN** SHALL 使用清晰的格式化输出
- **THEN** SHALL 使用分隔线和分组
- **THEN** SHALL 易于阅读和理解
### Requirement: CLI 参数配置支持
server 入口 SHALL 支持通过命令行参数设置所有配置项desktop 入口 SHALL NOT 将命令行参数作为配置源。
#### Scenario: Server 基本参数解析
- **WHEN** server 应用启动时传入命令行参数
- **THEN** SHALL 解析所有 CLI 参数
- **THEN** SHALL 将参数值应用到对应配置项
#### Scenario: 参数命名规范
- **WHEN** server 使用命令行参数
- **THEN** SHALL 使用 kebab-case 命名(如 `--server-port`
- **THEN** SHALL 保持完整的层次结构(如 `--database-max-idle-conns`
- **THEN** SHALL 与配置文件路径对应(`database.max_idle_conns``--database-max-idle-conns`
#### Scenario: 参数类型支持
- **WHEN** server 解析不同类型的参数
- **THEN** SHALL 支持 int 类型(如 `--server-port 9000`
- **THEN** SHALL 支持 string 类型(如 `--database-path /data/nex.db`
- **THEN** SHALL 支持 duration 类型(如 `--server-read-timeout 60s`
- **THEN** SHALL 支持 bool 类型(如 `--log-compress`
#### Scenario: Server 完整配置覆盖
- **WHEN** server 使用服务器相关参数
- **THEN** SHALL 支持 `--server-port``--server-read-timeout``--server-write-timeout`
- **WHEN** server 使用数据库相关参数
- **THEN** SHALL 支持 `--database-driver``--database-path``--database-host``--database-port``--database-user``--database-password``--database-dbname``--database-max-idle-conns``--database-max-open-conns``--database-conn-max-lifetime`
- **WHEN** server 使用日志相关参数
- **THEN** SHALL 支持 `--log-level``--log-path``--log-max-size``--log-max-backups``--log-max-age``--log-compress`
#### Scenario: Server 参数帮助信息
- **WHEN** server 使用 `--help` 参数
- **THEN** SHALL 显示所有支持的参数
- **THEN** SHALL 按功能分组展示参数(服务器、数据库、日志)
- **THEN** SHALL 显示每个参数的默认值和说明
#### Scenario: Server 参数错误处理
- **WHEN** server 传入无效的参数值(如 `--server-port abc`
- **THEN** SHALL 返回明确的错误信息,指示参数名称和期望类型
- **THEN** SHALL NOT 启动应用
- **WHEN** server 传入未定义的参数(如 `--unknown-param value`
- **THEN** SHALL 返回错误信息,指示未知参数名称
- **THEN** SHALL NOT 启动应用
#### Scenario: Desktop 忽略配置参数
- **WHEN** desktop 启动时传入 `--server-port 9000``--database-path /tmp/test.db``--config /tmp/custom.yaml`
- **THEN** SHALL 忽略这些参数
- **THEN** SHALL 从 `~/.nex/config.yaml` 和默认值加载配置
#### Scenario: Desktop 忽略未知参数
- **WHEN** desktop 启动时传入未知命令行参数
- **THEN** SHALL NOT 因未知参数导致配置加载失败
- **THEN** SHALL NOT 将未知参数应用为配置
### Requirement: 环境变量配置支持
server 入口 SHALL 支持通过环境变量设置所有配置项,符合 server 部署场景的 12-Factor App 原则desktop 入口 SHALL NOT 将 `NEX_*` 环境变量作为配置源。
#### Scenario: Server 环境变量读取
- **WHEN** server 应用启动时存在环境变量
- **THEN** SHALL 自动读取所有 `NEX_` 前缀的环境变量
- **THEN** SHALL 将环境变量值应用到对应配置项
#### Scenario: 环境变量命名规范
- **WHEN** server 使用环境变量配置
- **THEN** SHALL 使用 `NEX_` 前缀
- **THEN** SHALL 使用大写字母和下划线分隔(如 `NEX_SERVER_PORT`
- **THEN** SHALL 保持完整层次结构(如 `NEX_DATABASE_MAX_IDLE_CONNS`
- **THEN** SHALL 与配置文件路径对应(`database.max_idle_conns``NEX_DATABASE_MAX_IDLE_CONNS`
#### Scenario: 环境变量类型转换
- **WHEN** server 解析不同类型的环境变量
- **THEN** SHALL 支持 int 类型(如 `NEX_SERVER_PORT=9000`
- **THEN** SHALL 支持 string 类型(如 `NEX_DATABASE_PATH=/data/nex.db`
- **THEN** SHALL 支持 duration 类型(如 `NEX_SERVER_READ_TIMEOUT=60s`
- **THEN** SHALL 支持 bool 类型(如 `NEX_LOG_COMPRESS=true`
#### Scenario: Server 完整环境变量覆盖
- **WHEN** server 设置服务器相关环境变量
- **THEN** SHALL 支持 `NEX_SERVER_PORT``NEX_SERVER_READ_TIMEOUT``NEX_SERVER_WRITE_TIMEOUT`
- **WHEN** server 设置数据库相关环境变量
- **THEN** SHALL 支持 `NEX_DATABASE_DRIVER``NEX_DATABASE_PATH``NEX_DATABASE_HOST``NEX_DATABASE_PORT``NEX_DATABASE_USER``NEX_DATABASE_PASSWORD``NEX_DATABASE_DBNAME``NEX_DATABASE_MAX_IDLE_CONNS``NEX_DATABASE_MAX_OPEN_CONNS``NEX_DATABASE_CONN_MAX_LIFETIME`
- **WHEN** server 设置日志相关环境变量
- **THEN** SHALL 支持 `NEX_LOG_LEVEL``NEX_LOG_PATH``NEX_LOG_MAX_SIZE``NEX_LOG_MAX_BACKUPS``NEX_LOG_MAX_AGE``NEX_LOG_COMPRESS`
#### Scenario: Server 12-Factor App 合规
- **WHEN** server 部署到不同环境
- **THEN** SHALL 通过环境变量区分环境配置
- **THEN** SHALL NOT 修改代码或配置文件
- **WHEN** server 配置包含敏感信息(如密钥、密码)
- **THEN** SHALL 通过环境变量传递
- **THEN** SHALL NOT 存储在配置文件中
#### Scenario: Server 环境变量错误处理
- **WHEN** server 环境变量值格式无效(如 `NEX_SERVER_PORT=abc`
- **THEN** SHALL 返回明确的错误信息,指示环境变量名称和期望类型
- **THEN** SHALL NOT 启动应用
- **WHEN** server 必需配置项既无配置文件也无环境变量
- **THEN** SHALL 使用默认值
- **THEN** SHALL 正常启动应用
#### Scenario: Desktop 忽略环境变量
- **WHEN** desktop 启动时存在 `NEX_SERVER_PORT``NEX_DATABASE_PATH``NEX_LOG_LEVEL` 或其他 `NEX_*` 环境变量
- **THEN** SHALL NOT 读取这些环境变量作为配置源
- **THEN** SHALL 使用 `~/.nex/config.yaml` 和默认值加载配置