diff --git a/plugin/think-library/src/extend/IndexNameService.php b/plugin/think-library/src/extend/IndexNameService.php new file mode 100644 index 000000000..e45328c38 --- /dev/null +++ b/plugin/think-library/src/extend/IndexNameService.php @@ -0,0 +1,58 @@ +|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}"; + } +} diff --git a/plugin/think-library/src/extend/PhinxExtend.php b/plugin/think-library/src/extend/PhinxExtend.php index f6126421d..a08264ea5 100644 --- a/plugin/think-library/src/extend/PhinxExtend.php +++ b/plugin/think-library/src/extend/PhinxExtend.php @@ -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|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,1:array} + */ + 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); diff --git a/plugin/think-plugs-helper/src/DbIndexStruct.php b/plugin/think-plugs-helper/src/DbIndexStruct.php index 1b8ef01c7..987602e2d 100644 --- a/plugin/think-plugs-helper/src/DbIndexStruct.php +++ b/plugin/think-plugs-helper/src/DbIndexStruct.php @@ -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|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); } }