Files
2026-08-31 11:33:59 +08:00

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 bigInteger instead of id() for snowflake ID support
  • Soft Deletes: Always include deleted_at field
  • Timestamps: Use unsignedInteger for 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 of id()
  • 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');