webman_admin/src/plugin/admin/app/controller/database/TableController.php
2022-08-31 15:43:11 +08:00

712 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace plugin\admin\app\controller\database;
use Illuminate\Database\Schema\Blueprint;
use plugin\admin\app\controller\Base;
use plugin\admin\app\model\Option;
use plugin\admin\App\Util;
use Support\Db;
use Support\Exception\BusinessException;
use Support\Request;
class TableController extends Base
{
/**
* 不需要鉴权的方法
* @var string[]
*/
public $noNeedAuth = ['types'];
/**
* 查询表
*
* @param Request $request
* @return \support\Response
*/
public function show(Request $request)
{
$database = config('database.connections')['plugin.admin.mysql']['database'];
$field = $request->get('field', 'TABLE_NAME');
$order = $request->get('order', 'ascend');
$allow_column = ['TABLE_NAME','TABLE_COMMENT','ENGINE','TABLE_ROWS','CREATE_TIME','UPDATE_TIME','TABLE_COLLATION'];
if (!in_array($field, $allow_column)) {
$field = 'TABLE_NAME';
}
$order = $order === 'ascend' ? 'asc' : 'desc';
$tables = Db::select("SELECT TABLE_NAME,TABLE_COMMENT,ENGINE,TABLE_ROWS,CREATE_TIME,UPDATE_TIME,TABLE_COLLATION FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' order by $field $order");
if ($tables) {
$table_names = array_column($tables, 'TABLE_NAME');
$table_rows_count = [];
foreach ($table_names as $table_name) {
$table_rows_count[$table_name] = Db::connection('plugin.admin.mysql')->table($table_name)->count();
}
foreach ($tables as $key => $table) {
$tables[$key]->TABLE_ROWS = $table_rows_count[$table->TABLE_NAME] ?? $table->TABLE_ROWS;
}
}
return $this->json(0, 'ok', $tables);
}
/**
* 创建表
*
* @param Request $request
* @return \support\Response
*/
public function create(Request $request)
{
$data = $request->post();
$table_name = $data['table']['name'];
$table_comment = $data['table']['comment'];
$columns = $data['columns'];
$keys = $data['keys'];
Db::schema()->create($table_name, function (Blueprint $table) use ($columns) {
$type_method_map = Util::methodControlMap();
foreach ($columns as $column) {
if (!isset($column['type'])) {
throw new BusinessException("请为{$column['field']}选择类型");
}
if (!isset($type_method_map[$column['type']])) {
throw new BusinessException("不支持的类型{$column['type']}");
}
$this->createColumn($column, $table);
}
$table->charset = 'utf8mb4';
$table->collation = 'utf8mb4_general_ci';
$table->engine = 'InnoDB';
});
// @todo 防注入
Db::statement("ALTER TABLE `$table_name` COMMENT '$table_comment'");
// 索引
Db::schema()->table($table_name, function (Blueprint $table) use ($keys) {
foreach ($keys as $key) {
$name = $key['name'];
$columns = $key['columns'];
$type = $key['type'];
if ($type == 'unique') {
$table->unique($columns, $name);
continue;
}
$table->index($columns, $name);
}
});
$form_schema = $request->post('forms', []);
$form_schema_map = [];
foreach ($form_schema as $item) {
$form_schema_map[$item['field']] = $item;
}
$form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE);
$this->updateSchemaOption($table_name, $form_schema_map);
return $this->json(0, 'ok');
}
/**
* 修改表
*
* @param Request $request
* @return \support\Response
* @throws BusinessException
*/
public function modify(Request $request)
{
$data = $request->post();
$old_table_name = $data['table']['old_name'];
$table_name = $data['table']['name'];
$table_comment = $data['table']['comment'];
$columns = $data['columns'];
$keys = $data['keys'] ?? [];
// 改表名
if ($table_name != $old_table_name) {
Util::checkTableName($table_name);
Db::schema()->rename($old_table_name, $table_name);
}
$old_columns = $this->getSchema($table_name, 'columns');
$type_method_map = Util::methodControlMap();
foreach ($columns as $column) {
if (!isset($type_method_map[$column['type']])) {
throw new BusinessException("不支持的类型{$column['type']}");
}
$field = $column['field'];
// 重命名的字段 mysql8才支持
if (isset($column['old_field']) && $column['old_field'] !== $field) {
//Db::statement("ALTER TABLE $table_name RENAME COLUMN {$column['old_field']} to $field");
}
$old_column = $old_columns[$field] ?? [];
// 类型更改
foreach ($old_column as $key => $value) {
if (isset($column[$key]) && $column[$key] != $value) {
$this->modifyColumn($column, $table_name);
break;
}
}
}
$table = $this->getSchema($table_name, 'table');
// @todo $table_comment 防止SQL注入
if ($table_comment !== $table['comment']) {
Db::statement("ALTER TABLE `$table_name` COMMENT '$table_comment'");
}
$old_columns = $this->getSchema($table_name, 'columns');
Db::schema()->table($table_name, function (Blueprint $table) use ($columns, $old_columns, $keys, $table_name) {
foreach ($columns as $column) {
$field = $column['field'];
// 新字段
if (!isset($old_columns[$field])) {
$this->createColumn($column, $table);
}
}
// 更新索引名字
foreach ($keys as $key) {
if (!empty($key['old_name']) && $key['old_name'] !== $key['name']) {
$table->renameIndex($key['old_name'], $key['name']);
}
}
});
// 找到删除的字段
$old_columns = $this->getSchema($table_name, 'columns');
$exists_column_names = array_column($columns, 'field', 'field');
$old_columns_names = array_column($old_columns, 'field');
$drop_column_names = array_diff($old_columns_names, $exists_column_names);
foreach ($drop_column_names as $drop_column_name) {
//$table->dropColumn($drop_column_name); 无法使用
Db::statement("ALTER TABLE $table_name DROP COLUMN $drop_column_name");
}
$old_keys = $this->getSchema($table_name, 'keys');
Db::schema()->table($table_name, function (Blueprint $table) use ($keys, $old_keys, $table_name) {
foreach ($keys as $key) {
$key_name = $key['name'];
$old_key = $old_keys[$key_name] ?? [];
// 如果索引有变动,则删除索引,重新建立索引
if ($old_key && ($key['type'] != $old_key['type'] || $key['columns'] != $old_key['columns'])) {
$old_key = [];
unset($old_keys[$key_name]);
$table->dropIndex($key_name);
}
// 重新建立索引
if (!$old_key) {
$name = $key['name'];
$columns = $key['columns'];
$type = $key['type'];
if ($type == 'unique') {
$table->unique($columns, $name);
continue;
}
$table->index($columns, $name);
}
}
// 找到删除的索引
$exists_key_names = array_column($keys, 'name', 'name');
$old_keys_names = array_column($old_keys, 'name');
$drop_keys_names = array_diff($old_keys_names, $exists_key_names);
foreach ($drop_keys_names as $name) {
$table->dropIndex($name);
}
});
$form_schema = $request->post('forms', []);
$form_schema_map = [];
foreach ($form_schema as $item) {
$form_schema_map[$item['field']] = $item;
}
$form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE);
$option_name = $this->updateSchemaOption($table_name, $form_schema_map);
return $this->json(0,$option_name);
}
/**
* 查询记录
*
* @param Request $request
* @return \support\Response
*/
public function select(Request $request)
{
$page = $request->get('page', 1);
$field = $request->get('field');
$order = $request->get('order', 'descend');
$table = $request->get('table');
$format = $request->get('format', 'normal');
$page_size = $request->get('pageSize', $format === 'tree' ? 1000 : 10);
if (!preg_match('/[a-zA-Z_0-9]+/', $table)) {
return $this->json(1, '表不存在');
}
$allow_column = Db::select("desc $table");
if (!$allow_column) {
return $this->json(2, '表不存在');
}
$allow_column = array_column($allow_column, 'Field', 'Field');
if (!in_array($field, $allow_column)) {
$field = current($allow_column);
}
$order = $order === 'ascend' ? 'asc' : 'desc';
$paginator = Db::connection('plugin.admin.mysql')->table($table);
foreach ($request->get() as $column => $value) {
if (!$value) {
continue;
}
if (isset($allow_column[$column])) {
if (is_array($value)) {
if ($value[0] == 'undefined' || $value[1] == 'undefined') {
continue;
}
$paginator = $paginator->whereBetween($column, $value);
} else {
$paginator = $paginator->where($column, $value);
}
}
}
$paginator = $paginator->orderBy($field, $order)->paginate($page_size, '*', 'page', $page);
$items = $paginator->items();
if ($format == 'tree') {
$items_map = [];
foreach ($items as $item) {
$items_map[$item->id] = (array)$item;
}
$formatted_items = [];
foreach ($items_map as $item) {
if ($item['pid'] && isset($items_map[$item['pid']])) {
$items_map[$item['pid']]['children'][] = $item;
}
}
foreach ($items_map as $item) {
if (!$item['pid']) {
$formatted_items[] = $item;
}
}
$items = $formatted_items;
}
return $this->json(0, 'ok', [
'items' => $items,
'total' => $paginator->total()
]);
}
/**
* 插入记录
*
* @param Request $request
* @return \support\Response
*/
public function insert(Request $request)
{
$table = $request->input('table');
$data = $request->post('data');
$columns = $this->getSchema($table, 'columns');
foreach ($data as $col => $item) {
if (is_array($item)) {
$data[$col] = implode(',', $item);
continue;
}
if ($col === 'password') {
$data[$col] = Util::passwordHash($item);
}
}
$datetime = date('Y-m-d H:i:s');
if (isset($columns['created_at']) && !isset($data['created_at'])) {
$data['created_at'] = $datetime;
}
if (isset($columns['updated_at']) && !isset($data['updated_at'])) {
$data['updated_at'] = $datetime;
}
$id = Db::connection('plugin.admin.mysql')->table($table)->insertGetId($data);
return $this->json(0, $id);
}
/**
* 更新记录
*
* @param Request $request
* @return \support\Response
* @throws BusinessException
*/
public function update(Request $request)
{
$table = $request->input('table');
$column = $request->post('column');
$value = $request->post('value');
$data = $request->post('data');
$columns = $this->getSchema($table, 'columns');
foreach ($data as $col => $item) {
if (is_array($item)) {
$data[$col] = implode(',', $item);
}
if ($col === 'password') {
// 密码为空,则不更新密码
if ($item == '') {
unset($data[$col]);
continue;
}
$data[$col] = Util::passwordHash($item);
}
}
$datetime = date('Y-m-d H:i:s');
if (isset($columns['updated_at']) && !isset($data['updated_at'])) {
$data['updated_at'] = $datetime;
}
var_export($data);
Util::checkTableName($table);
Db::connection('plugin.admin.mysql')->table($table)->where($column, $value)->update($data);
return $this->json(0);
}
/**
* 删除记录
*
* @param Request $request
* @return \support\Response
* @throws BusinessException
*/
public function delete(Request $request)
{
$table = $request->input('table');
$column = $request->post('column');
$value = $request->post('value');
Util::checkTableName($table);
Db::connection('plugin.admin.mysql')->table($table)->where([$column => $value])->delete();
return $this->json(0);
}
/**
* 表摘要
*
* @param Request $request
* @return \support\Response
* @throws BusinessException
*/
public function schema(Request $request)
{
$table = $request->get('table');
Util::checkTableName($table);
$schema = Option::where('name', "table_form_schema_$table")->value('value');
$form_schema_map = $schema ? json_decode($schema, true) : [];
$data = $this->getSchema($table);
foreach ($data['forms'] as $field => $item) {
if (isset($form_schema_map[$field])) {
$data['forms'][$field] = $form_schema_map[$field];
}
}
return $this->json(0, 'ok', [
'table' => $data['table'],
'columns' => array_values($data['columns']),
'forms' => array_values($data['forms']),
'keys' => array_values($data['keys']),
]);
}
/**
* 获取摘要
*
* @param $table
* @param $section
* @return array|mixed
*/
protected function getSchema($table, $section = null)
{
$database = config('database.connections')['plugin.admin.mysql']['database'];
$schema_raw = $section !== 'table' ? Db::select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table'") : [];
$forms = [];
$columns = [];
foreach ($schema_raw as $item) {
$field = $item->COLUMN_NAME;
$columns[$field] = [
'field' => $field,
'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')),
'comment' => $item->COLUMN_COMMENT,
'default' => $item->COLUMN_DEFAULT,
'length' => $this->getLengthValue($item),
'nullable' => $item->IS_NULLABLE !== 'NO',
'primary_key' => $item->COLUMN_KEY === 'PRI',
'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false
];
$forms[$field] = [
'field' => $field,
'comment' => $item->COLUMN_COMMENT,
'control' => Util::typeToControl($item->DATA_TYPE),
'form_show' => $item->COLUMN_KEY !== 'PRI',
'list_show' => true,
'enable_sort' => false,
'readonly' => $item->COLUMN_KEY === 'PRI',
'searchable' => false,
'search_type' => 'normal',
'control_args' => '',
];
}
$table_schema = $section == 'table' || !$section ? Db::select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : [];
$indexes = $section == 'keys' || !$section ? Db::select("SHOW INDEX FROM $table") : [];
$keys = [];
foreach ($indexes as $index) {
$key_name = $index->Key_name;
if ($key_name == 'PRIMARY') {
continue;
}
if (!isset($keys[$key_name])) {
$keys[$key_name] = [
'name' => $key_name,
'columns' => [],
'type' => $index->Non_unique == 0 ? 'unique' : 'normal'
];
}
$keys[$key_name]['columns'][] = $index->Column_name;
}
$data = [
'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? ''],
'columns' => $columns,
'forms' => $forms,
'keys' => array_reverse($keys, true)
];
return $section ? $data[$section] : $data;
}
/**
* 获取字段长度
*
* @param $schema
* @return string
*/
protected function getLengthValue($schema)
{
$type = $schema->DATA_TYPE;
if (in_array($type, ['float', 'decimal', 'double'])) {
return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}";
}
if ($type === 'enum') {
return implode(',', array_map(function($item){
return trim($item, "'");
}, explode(',', substr($schema->COLUMN_TYPE, 5, -1))));
}
if (in_array($type, ['varchar', 'text', 'char'])) {
return $schema->CHARACTER_MAXIMUM_LENGTH;
}
if (in_array($type, ['time', 'datetime', 'timestamp'])) {
return $schema->CHARACTER_MAXIMUM_LENGTH;
}
return '';
}
/**
* 删除表
*
* @param Request $request
* @return \support\Response
*/
public function drop(Request $request)
{
$table_name = $request->post('table');
if (!$table_name) {
return $this->json(0, 'not found');
}
$table_not_allow_drop = ['wa_admins', 'wa_users', 'wa_options', 'wa_admin_roles', 'wa_admin_rules'];
if (in_array($table_name, $table_not_allow_drop)) {
return $this->json(400, "$table_name 不允许删除");
}
Db::schema()->drop($table_name);
// 删除schema
Db::table('wa_options')->where('name', "table_form_schema_$table_name")->delete();
return $this->json(0, 'ok');
}
/**
* 创建字段
*
* @param $column
* @param Blueprint $table
* @return mixed
*/
protected function createColumn($column, Blueprint $table)
{
$method = $column['type'];
$args = [$column['field']];
if (stripos($method, 'int') !== false) {
// auto_increment 会自动成为主键
if ($column['auto_increment']) {
$column['nullable'] = false;
$column['default'] = '';
$args[] = true;
}
} elseif (in_array($method, ['string', 'char']) || stripos($method, 'time') !== false) {
if ($column['length']) {
$args[] = $column['length'];
}
} elseif ($method === 'enum') {
$args[] = array_map('trim', explode(',', $column['length']));
} elseif (in_array($method, ['float', 'decimal', 'double'])) {
if ($column['length']) {
$args = array_merge($args, array_map('trim', explode(',', $column['length'])));
}
} else {
$column['auto_increment'] = false;
}
$column_def = [$table, $method](...$args);
if (!empty($column['comment'])) {
$column_def = $column_def->comment($column['comment']);
}
if (!$column['auto_increment'] && $column['primary_key']) {
$column_def = $column_def->primary(true);
}
if ($column['auto_increment'] && !$column['primary_key']) {
$column_def = $column_def->primary(false);
}
$column_def = $column_def->nullable($column['nullable']);
if ($column['primary_key']) {
$column_def = $column_def->nullable(false);
}
if ($column['default'] && !in_array($method, ['text'])) {
$column_def->default($column['default']);
}
return $column_def;
}
/**
* 更改字段
*
* @param $column
* @param Blueprint $table
* @return mixed
*/
protected function modifyColumn($column, $table)
{
$method = $column['type'];
$field = $column['field'];
$nullable = $column['nullable'];
$default = $column['default'];
$comment = $column['comment'];
$auto_increment = $column['auto_increment'];
$length = (int)$column['length'];
$primary_key = $column['primary_key'];
// @todo 防止SQL注入
if (isset($column['old_field']) && $column['old_field'] !== $field) {
$sql = "ALTER TABLE $table CHANGE COLUMN {$column['old_field']} $field ";
} else {
$sql = "ALTER TABLE $table MODIFY $field ";
}
if (stripos($method, 'integer') !== false) {
$type = str_ireplace('integer', 'int', $method);
if (stripos($method, 'unsigned') !== false) {
$type = str_ireplace('unsigned', '', $type);
$sql .= "$type ";
$sql .= 'unsigned ';
} else {
$sql .= "$type ";
}
if ($auto_increment) {
$column['nullable'] = false;
$column['default'] = '';
$sql .= 'AUTO_INCREMENT ';
}
} else {
switch ($method) {
case 'string':
$length = $length ?: 255
; $sql .= "varchar($length) ";
break;
case 'char':
case 'time':
$sql .= $length ? "$method($length) " : "$method ";
break;
case 'enum':
// @todo 防止SQL注入
$args = array_map('trim', explode(',', $column['length']));
$sql .= "enum('" . implode("','", $args) . "') ";
break;
case 'double':
case 'float':
case 'decimal':
if (trim($column['length'])) {
$args = array_map('intval', explode(',', $column['length']));
$args[1] = $args[1] ?? $args[0];
$sql .= "$method({$args[0]}, {$args[1]}) ";
break;
}
$sql .= "$method ";
break;
default :
$sql .= "$method ";
}
}
if (!$nullable) {
$sql .= "NOT NULL ";
}
if ($default !== null && !in_array($method, ['text'])) {
$sql .= "DEFAULT '$default' ";
}
if ($comment !== null) {
$sql .= "COMMENT '$comment' ";
}
Db::statement($sql);
}
/**
* 字段类型列表
*
* @param Request $request
* @return \support\Response
*/
public function types(Request $request)
{
$types = Util::methodControlMap();
return $this->json(0, 'ok', $types);
}
/**
* 获取在options对用的name
*
* @param $table_name
* @return string
*/
protected function getSchemaOptionName($table_name)
{
return "table_form_schema_$table_name";
}
/**
* 更新表的form schema信息
*
* @param $table_name
* @param $data
* @return string
*/
protected function updateSchemaOption($table_name, $data)
{
$option_name = $this->getSchemaOptionName($table_name);
$option = Option::where('name', $option_name)->first();
if ($option) {
Option::where('name', $option_name)->update(['value' => $data]);
} else {
Option::insert(['name' => $option_name, 'value' => $data]);
}
return $option_name;
}
}