🗄️ 插件数据库

ThinkAdmin 提供完善的插件数据库管理机制,基于 Phinx 脚本实现数据库的版本控制和自动化管理。

📚 基础概念

🤔 基本介绍

插件数据库管理是 ThinkAdmin 框架提供的数据库迁移机制,用于管理插件的数据库结构。

简单理解:就像数据库的"版本控制系统",帮你管理数据库结构的变化,确保数据库结构的一致性和可追溯性。

传统方式(不推荐)

-- ❌ 传统方式:手动执行 SQL 脚本
-- 1. 开发环境:手动执行 SQL
CREATE TABLE `system_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2. 生产环境:再次手动执行 SQL(容易出错)
-- 可能忘记执行某些 SQL,导致数据库结构不一致

使用数据库迁移(推荐)

// ✅ 使用数据库迁移:自动管理数据库结构
// 1. 创建迁移脚本
php think xadmin:package

// 2. 执行迁移(自动执行所有未执行的脚本)
php think migrate:run

// 3. 数据库结构自动同步,无需手动操作

🤔 使用优势

问题1:数据库结构不一致

不使用数据库迁移时,不同环境的数据库结构可能不一致:

-- ❌ 开发环境有某个字段,生产环境没有
-- 开发环境:ALTER TABLE user ADD COLUMN email VARCHAR(100);
-- 生产环境:忘记执行,导致程序出错

问题2:无法追踪数据库变更

不使用数据库迁移时,无法追踪数据库结构的变更历史:

-- ❌ 不知道数据库结构什么时候改的
-- 不知道改了什么
-- 无法回滚到之前的版本

问题3:团队协作困难

不使用数据库迁移时,团队协作困难:

-- ❌ 团队成员需要手动同步数据库结构
-- 容易出错,导致数据库结构不一致

使用数据库迁移的优势

// ✅ 统一管理,自动同步
// 1. 创建迁移脚本
// 2. 提交到代码仓库
// 3. 团队成员拉取代码后,执行迁移即可
php think migrate:run

🎯 核心功能

功能1:版本控制

管理数据库结构的版本,确保结构一致性:

// 每个迁移脚本都有版本号
// 20221013045829_install_account.php
// 版本号:20221013045829
// 脚本名:install_account

功能2:自动迁移

自动执行数据库结构变更,无需手动操作:

// 执行所有未执行的迁移脚本
php think migrate:run
// 自动检测并执行新的迁移脚本

功能3:回滚支持

支持数据库结构的回滚操作:

// 回滚最后一次迁移
php think migrate:rollback
// 撤销最后一次数据库结构变更

功能4:多数据库支持

支持多种数据库类型:

// 支持 MySQL、SQLite、SQL Server 等
// 同一套迁移脚本可以在不同数据库上运行

📖 相关概念

Phinx

  • PHP 数据库迁移工具
  • 基于 ThinkPHP 的 think-migration 组件
  • 支持数据库结构的版本控制

迁移脚本

  • 数据库结构变更的脚本文件
  • 位于 database/migrations/ 目录
  • 命名格式:版本号_脚本名称.php

版本号

  • 8 位年月日 + 6 位数字
  • 例如:20221013045829
  • 确保版本号的唯一性和可排序性

脚本名称

  • 容易区别功能的组合单词
  • 例如:install_accountupdate_user_table
  • 类名首字母大写:InstallAccountUpdateUserTable

🚀 主要功能

  • 版本控制: 基于 Phinx 的数据库版本控制
  • 自动迁移: 自动执行数据库结构变更
  • 多数据库支持: 支持 Sqlite、MySQL、SQL Server
  • 规范管理: 统一的数据库脚本管理规范
  • 插件集成: 与 ThinkPHP 插件机制无缝对接
  • 开发简化: 简化数据库迁移过程

📋 支持的数据库

主要支持

  • Sqlite: 轻量级数据库,适合开发和测试
  • MySQL: 主流关系型数据库
  • SQL Server: 企业级数据库

扩展支持

  • 其他数据库: 支持其他数据库类型
  • 驱动配置: 需要自行安装相应驱动
  • 配置优化: 支持自定义数据库配置

关于数据库脚本

为了确保数据库脚本文件管理的规范性和准确性,对数据库脚本的命名规则、结构以及执行机制进行了进一步的优化。

管理规范明确规定了数据库脚本文件的命名规则。每个脚本文件都由版本号和脚本名称两部分组成,确保每个脚本文件具有唯一的标识。版本号采用8位年月日加6位数字的组合形式,确保版本号的唯一性和可排序性。脚本名称则取容易区别其功能的组合单词,以便于理解和维护。

每个脚本文件都是一个继承自 think\migration\Migrator 的子类,主要实现其中的 change 方法。change 方法是脚本的核心部分,包含了数据库结构的变更逻辑。开发者可以根据需要自定义该方法,以实现各种复杂的数据库迁移操作。同时也提供了更多高级用法的文档和示例,供开发者自行研究和使用。

脚本的安装信息会记录在数据表 migrations 中,以便于跟踪和管理脚本的执行情况。如果需要清理并重复执行脚本,开发者只需删除该表中的数据即可。这种机制保证了脚本的重复执行不会出现问题,同时也方便了开发者进行数据库结构的调整和测试。

通过一个具体的案例来解析命名规则的实际应用。例如,文件名 20221013045829_install_wechat.php 对应的版本号是 20221013045829,脚本名称为 install_wechat 。根据命名规则,该脚本内的类名应为 InstallWechat ,类名首字母大写,与脚本名称保持一致,符合PHP的命名规范。

生成数据库脚本

目前在核心组件 ThinkLibrary 内集成了 php think xadmin:package 指令,可以将现有 MySQL 数据库打包为数据库脚本,打包好的文件存入在 database/migrations 目录。 打包好后的文件可能还需要后期修改,注意不能出现版本号重复和类名重复,如果出现重复需要将重复的历史数据删除或者修改文件名称。

数据库脚本里面需要做好执行前置检查逻辑,要保证同一个脚本可以重复多次运行,不会导致系统出现问题,系统不能保证同一个脚本只会执行一次,有可能是人为原因。

系统也可以通过这个指令进行数据库备份,支持数据结构与指定数据表内容备份,数据库备份配置文件见项目的 config/phinx.php 文件,主要有三个参数,描述如下:

  • 参数 backup 数组,表示要备份内容的数据表名,不填写就是不备份内容;
  • 参数 tables 数组,表示要备份结构的数据表名,不填写就是备份全部表;
  • 参数 ignore 数组,表示要忽略备份结构的数据表名,不填写就是备份全部表;

经验教训: 以往我们做过很多的项目,经常忘记将数据库打包备份落地到项目仓库,项目交付经过一段时间停止运维后,线上数据库和本地开发数据库可能都已经丢失,只剩下仓库里面的项目代码,此时这个项目就失去了重复利用的价值,没有数据库项目是跑不起来的,因此我们开发项目的时候需要经常备份数据库结构并与项目保存到一起。

备份所有数据: 可以通过 php think xadmin:package --all 备份所有数据 ( 数据库表结构和数据记录 ),为了维持项目不无限增大,备份记录只保存一份。将此备份脚本上传到对应项目,执行 php think migrate:run 即可完成数据恢复 ( 只有空数据库才会执行恢复 )。

操作演示截图

封装插件数据库

将上面生成的数据库脚本文件复制到插件目录,建议不要与源码 src 放到一个目录,防止 Composerautoload 扫描到而导致加载规则污染。

推荐目录结构:

plugin/think-plugs-account/
├── src/                    # 插件源码
├── stc/                    # 静态资源(可选)
│   └── database/          # 数据库脚本目录
│       └── migrations/    # 迁移脚本
│           └── 20221013045829_install_account.php
├── composer.json
└── readme.md

安装应用插件时通过 插件安装器 将应用插件的数据库脚本复制到 database/migrations 目录,执行 php think migrate:run 完成数据库安装与更新。

🔧 脚本示例

创建数据表(使用 PhinxExtend)

推荐使用 PhinxExtend::upgrade() 方法创建或更新数据表,该方法会自动处理表结构变更:

<?php
use think\admin\extend\PhinxExtend;
use think\migration\Migrator;

class InstallAdmin20241010 extends Migrator
{
    /**
     * 获取脚本名称
     * @return string
     */
    public function getName(): string
    {
        return 'AdminPlugin';
    }

    /**
     * 数据库变更
     */
    public function change()
    {
        $this->_create_system_auth();
        $this->_create_system_user();
    }

    /**
     * 创建权限表
     */
    private function _create_system_auth()
    {
        $table = $this->table('system_auth', [
            'engine' => 'InnoDB',
            'collation' => 'utf8mb4_general_ci',
            'comment' => '系统-权限',
        ]);
        
        // 使用 PhinxExtend::upgrade() 自动创建或更新表结构
        PhinxExtend::upgrade($table, [
            ['title', 'string', ['limit' => 100, 'default' => '', 'null' => true, 'comment' => '权限名称']],
            ['utype', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '身份权限']],
            ['desc', 'string', ['limit' => 500, 'default' => '', 'null' => true, 'comment' => '备注说明']],
            ['sort', 'biginteger', ['limit' => 20, 'default' => 0, 'null' => true, 'comment' => '排序权重']],
            ['status', 'integer', ['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '权限状态(1使用,0禁用)']],
            ['create_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间']],
        ], [
            'sort', 'title', 'status',  // 索引字段
        ], true);  // true 表示如果表已存在则更新,false 表示只创建
    }

    /**
     * 创建用户表
     */
    private function _create_system_user()
    {
        $table = $this->table('system_user', [
            'engine' => 'InnoDB',
            'collation' => 'utf8mb4_general_ci',
            'comment' => '系统-用户',
        ]);
        
        PhinxExtend::upgrade($table, [
            ['username', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '登录账号']],
            ['password', 'string', ['limit' => 32, 'default' => '', 'null' => true, 'comment' => '登录密码']],
            ['nickname', 'string', ['limit' => 50, 'default' => '', 'null' => true, 'comment' => '用户昵称']],
            ['authorize', 'text', ['null' => true, 'comment' => '权限授权']],
            ['status', 'integer', ['limit' => 1, 'default' => 1, 'null' => true, 'comment' => '用户状态(1使用,0禁用)']],
            ['is_deleted', 'integer', ['limit' => 1, 'default' => 0, 'null' => true, 'comment' => '删除状态(0未删,1已删)']],
            ['create_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP', 'null' => true, 'comment' => '创建时间']],
        ], [
            'username', 'status', 'create_at',  // 索引字段
        ], true);
    }
}

PhinxExtend::upgrade() 方法说明:

PhinxExtend::upgrade(
    $table,           // 表对象
    $columns,         // 字段定义数组
    $indexes,         // 索引字段数组
    $update = false   // 是否更新已存在的表
);

字段定义格式:

[
    '字段名',
    '字段类型',  // string, integer, biginteger, text, timestamp, datetime 等
    [
        'limit' => 100,           // 字段长度
        'default' => '',           // 默认值
        'null' => true,            // 是否允许 NULL
        'comment' => '字段说明'    // 字段注释
    ]
]

创建数据表(传统方式)

如果不使用 PhinxExtend,也可以使用传统方式:

<?php
use think\migration\Migrator;
use think\migration\db\Column;

class InstallAccount extends Migrator
{
    /**
     * 数据库变更
     */
    public function change()
    {
        // 创建用户账号表
        $table = $this->table('account_user', [
            'engine' => 'InnoDB',
            'comment' => '用户账号表',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci'
        ]);
        
        $table->addColumn('username', 'string', ['limit' => 50, 'comment' => '用户名'])
              ->addColumn('password', 'string', ['limit' => 32, 'comment' => '密码'])
              ->addColumn('nickname', 'string', ['limit' => 50, 'null' => true, 'comment' => '昵称'])
              ->addColumn('phone', 'string', ['limit' => 20, 'null' => true, 'comment' => '手机号'])
              ->addColumn('email', 'string', ['limit' => 100, 'null' => true, 'comment' => '邮箱'])
              ->addColumn('status', 'integer', ['limit' => 1, 'default' => 1, 'comment' => '状态'])
              ->addColumn('create_at', 'datetime', ['null' => true, 'comment' => '创建时间'])
              ->addColumn('update_at', 'datetime', ['null' => true, 'comment' => '更新时间'])
              ->addIndex(['username'], ['unique' => true, 'name' => 'idx_username'])
              ->addIndex(['phone'], ['name' => 'idx_phone'])
              ->create();
    }
}

修改数据表结构

<?php
use think\migration\Migrator;
use think\migration\db\Column;

class UpdateAccountTable extends Migrator
{
    /**
     * 数据库变更
     */
    public function change()
    {
        $table = $this->table('account_user');
        
        // 添加新字段
        $table->addColumn('avatar', 'string', ['limit' => 255, 'null' => true, 'comment' => '头像'])
              ->addColumn('gender', 'integer', ['limit' => 1, 'default' => 0, 'comment' => '性别'])
              ->update();
        
        // 修改字段
        $table->changeColumn('phone', 'string', ['limit' => 20, 'null' => true, 'comment' => '手机号码'])
              ->update();
    }
}

插入初始数据

<?php
use think\migration\Migrator;
use think\admin\extend\PhinxExtend;

class InstallAccountData extends Migrator
{
    /**
     * 数据库变更
     */
    public function change()
    {
        // 插入初始数据
        $this->table('account_user')->insert([
            [
                'username' => 'admin',
                'password' => md5('admin'),
                'nickname' => '管理员',
                'status' => 1,
                'create_at' => date('Y-m-d H:i:s'),
            ]
        ])->save();
        
        // 使用 PhinxExtend 写入菜单数据
        // 注意:需要调用插件的 Service 类 menu() 方法获取菜单数据
        PhinxExtend::write2menu([
            'name' => '账号管理',
            'subs' => [
                ['name' => '用户列表', 'node' => 'account/user/index'],
                ['name' => '添加用户', 'node' => 'account/user/add'],
            ]
        ]);

PhinxExtend::write2menu() 方法说明:

该方法用于将菜单数据写入 system_menu 数据表,通常在数据库迁移脚本中调用:

// 参数 array $menus 菜单数据数组
// 返回 void
PhinxExtend::write2menu(array $menus)

菜单数据格式:

[
    [
        'name' => '菜单分组名称',
        'subs' => [
            [
                'name' => '菜单项名称',
                'icon' => 'layui-icon layui-icon-set',  // 图标(可选)
                'node' => 'admin/config/index',         // 权限节点
            ],
            // 更多菜单项...
        ],
    ],
    // 更多菜单分组...
]

实际应用示例:

// 在数据库迁移脚本中调用插件的 Service 类获取菜单
use app\admin\Service as AdminService;

public function change()
{
    // 获取插件菜单数据
    $menus = AdminService::menu();
    
    // 写入菜单到数据库
    PhinxExtend::write2menu($menus);
}

执行前置检查

<?php
use think\migration\Migrator;
use think\migration\db\Column;

class InstallAccount extends Migrator
{
    /**
     * 数据库变更
     */
    public function change()
    {
        // 检查表是否已存在
        if (!$this->hasTable('account_user')) {
            $table = $this->table('account_user');
            // ... 创建表结构
            $table->create();
        } else {
            // 表已存在,只添加缺失的字段
            $table = $this->table('account_user');
            
            if (!$table->hasColumn('avatar')) {
                $table->addColumn('avatar', 'string', ['limit' => 255, 'null' => true])
                      ->update();
            }
        }
    }
}

📋 常用命令

# 生成数据库脚本(从现有数据库)
php think xadmin:package

# 备份所有数据(结构和数据)
php think xadmin:package --all

# 执行数据库迁移
php think migrate:run

# 回滚最后一次迁移
php think migrate:rollback

# 查看迁移状态
php think migrate:status
最近更新:
Contributors: 邹景立, Anyon