fix(migration): 支持复合索引与前缀索引同步迁移

This commit is contained in:
Anyon 2026-04-02 12:55:15 +08:00
parent 9ff9895449
commit 4e3c460694
3 changed files with 160 additions and 32 deletions

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/**
* +----------------------------------------------------------------------
* | ThinkAdmin Plugin for ThinkAdmin
* +----------------------------------------------------------------------
* | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
* +----------------------------------------------------------------------
* | 官方网站: https://thinkadmin.top
* +----------------------------------------------------------------------
* | 开源协议 ( https://mit-license.org )
* | 免责声明 ( https://thinkadmin.top/disclaimer )
* | 会员特权 ( https://thinkadmin.top/vip-introduce )
* +----------------------------------------------------------------------
* | gitee 代码仓库https://gitee.com/zoujingli/ThinkAdmin
* | github 代码仓库https://github.com/zoujingli/ThinkAdmin
* +----------------------------------------------------------------------
*/
namespace think\admin\extend;
final class IndexNameService
{
/**
* 生成符合长度限制的索引名称,支持单列与复合索引。
* @param array<int, string>|string $columns
*/
public static function generate(string $table, array|string $columns, bool $unique = false): string
{
$columns = is_array($columns) ? $columns : [$columns];
$columns = array_values(array_filter(array_map(static function ($column): string {
return trim((string)$column);
}, $columns), 'strlen'));
$abbr = implode('', array_map(static function (string $word): string {
return $word[0] ?? '';
}, array_values(array_filter(explode('_', $table), 'strlen'))));
$prefix = $unique ? 'uni_' : 'idx_';
$tableHash = substr(md5($table), -4);
$firstColumn = $columns[0] ?? 'col';
$columnsKey = implode(',', $columns);
if (count($columns) <= 1) {
$candidate = "{$prefix}{$abbr}_{$tableHash}_{$firstColumn}";
} else {
$candidate = "{$prefix}{$abbr}_{$tableHash}_{$firstColumn}_" . substr(md5($columnsKey), 0, 8);
}
if (strlen($candidate) <= 64) {
return $candidate;
}
$hash = substr(md5($table . '|' . $columnsKey . '|' . ($unique ? '1' : '0')), 0, 16);
return "{$prefix}{$abbr}_{$tableHash}_{$hash}";
}
}

View File

