1
0
Files
nex/openspec/specs/mysql-driver/spec.md
lanyuanxiaoyao 8600a39b6c fix: 发布产物自包含数据库迁移资源,修复 macOS DMG 安装后无法启动
使用 go:embed 嵌入迁移 SQL 到二进制,移除 runtime.Caller 源码路径依赖,
server 和 desktop 发布产物均可在无源码目录环境下完成数据库初始化和迁移。
2026-05-05 23:47:58 +08:00

5.2 KiB
Raw Blame History

MySQL Driver

Purpose

支持 MySQL 作为可选数据库后端,通过配置选择 sqlite 或 mysql 驱动,提供 MySQL 连接管理、初始化和方言迁移文件。

Requirements

Requirement: MySQL 数据库驱动支持

系统 SHALL 支持通过配置项 database.driver 选择 sqlitemysql 数据库驱动,默认值为 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 不是 sqlitemysql
  • THEN SHALL 配置验证失败,拒绝启动

Requirement: MySQL 连接配置

系统 SHALL 在 DatabaseConfig 中支持 MySQL 连接参数。

Scenario: MySQL 连接参数字段

  • WHEN database.drivermysql
  • THEN SHALL 读取 hostMySQL 主机地址,必填)
  • THEN SHALL 读取 portMySQL 端口,默认 3306
  • THEN SHALL 读取 userMySQL 用户名,必填)
  • THEN SHALL 读取 passwordMySQL 密码,选填)
  • THEN SHALL 读取 dbname(数据库名,必填)

Scenario: SQLite 模式忽略 MySQL 参数

  • WHEN database.driversqlite
  • THEN SHALL 忽略 MySQL 相关配置字段host/port/user/password/dbname
  • THEN SHALL 仅使用 path 字段作为数据库文件路径

Scenario: MySQL 模式忽略 SQLite 参数

  • WHEN database.drivermysql
  • THEN SHALL 忽略 path 字段

Requirement: 数据库初始化公共包

系统 SHALL 提供 internal/database 公共包,封装数据库初始化、迁移执行和连接关闭逻辑,供 cmd/servercmd/desktop 共同调用,并 SHALL 使用随应用构建产物打包的迁移资源执行运行时迁移。

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 时选择 SQLite 方言迁移资源goose dialect 为 sqlite3
  • THEN SHALL 在 driver=mysql 时选择 MySQL 方言迁移资源goose dialect 为 mysql
  • THEN 运行时迁移资源 SHALL 来源于打包资源而非源码目录
  • THEN SHALL 在方言子资源解析失败时返回明确错误并拒绝启动

Scenario: 公共包迁移资源来源

  • WHEN 调用 database.Init(cfg, logger) 且当前工作目录不是仓库根目录或 backend/ 目录
  • THEN SHALL 仍能解析并执行对应方言的迁移资源
  • THEN SHALL NOT 要求当前进程工作目录位于仓库根目录或 backend/ 目录
  • THEN SHALL NOT 依赖 runtime.Caller 推导源码路径

Requirement: MySQL 方言迁移文件

系统 SHALL 提供 MySQL 方言的初始迁移文件 migrations/mysql/20260421000001_initial_schema.sql

Scenario: providers 表

  • WHEN 执行 MySQL 初始迁移
  • THEN SHALL 创建 providers 表,字段:id VARCHAR(36) PRIMARY KEYname VARCHAR(255) NOT NULLapi_key VARCHAR(255) NOT NULLbase_url VARCHAR(255) NOT NULLprotocol VARCHAR(50) DEFAULT 'openai'enabled BOOLEAN DEFAULT TRUEcreated_at DATETIME(3)updated_at DATETIME(3)

Scenario: models 表

  • WHEN 执行 MySQL 初始迁移
  • THEN SHALL 创建 models 表,字段:id VARCHAR(36) PRIMARY KEYprovider_id VARCHAR(36) NOT NULLmodel_name VARCHAR(255) NOT NULLenabled BOOLEAN DEFAULT TRUEcreated_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_ididx_models_model_name 索引

Scenario: usage_stats 表

  • WHEN 执行 MySQL 初始迁移
  • THEN SHALL 创建 usage_stats 表,字段:id INT UNSIGNED AUTO_INCREMENT PRIMARY KEYprovider_id VARCHAR(36) NOT NULLmodel_name VARCHAR(255) NOT NULLrequest_count INT DEFAULT 0date 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