# webman-seeds **Repository Path**: doc5/webman-seeds ## Basic Information - **Project Name**: webman-seeds - **Description**: No description available - **Primary Language**: PHP - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-02 - **Last Updated**: 2026-07-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # webman-seeder webman 框架种子数据与结构迁移插件。解决网站初始化、跨库同步时的数据迁移问题。 **核心能力:** - **种子数据增量导出** — 按 `max_id` 追踪,只导出新增行,生成 Phinx Migration 类 - **种子数据导入去重** — 基于 phinxlog 表,`--fields` 字段覆盖(`site_id` 重映射) - **表结构迁移** — 数据库 schema 快照对比,自动生成增量 DDL 迁移文件 - **非表对象同步** — 自动识别存储过程、函数、触发器、事件的 DDL 变更 ## 安装 该包托管在 Gitee,未提交到 Packagist,需在项目 `composer.json` 中配置仓库源: ```json { "repositories": [ { "type": "git", "url": "https://gitee.com/doc5/webman-seeds.git" } ] } ``` 然后在 `composer.json` 所在目录执行: ```bash composer require leicong/webman-seeder ``` 安装后插件配置自动复制到 `config/plugin/leicong/webman-seeder/`。 ## 配置 编辑 `config/plugin/leicong/webman-seeder/seed.php`,定义需要迁移的表及依赖关系: ```php [ // 独立表(无依赖) 'users', 'sites', // 有依赖关系的表 [ 'table' => 'posts', 'relative' => [ ['user_id', ['users.id']], ['site_id', ['sites.id']], ], // 可选:导出时附加过滤条件(字段 => 值) 'condition' => ['status' => 'published'], // 可选:嵌套子表 'children' => [ [ 'table' => 'comments', 'relative' => [ ['post_id', ['posts.id']], ], ], ], ], ], ]; ``` ### 配置节点说明 | 节点 | 格式 | 必填 | 说明 | |------|------|------|------| | 独立表 | `'table_name'` | 可选 | 无依赖的表,按数组顺序导出 | | 依赖表 | `['table' => ..., 'relative' => [...]]` | 可选 | 通过 `relative` 定义外键 | | `table` | `string` | 是 | 表名 | | `relative` | `[[子字段, [祖先表.字段, ...]], ...]` | 否 | 子表字段 → 祖先表字段映射 | | `children` | `[子表配置, ...]` | 否 | 嵌套子表定义,可递归,结构同依赖表 | | `condition` | `['字段' => '值', ...]` | 否 | 导出时附加 AND 过滤条件。与 `--fields` 行为一致,字段不存在则自动跳过 | ### condition 说明 `condition` 定义在 seed.php 中,导出时自动附加到该表的 WHERE 子句: ```php [ 'table' => 'posts', 'condition' => ['status' => 'published', 'deleted_at' => null], ] ``` 等价于 `WHERE status = 'published' AND deleted_at IS NULL`。 - 与 `--fields` CLI 参数组合使用(AND 关系) - 表中不存在该字段时自动跳过 - 子表(`children`)同样支持 ## 命令一览 | 命令 | 功能 | |------|------| | `seeds:export` | 增量导出种子数据为 Phinx Migration 文件 | | `seeds:import` | 导入种子数据(phinxlog 去重) | | `seeds:migration` | 对比数据库结构快照,生成结构迁移文件 | | `seeds:migrate` | 执行待处理的结构迁移 | --- ## seeds:export — 增量导出种子数据 读取 seed.php 配置,从数据库导出数据为 Phinx Migration 类文件。基于 `max_id` 追踪,每次只导出新增数据: ```bash # 增量导出(默认,只导 ID 大于上次 max_id 的新增行) php webman seeds:export # 全量重新导出(忽略进度) php webman seeds:export --full # 指定源库连接 php webman seeds:export --connection=source # 导出过滤:只导出 site_id=10 的数据(对应字段在表中存在时生效) php webman seeds:export --fields site_id=10 # 多字段过滤 php webman seeds:export --fields site_id=10 --fields lang=zh ``` ### 选项 | 参数 | 说明 | 默认 | |------|------|------| | `--config` | 种子配置文件路径(相对于 base_path) | `config/plugin/leicong/webman-seeder/seed.php` | | `--connection` | 源数据库连接 | `default` | | `--seed-path` | 迁移文件输出目录 | `database/seeds_migrations` | | `--progress-file` | 进度文件路径 | `database/seeds/.seed_progress.json` | | `--full` | 全量重新导出(清空进度,忽略 max_id) | 否 | | `--fields` | 导出过滤条件(可多次指定,格式 `field=value`) | 无 | ### 增量追踪 - 每张表单独记录 `max_id`(表中最大 `id`) - 每次导出取 `id > max_id` 的新增行,按 `id` 升序 - 导出后更新 `max_id` 为当前批次最大 ID - `--full` 清空所有进度记录,下次导出全量数据 ### 导出过滤(`--fields`) `--fields` 作为导出时的过滤条件,仅导出匹配的数据行: ```bash php webman seeds:export --fields site_id=10 --fields lang=zh ``` | 场景 | 行为 | |------|------| | 表中存在该字段 | 添加 `WHERE field = value` | | 表中不存在该字段 | 跳过该条件 | | 组合多个 `--fields` | 所有存在的字段条件以 AND 组合 | ### FK 包含过滤 `--fields` 生效时,系统自动追踪已导出的父表 ID。即使子表有 `--fields` 列,它的 FK 值可能指向未被导出的父表行。此时自动追加 FK 包含过滤,确保导出数据自洽: ``` 主表 wa_speakers (site_id=10): 只导出 site_id=10 的行 子表 wa_speakers_body_movements: 自动追加 WHERE speakers_id IN (导出的speaker IDs) ``` 当 FK 字段值为 NULL 或 0(可选外键)时,也会包含在导出结果中,不会强制关联。 ### 外键智能映射 在 `seed.php` 的 `relative` 中定义外键关系,导入时自动解析: ```php 'relative' => [ ['user_id', ['users.id']], // FK → users.id ['site_id', ['sites.domain']], // FK → sites.domain(业务字段匹配) ] ``` 映射策略: | 场景 | 策略 | 生成的代码 | |------|------|-----------| | 父表数据也在本次增量中 | **直接映射** (direct) | 通过 `$idMap[parentTable][oldId]` 用 `lastInsertId()` 取新 ID | | 父表数据不在本次增量中 | **查询映射** (lookup) | 在目标库通过业务字段 `SELECT id FROM parentTable WHERE field = ?` 匹配 | | FK 字段为 NULL/0 | **跳过** | 不执行映射,保持原值 | 多字段联合查询: ```php 'relative' => [ ['user_id', ['users.email', 'users.name']], ] ``` ### 拓扑排序 多表间存在外键引用时,PhinxSeedWriter 使用 **Kahn 算法** 进行拓扑排序,确保父表在子表之前插入。循环依赖的表追加到末尾,不丢失数据。 ### 生成的文件格式 输出到 `database/seeds_migrations/YYYYMMDDHHMMSS_seed_data_XXXXXX.php`: ```php $value) { if (array_key_exists($field, $row)) { $row[$field] = $value; } } }; // ===== users (new rows) ===== $usersRows = [ ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'], ['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com'], ]; foreach ($usersRows as $r) { $applyGlobals($r); $rowId = $r['id']; unset($r['id']); // FK: site_id → sites (direct) $sourceId = $r['site_id']; if (!empty($sourceId)) { if (!isset($idMap['sites'][$sourceId])) { throw new \RuntimeException('...'); } $r['site_id'] = $idMap['sites'][$sourceId]; } $this->getAdapter()->getConnection() ->prepare( 'INSERT INTO `users` (`name`, `email`) VALUES (?, ?)' ) ->execute(array_values($r)); } } } ``` --- ## seeds:import — 导入种子数据 遍历 `database/seeds_migrations/` 下的迁移文件,按时间顺序执行,通过 phinxlog 去重: ```bash # 直接导入 php webman seeds:import # 字段覆盖:将所有 site_id 替换为 5,lang 替换为 zh php webman seeds:import --fields site_id=5 --fields lang=zh # 指定目标库连接 php webman seeds:import --connection=target ``` ### 选项 | 参数 | 说明 | 默认 | |------|------|------| | `--seed-path` | 迁移文件目录 | `database/seeds_migrations` | | `--fields` | 字段覆盖值(可多次指定,格式 `field=value`) | 无 | | `--connection` | 目标数据库连接 | `default` | ### 字段覆盖(`--fields`) 导入时替换数据中指定字段的值。典型场景:源库 `site_id=10`,目标库需要 `site_id=5`: ```bash # 导出时过滤 site_id=10 的数据 php webman seeds:export --fields site_id=10 # 导入时将所有 site_id=10 替换为 5 php webman seeds:import --fields site_id=5 ``` `$applyGlobals` 闭包在生成的迁移代码中运行,通过 `$row[field] = value` 逐一替换。 ### 去重机制 通过 `phinxlog` 表实现去重: | 条件 | version 格式 | 去重依据 | |------|-------------|---------| | 无 `--fields` | `YYYYMMDDHHMMSS` | 仅按 version | | 有 `--fields` | `YYYYMMDDHHMMSS-{fields_hash}` | version(含 hash) | - `fields_hash` = `md5(字段=值&...)` 截取前 10 位,key 按字典序排序 - 同一文件 + 同一 `--fields` → 不重复执行 - 同一文件 + 不同 `--fields` → 各自执行(不同 hash 产生不同 version) `phinxlog` 表结构: | 字段 | 类型 | 说明 | |------|------|------| | `version` | VARCHAR(50) PK | 时间戳或 `{timestamp}-{hash}` | | `migration_name` | VARCHAR(100) | `SeedData` | | `git_version` | VARCHAR(40) | 导入时的 git commit hash | | `start_time` | DATETIME | 开始时间 | | `end_time` | DATETIME | 结束时间 | | `breakpoint` | TINYINT(1) | 断点标记(默认 0) | ### 执行流程 1. 扫描 `database/seeds_migrations/` 下所有 `*.php` 文件,按文件名排序 2. 从文件名提取 14 位时间戳 3. 使用 Phinx `Util::mapFileNameToClassName()` 推导类名 4. 计算 `fields_hash`(如有 `--fields`),拼接去重 version 5. 查询 phinxlog 表,已存在的跳过 6. `require_once` 文件,实例化类,执行 `change()` 7. 写入 phinxlog 记录 --- ## seeds:migration — 生成结构迁移 对比当前数据库结构与保存的快照文件,自动生成 Phinx DDL 迁移: ```bash # 首次:生成全量 CREATE TABLE + 保存快照 php webman seeds:migration # 后续:生成增量 ALTER TABLE + 更新快照 php webman seeds:migration # 覆盖快照(从当前结构重新生成快照) php webman seeds:migration --overwrite # 指定数据库连接 php webman seeds:migration --connection=target ``` ### 行为说明 | 场景 | 行为 | |------|------| | 无快照文件 | 生成全量 CREATE TABLE 迁移,保存当前结构为快照 | | 有快照 | 对比当前结构与快照差异,生成增量 ALTER TABLE 迁移,更新快照 | | `--overwrite` | 忽略已有快照,重新生成全量迁移并覆盖快照 | ### 快照格式 快照文件 `database/schema.php` 为 PHP 数组格式,记录每张表的完整结构信息(字段、类型、索引、外键等)。 ### 选项 | 参数 | 说明 | 默认 | |------|------|------| | `--connection` | 数据库连接 | `default` | | `--name` | 类名 | `Schema`(自动追加时间戳后缀) | | `--overwrite` | 覆盖已存在的 schema 快照文件 | 否 | | `--schema-file` | 快照文件路径(相对于 base_path) | `database/schema.php` | | `--phinx-config` | Phinx 配置文件路径 | 无 | ### 类名与冲突处理 迁移文件生成到 `database/migrations/`,类名为 `Schema` + 时间戳后缀(如 `Schema2607071922`): - 自动检测类名冲突,追加 `ymdHi` 后缀 - 仍然冲突再加 5 位 `uniqid`(最大重试 10 次) - 类名符合 Phinx CamelCase 规范 ### 非表对象支持 自动检测并导出以下数据库对象的 DDL 变更: | 对象类型 | 源 | 操作 | |---------|-----|------| | 存储过程 (PROCEDURE) | `SHOW CREATE PROCEDURE` | CREATE / DROP | | 函数 (FUNCTION) | `SHOW CREATE FUNCTION` | CREATE / DROP | | 触发器 (TRIGGER) | `SHOW CREATE TRIGGER` | CREATE / DROP | | 事件 (EVENT) | `SHOW CREATE EVENT` | CREATE / DROP | 对比策略: | 场景 | 生成的代码 | |------|-----------| | 对象只存在于当前库 | `DROP IF EXISTS` + `CREATE`(保证幂等) | | 对象只存在于快照 | `DROP IF EXISTS` | | 对象 DDL 发生变更 | `DROP IF EXISTS` + `CREATE` | ### 幂等性 所有非表对象的 `CREATE` 操作都前置 `DROP IF EXISTS`,确保迁移在目标库可重复执行。 --- ## seeds:migrate — 执行结构迁移 按时间顺序遍历 `database/migrations/` 下的迁移文件,通过 phinxlog 去重执行: ```bash php webman seeds:migrate php webman seeds:migrate --connection=target ``` ### 选项 | 参数 | 说明 | 默认 | |------|------|------| | `--connection` | 目标数据库连接 | `default` | | `--path` | 迁移文件目录 | `database/migrations` | ### 去重机制 | 去重依据 | 说明 | |---------|------| | `version` | 文件名中的 14 位时间戳(phinxlog PK) | | `migration_name` | 执行后覆盖为 `Schema`(标记用,不参与去重) | ### 执行流程 1. 扫描 `database/migrations/` 下所有 `*.php` 文件,按文件名排序 2. 从文件名提取 14 位时间戳作为 version 3. 查询 phinxlog 表,已存在的 version 跳过 4. `require` 迁移文件,实例化 Migration 类 5. 调用 `change()` 执行迁移 6. 写入 phinxlog,`migration_name` 设为 `Schema` --- ## 数据文件位置 | 路径 | 说明 | |------|------| | `database/seeds_migrations/` | 种子数据迁移文件(`seeds:export` 生成) | | `database/seeds/.seed_progress.json` | 增量导出进度(每表的 max_id) | | `database/migrations/` | 表结构迁移文件(`seeds:migration` 生成) | | `database/schema.php` | 数据库结构快照(PHP 数组格式) | | `phinxlog` 表 | 迁移日志表(自动创建) | | `config/plugin/leicong/webman-seeder/seed.php` | 种子数据迁移配置 | ## 工作流程 ### 完整初始化 ```bash # 1. 从源库生成结构迁移(首次全量) php webman seeds:migration --connection=source # 2. 在目标库执行结构迁移 php webman seeds:migrate --connection=target # 3. 从源库导出种子数据(首次全量,按条件过滤) php webman seeds:export --connection=source --fields site_id=10 --full # 4. 在目标库导入种子数据(映射 site_id) php webman seeds:import --connection=target --fields site_id=5 ``` ### 增量同步 ```bash # 源库导出增量 php webman seeds:export --connection=source --fields site_id=10 # 目标库导入 php webman seeds:import --connection=target --fields site_id=5 ``` ### 数据流示意 ``` 种子数据同步: 源站 DB ──seeds:export──→ seeds_migrations/{ts}_data.php │ seeds:import --fields site_id=5 │ ▼ 目标站 DB 结构同步: 源站 DB ──seeds:migration──→ migrations/{ts}_schema_*.php │ seeds:migrate │ ▼ 目标站 DB ``` ## 依赖 - PHP >= 8.0 - ext-pdo - robmorgan/phinx ^0.15 \|\| ^0.16 - webman/console ^2.0 - workerman/webman-framework ^1.5 - illuminate/database ^13.18