1
0
Files
nex/openspec/specs/mysql-driver/spec.md

108 lines
4.5 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.
# MySQL Driver
## Purpose
支持 MySQL 作为可选数据库后端,通过配置选择 sqlite 或 mysql 驱动,提供 MySQL 连接管理、初始化和方言迁移文件。
## Requirements
### Requirement: MySQL 数据库驱动支持
系统 SHALL 支持通过配置项 `database.driver` 选择 `sqlite``mysql` 数据库驱动,默认值为 `sqlite`
#### Scenario: 默认使用 SQLite 驱动
- **WHEN** 配置中未指定 `database.driver`
- **THEN** SHALL 使用 `sqlite` 作为数据库驱动
- **THEN** SHALL 行为与现有逻辑完全一致
#### Scenario: 配置 MySQL 驱动
- **WHEN** 配置 `database.driver` 设置为 `mysql`
- **THEN** SHALL 使用 MySQL 驱动连接远程数据库
- **THEN** SHALL 使用 `gorm.io/driver/mysql` 打开连接
- **THEN** SHALL 构建 DSN 格式为 `{user}:{password}@tcp({host}:{port})/{dbname}?charset=utf8mb4&parseTime=true&loc=Local`
#### Scenario: driver 值不合法
- **WHEN** 配置 `database.driver` 不是 `sqlite``mysql`
- **THEN** SHALL 配置验证失败,拒绝启动
### Requirement: MySQL 连接配置
系统 SHALL 在 `DatabaseConfig` 中支持 MySQL 连接参数。
#### Scenario: MySQL 连接参数字段
- **WHEN** `database.driver``mysql`
- **THEN** SHALL 读取 `host`MySQL 主机地址,必填)
- **THEN** SHALL 读取 `port`MySQL 端口,默认 3306
- **THEN** SHALL 读取 `user`MySQL 用户名,必填)
- **THEN** SHALL 读取 `password`MySQL 密码,选填)
- **THEN** SHALL 读取 `dbname`(数据库名,必填)
#### Scenario: SQLite 模式忽略 MySQL 参数
- **WHEN** `database.driver``sqlite`
- **THEN** SHALL 忽略 MySQL 相关配置字段host/port/user/password/dbname
- **THEN** SHALL 仅使用 `path` 字段作为数据库文件路径
#### Scenario: MySQL 模式忽略 SQLite 参数
- **WHEN** `database.driver``mysql`
- **THEN** SHALL 忽略 `path` 字段
### Requirement: 数据库初始化公共包
系统 SHALL 提供 `internal/database` 公共包,封装数据库初始化、迁移执行和连接关闭逻辑,供 `cmd/server``cmd/desktop` 共同调用。
#### Scenario: 公共包 Init 函数
- **WHEN** 调用 `database.Init(cfg, logger)`
- **THEN** SHALL 根据 `cfg.Driver` 选择对应的 GORM 驱动打开连接
- **THEN** SHALL 执行对应方言的 goose 迁移
- **THEN** SHALL 配置连接池参数
- **THEN** SHALL 在 `driver=sqlite` 时执行 `PRAGMA journal_mode=WAL`
- **THEN** SHALL 在 `driver=mysql` 时跳过 SQLite 专有 PRAGMA
- **THEN** SHALL 返回 `*gorm.DB` 实例
#### Scenario: 公共包 Close 函数
- **WHEN** 调用 `database.Close(db)`
- **THEN** SHALL 获取底层 `sql.DB` 并关闭连接
#### Scenario: 迁移目录选择
- **WHEN** 执行迁移
- **THEN** SHALL 在 `driver=sqlite` 时使用 `migrations/sqlite/` 目录goose dialect 为 `sqlite3`
- **THEN** SHALL 在 `driver=mysql` 时使用 `migrations/mysql/` 目录goose dialect 为 `mysql`
### Requirement: MySQL 方言迁移文件
系统 SHALL 提供 MySQL 方言的初始迁移文件 `migrations/mysql/20260421000001_initial_schema.sql`
#### Scenario: providers 表
- **WHEN** 执行 MySQL 初始迁移
- **THEN** SHALL 创建 `providers` 表,字段:`id VARCHAR(36) PRIMARY KEY``name VARCHAR(255) NOT NULL``api_key VARCHAR(255) NOT NULL``base_url VARCHAR(255) NOT NULL``protocol VARCHAR(50) DEFAULT 'openai'``enabled BOOLEAN DEFAULT TRUE``created_at DATETIME(3)``updated_at DATETIME(3)`
#### Scenario: models 表
- **WHEN** 执行 MySQL 初始迁移
- **THEN** SHALL 创建 `models` 表,字段:`id VARCHAR(36) PRIMARY KEY``provider_id VARCHAR(36) NOT NULL``model_name VARCHAR(255) NOT NULL``enabled BOOLEAN DEFAULT TRUE``created_at DATETIME(3)`
- **THEN** SHALL 创建 `UNIQUE(provider_id, model_name)` 约束
- **THEN** SHALL 创建 `FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE` 约束
- **THEN** SHALL 创建 `idx_models_provider_id``idx_models_model_name` 索引
#### Scenario: usage_stats 表
- **WHEN** 执行 MySQL 初始迁移
- **THEN** SHALL 创建 `usage_stats` 表,字段:`id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY``provider_id VARCHAR(36) NOT NULL``model_name VARCHAR(255) NOT NULL``request_count INT DEFAULT 0``date DATE NOT NULL`
- **THEN** SHALL 创建 `UNIQUE(provider_id, model_name, date)` 约束
- **THEN** SHALL 创建 `idx_usage_stats_provider_model_date` 复合索引
#### Scenario: Down 迁移
- **WHEN** 执行 MySQL down 迁移
- **THEN** SHALL 按正确顺序删除索引和表usage_stats → models → providers