13 KiB
13 KiB
name, description
| name | description |
|---|---|
| backend-gen-migrate | Generate database migration file for madong module. Supports both app (main project) and plugin targets. Creates migration with proper field types, soft deletes, and indexes following madong conventions. Primary key uses bigInteger for snowflake ID support. |
Step 2: Generate Migration
Create database migration file for module.
File Location
App (主项目):
resource/database/migrations/{timestamp}_create_{table_name}_table.php
Plugin (插件):
plugin/{plugin}/resource/database/migrations/{timestamp}_create_{table_name}_table.php
单独执行指定迁移
当前项目命令位于 backend/ 目录,支持通过第二个位置参数传入迁移文件名,只执行指定的待迁移文件:
# 先预览指定迁移,不实际执行
php webman madong-migrate up {migration_file_name} --dry-run
# 单独执行指定迁移(可带不带 .php 后缀)
php webman madong-migrate up {migration_file_name}
# 查看执行状态
php webman madong-migrate status
示例:
php webman madong-migrate up 2026_08_24_000001_create_system_feedback_table --dry-run
php webman madong-migrate up 2026_08_24_000001_create_system_feedback_table
说明:
{migration_file_name}是resource/database/migrations/下的迁移文件名,不需要填写完整路径。- 指定迁移已经执行时,命令会提示
Migration already executed,不会重复执行。 - 指定文件不存在时,命令会提示
Migration file not found。 - 不传迁移名时,
up仍会按文件名顺序执行全部待迁移文件。 - 当前迁移命令已避免对 MySQL DDL 包裹外层事务,防止建表隐式提交后出现
There is no active transaction。
插件迁移的单文件执行
madong-migrate 仅搜索主项目的 resource/database/migrations/,不能传入 plugin/{plugin}/resource/database/migrations/ 的路径或文件名。因此,插件新增迁移需要通过插件安装/升级流程执行;紧急单独执行某一个已确认安全的插件迁移时,可在 backend/ 目录使用以下命令:
php -r "require 'vendor/autoload.php'; require 'support/bootstrap.php'; `$schema = support\Db::connection()->getSchemaBuilder(); `$migration = require 'plugin/produce/resource/database/migrations/{migration_file_name}.php'; `$migration->up(`$schema);"
执行后应验证目标列或表,例如:
php -r "require 'vendor/autoload.php'; require 'support/bootstrap.php'; echo support\Db::connection()->getSchemaBuilder()->hasColumn('produce_repair_record', 'images') ? 'images 字段已存在' . PHP_EOL : 'images 字段不存在' . PHP_EOL;"
注意:此紧急方式不会写入插件迁移日志;常规发布仍应通过插件安装或升级流程,以维护完整迁移记录。
Table Naming Convention
| Target | Table Name | Example |
|---|---|---|
| App | {prefix}_{module}_{models} |
ask_questions |
| Plugin | {prefix}_{plugin}_{module}_{models} |
official_ask_questions |
Key Features
- Snowflake ID: Primary key uses
bigIntegerinstead ofid()for snowflake ID support - Soft Deletes: Always include
deleted_atfield - Timestamps: Use
unsignedIntegerfor created_at and updated_at - Table Prefix: Respect system table prefix configuration
Migration Template (Snowflake ID)
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use support\Db;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$tablePrefix = config('admin.database.table_prefix');
$table = $tablePrefix . '{table_name}';
// 创建表
if (!Db::schema()->hasTable($table)) {
Db::schema()->create($table, function (Blueprint $table) use ($table) {
$table->comment = '{table_comment}';
// 主键 - 使用 bigInteger 支持雪花ID
$table->bigInteger('id')->unsigned()->primary();
{fields}
// 软删除
$table->unsignedInteger('deleted_at')->nullable()->comment('删除时间');
// 时间戳
$table->unsignedInteger('created_at')->comment('创建时间');
$table->unsignedInteger('updated_at')->comment('更新时间');
// 索引
{indexes}
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tablePrefix = config('admin.database.table_prefix');
$table = $tablePrefix . '{table_name}';
if (Db::schema()->hasTable($table)) {
Db::schema()->dropIfExists($table);
}
}
};
Field Types
String Field
$table->string('name', 100)->comment('名称');
$table->string('email', 255)->unique()->comment('邮箱');
$table->string('cover', 255)->nullable()->comment('封面图');
Text Field
$table->text('description')->nullable()->comment('描述');
$table->text('content')->comment('内容');
Integer Fields
$table->unsignedInteger('sort')->default(0)->comment('排序');
$table->unsignedInteger('views')->default(0)->comment('浏览量');
$table->unsignedBigInteger('category_id')->comment('分类ID');
Decimal Field
$table->decimal('price', 10, 2)->default(0)->comment('价格');
$table->decimal('balance', 10, 2)->default(0)->comment('余额');
Boolean Field
$table->boolean('is_recommend')->default(false)->comment('是否推荐');
$table->boolean('is_hot')->default(false)->comment('是否热门');
JSON Field
$table->json('images')->nullable()->comment('图片列表');
$table->json('extra')->nullable()->comment('扩展数据');
Enum Field (using tinyInteger)
$table->tinyInteger('status')->default(1)->comment('状态: 1正常 2禁用');
$table->tinyInteger('type')->default(1)->comment('类型: 1文本 2图片 3视频');
Foreign Key
$table->unsignedBigInteger('category_id')->comment('分类ID');
$table->unsignedBigInteger('user_id')->comment('用户ID');
$table->unsignedBigInteger('parent_id')->nullable()->comment('父级ID');
// 外键约束(可选)
$table->foreign('category_id')->references('id')->on($tablePrefix . 'categories')->onDelete('cascade');
Member ID (for API tables)
$table->unsignedBigInteger('member_id')->comment('会员ID');
$table->index('member_id', '{table}_member_id_index');
Index Types
Single Index
$table->index('status', '{table}_status_index');
$table->index('category_id', '{table}_category_id_index');
$table->index(['status', 'created_at'], '{table}_status_created_at_index');
Unique Index
$table->unique('name', '{table}_name_unique');
$table->unique(['user_id', 'type'], '{table}_user_type_unique');
Complete Migration Example (App)
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use support\Db;
return new class extends Migration
{
public function up(): void
{
$tablePrefix = config('admin.database.table_prefix');
$table = $tablePrefix . 'ask_questions';
if (!Db::schema()->hasTable($table)) {
Db::schema()->create($table, function (Blueprint $table) use ($table) {
$table->comment = '问答-问题表';
// 主键 - 使用 bigInteger 支持雪花ID
$table->bigInteger('id')->unsigned()->primary();
// 分类ID
$table->unsignedBigInteger('category_id')->comment('分类ID');
$table->index('category_id', 'ask_questions_category_id_index');
// 标题
$table->string('title', 200)->comment('标题');
// 内容
$table->text('content')->comment('内容');
// 价格
$table->decimal('price', 10, 2)->default(0)->comment('价格');
// 浏览量
$table->unsignedInteger('view_count')->default(0)->comment('浏览量');
// 状态
$table->tinyInteger('status')->default(1)->comment('状态: 1正常 2禁用');
$table->index('status', 'ask_questions_status_index');
// 软删除
$table->unsignedInteger('deleted_at')->nullable()->comment('删除时间');
// 时间戳
$table->unsignedInteger('created_at')->comment('创建时间');
$table->unsignedInteger('updated_at')->comment('更新时间');
});
}
}
public function down(): void
{
$tablePrefix = config('admin.database.table_prefix');
$table = $tablePrefix . 'ask_questions';
if (Db::schema()->hasTable($table)) {
Db::schema()->dropIfExists($table);
}
}
};
Complete Migration Example (Plugin)
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use support\Db;
return new class extends Migration
{
public function up(): void
{
$tablePrefix = config('admin.database.table_prefix');
$table = $tablePrefix . 'official_ask_questions';
if (!Db::schema()->hasTable($table)) {
Db::schema()->create($table, function (Blueprint $table) use ($table) {
$table->comment = '问答-问题表';
// 主键 - 使用 bigInteger 支持雪花ID
$table->bigInteger('id')->unsigned()->primary();
// 分类ID
$table->unsignedBigInteger('category_id')->comment('分类ID');
$table->index('category_id', 'official_ask_questions_category_id_index');
// 会员ID (可选,用于API数据隔离)
$table->unsignedBigInteger('member_id')->nullable()->comment('会员ID');
$table->index('member_id', 'official_ask_questions_member_id_index');
// 标题
$table->string('title', 200)->comment('标题');
// 内容
$table->text('content')->comment('内容');
// 价格
$table->decimal('price', 10, 2)->default(0)->comment('价格');
// 浏览量
$table->unsignedInteger('view_count')->default(0)->comment('浏览量');
// 状态
$table->tinyInteger('status')->default(1)->comment('状态: 1正常 2禁用');
$table->index('status', 'official_ask_questions_status_index');
// 软删除
$table->unsignedInteger('deleted_at')->nullable()->comment('删除时间');
// 时间戳
$table->unsignedInteger('created_at')->comment('创建时间');
$table->unsignedInteger('updated_at')->comment('更新时间');
});
}
}
public function down(): void
{
$tablePrefix = config('admin.database.table_prefix');
$table = $tablePrefix . 'official_ask_questions';
if (Db::schema()->hasTable($table)) {
Db::schema()->dropIfExists($table);
}
}
};
Field Type Mapping
| User Input | Migration Method | Notes |
|---|---|---|
| string(n) | string('name', n) |
VARCHAR |
| text | text('name') |
TEXT |
| integer | unsignedInteger('name') |
INT UNSIGNED |
| bigint | unsignedBigInteger('name') |
BIGINT UNSIGNED |
| decimal(m,n) | decimal('name', m, n) |
DECIMAL |
| tinyint | tinyInteger('name') |
TINYINT |
| boolean | boolean('name') |
TINYINT(1) |
| json | json('name') |
JSON |
| date | date('name') |
DATE |
| datetime | dateTime('name') |
DATETIME |
| foreign_key | unsignedBigInteger('name') |
BIGINT UNSIGNED |
Naming Convention
| Item | App Convention | Plugin Convention |
|---|---|---|
| Table | {prefix}_{module}_{models} |
{prefix}_{plugin}_{module}_{models} |
| Index | {table}_{column}_index |
{table}_{column}_index |
| Foreign Key | (auto) | (auto) |
Auto-generation Checklist
When generating migration:
- Use
bigInteger('id')->unsigned()->primary()instead ofid() - Check table prefix from config
- Generate timestamp prefix
- Use correct table name based on target (app/plugin)
- Add all fields with proper types
- Add index for foreign keys
- Add index for status field
- Add member_id field for API tables
- Add softDeletes field (deleted_at)
- Add created_at and updated_at fields
- Add table comment
Common Issues
1. Duplicate Table
Always check with hasTable() before creating:
if (!Db::schema()->hasTable($table)) {
// create table
}
2. Index Naming
Keep index names short but descriptive:
// Good
$table->index('category_id', 'questions_category_id_index');
// Bad
$table->index('category_id', 'idx_questions_category_id');
3. Foreign Key Constraint
Only add foreign key constraints if needed:
// Optional - adds CASCADE delete
$table->foreign('category_id')
->references('id')
->on($tablePrefix . 'categories')
->onDelete('cascade');