@ -97,10 +97,12 @@ class PhinxExtend
}
}
// 生成索引规则
foreach ($indexs as $field) {
if (empty($isExists) || !$table->hasIndex($field)) {
$table->addIndex($field, ['name' => self::genIndexName($table->getName(), $field)]);
foreach ($indexs as $spec) {
[$columns, $options] = self::parseIndexSpec($table->getName(), $spec);
if (empty($columns) || (!empty($isExists) && $table->hasIndex($columns))) {
continue;
}
$table->addIndex($columns, $options);
}
$isExists ? $table->update() : $table->create();
if ($table->hasColumn('id')) {
@ -216,17 +218,38 @@ class PhinxExtend
* 缩写规则: 取每个下划线分隔部分的第一个字母
*
* @param string $table 表名
* @param string $name 字段名
* @param array<int, string>|string $name 字段名
* @return string 生成的索引名称
*/
private static function genIndexName(string $table, string $name): string
private static function genIndexName(string $table, array|string $name, bool $unique = false): string
{
$getInitials = function (string $str): string {
return implode('', array_map(function ($part) {
return $part[0] ?? '';
}, explode('_', $str)));
};
return sprintf('idx_%s_%s_%s', substr(md5($table), -4), $getInitials($table), $name);
return IndexNameService::generate($table, $name, $unique);
}
/**
* @return array{0:array<int, string>,1:array<string, mixed>}
*/
private static function parseIndexSpec(string $table, mixed $spec): array
{
if (is_string($spec)) {
$columns = [$spec];
return [$columns, ['name' => self::genIndexName($table, $columns)]];
}
if (is_array($spec) && array_is_list($spec)) {
$columns = array_values(array_filter($spec, 'is_string'));
return [$columns, ['name' => self::genIndexName($table, $columns)]];
}
if (is_array($spec)) {
$columns = array_values(array_filter((array)($spec['columns'] ?? []), 'is_string'));
$unique = !empty($spec['unique']);
$options = array_diff_key($spec, ['columns' => true]);
$options['name'] = $options['name'] ?? self::genIndexName($table, $columns, $unique);
return [$columns, $options];
}
return [[], []];
}
/**
@ -343,16 +366,50 @@ CODE;
// 生成索引内容
$_indexs = [];
foreach (Library::$sapp->db->connect()->query("show index from {$table}") as $index) {
$index['Key_name'] !== 'PRIMARY' && $_indexs[] = $index['Column_name'];
$keyName = strval($index['Key_name'] ?? '');
if ($keyName === '' || $keyName === 'PRIMARY') {
continue;
}
$_indexs[$keyName]['unique'] = intval($index['Non_unique'] ?? 1) === 0;
$column = strval($index['Column_name'] ?? '');
$_indexs[$keyName]['columns'][intval($index['Seq_in_index'] ?? 0)] = $column;
if (is_numeric($index['Sub_part'] ?? null) && intval($index['Sub_part']) > 0) {
$_indexs[$keyName]['limits'][$column] = intval($index['Sub_part']);
}
}
usort($_indexs, function ($a, $b) {
return strlen($a) <=> strlen($b);
});
$_indexString = '[' . PHP_EOL . "\t\t\t";
ksort($_indexs);
$_indexSpecs = [];
foreach ($_indexs as $index) {
$_indexString .= "'{$index}', ";
$columns = $index['columns'] ?? [];
ksort($columns);
$columns = array_values(array_filter($columns, 'strlen'));
if (empty($columns)) {
continue;
}
$options = [];
if (!empty($index['limits'])) {
$limits = [];
foreach ($columns as $column) {
if (isset($index['limits'][$column])) {
$limits[$column] = intval($index['limits'][$column]);
}
}
if (!empty($limits)) {
$options['limit'] = $limits;
}
}
if (!empty($index['unique'])) {
$options['unique'] = true;
}
if (count($columns) === 1 && empty($options)) {
$_indexSpecs[] = $columns[0];
} elseif (count($columns) > 1 && empty($options)) {
$_indexSpecs[] = $columns;
} else {
$_indexSpecs[] = array_merge(['columns' => $columns], $options);
}
}
$_indexString .= PHP_EOL . "\t\t]";
$_indexString = self::_arr2str($_indexSpecs);
$content = str_replace(['_FIELDS_', '_INDEXS_', '__FORCE__'], [$_fieldString, $_indexString, $force ? 'true' : 'false'], $content) . PHP_EOL . PHP_EOL;
}
return $rehtml ? $content : highlight_string($content, true);

View File

@ -20,6 +20,7 @@ declare(strict_types=1);
namespace plugin\helper;
use think\admin\extend\IndexNameService;
use think\admin\service\SystemService;
use think\console\Command;
use think\facade\Db;
@ -50,36 +51,48 @@ class DbIndexStruct extends Command
*/
public function handle(): void
{
[$tables, $total, $count] = SystemService::getTables();
[$tables, $total] = SystemService::getTables();
$number = 1;
$renamed = 0;
foreach ($tables as $table) {
$this->output->writeln(sprintf("[%s/%s] 开始处理表 {$table}", $count++, $total));
$this->output->writeln(sprintf("[%s/%s] 开始处理表 %s", $number++, $total, $table));
$indexes = [];
foreach (Db::query(sprintf('SHOW INDEX FROM `%s`', $table)) as $index) {
$keyName = $index['Key_name'] ?? '';
if ($keyName === 'PRIMARY') {
$keyName = strval($index['Key_name'] ?? '');
if ($keyName === '' || $keyName === 'PRIMARY') {
continue;
}
$newName = $this->genIndexName($table, (array)$index);
if ($keyName === $newName) {
$indexes[$keyName]['unique'] = intval($index['Non_unique'] ?? 1) === 0;
$indexes[$keyName]['columns'][intval($index['Seq_in_index'] ?? 0)] = strval($index['Column_name'] ?? '');
}
$exists = array_fill_keys(array_keys($indexes), true);
foreach ($indexes as $keyName => $index) {
$columns = $index['columns'] ?? [];
ksort($columns);
$columns = array_values(array_filter($columns, 'strlen'));
if (empty($columns)) {
continue;
}
$newName = $this->genIndexName($table, $columns, !empty($index['unique']));
if ($keyName === $newName || isset($exists[$newName])) {
continue;
}
Db::execute(sprintf('ALTER TABLE `%s` RENAME INDEX `%s` TO `%s`', $table, $keyName, $newName));
++$count;
$exists[$newName] = true;
++$renamed;
}
}
$this->output->writeln("✅ 完成 {$count} 个索引重命名");
$this->output->writeln("✅ 完成 {$renamed} 个索引重命名");
}
/**
* 生成索引名称.
* @param string $table 表名
* @param array $index 索引信息
* @param array<int, string>|string $columns 索引字段
* @return string 生成的索引名称
*/
private function genIndexName(string $table, array $index): string
private function genIndexName(string $table, array|string $columns, bool $unique = false): string
{
$abbr = implode('', array_map(function ($word) {
return $word[0];
}, explode('_', $table)));
return ($index['Non_unique'] ? 'idx_' : 'uni_') . $abbr . '_' . substr(md5($table), -4) . '_' . $index['Column_name'];
return IndexNameService::generate($table, $columns, $unique);
}
}