<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests; use App\Http\Controllers\Controller; use Config; use DB; use Exception; use Log; use Validator; define('TYPE_INCREMENT', 10); // 自增量 define('TYPE_INT', 11); // 整数 define('TYPE_UINT', 12); // 无符号整数 define('TYPE_FLOAT', 13); // 浮点数 define('TYPE_BOOL', 14); // bool define('TYPE_DATE', 20); // 日期 define('TYPE_DATETIME', 21); // 日期时间 define('TYPE_TIMESTAMP', 22); // 时间戳 define('TYPE_MTIME', 23); // 修改时间 define('TYPE_CTIME', 24); // 创建时间 define('TYPE_STRING', 30); // 字符串 define('TYPE_JSON', 31); // Json define('TYPE_TEXT', 32); // 长文本 define('TYPE_USER', 40); // 用户名 define('TYPE_MUSER', 41); // 修改者 define('TYPE_CUSER', 42); // 创建者 define('TYPE_URL', 43); // URL define('TYPE_IMAGE', 44); // 图片地址; 允许上传 define('TYPE_EMAIL', 45); // 邮箱 define('TYPE_IP', 46); // IP地址 define('TYPE_COMPLATE', 50); // 自动补全 class TableConfig { // 转换数据库的set,enum类型的定义 private function converEnums($v) { if (is_array($v)) { $str = ""; foreach ($v as $item) $str .= ',' . $item; return substr($str, 1); } else { return $v; } } private function enumValues($enums) { preg_match_all("/'(([^']|'')+)',?/", substr($enums, 5, -1), $match); foreach ($match[1] as &$elm) $elm = str_replace("''", "'", $elm); return $match[1]; } // 检查输入 private function checkInputFormt($fielddef, $v, $raw, $limit=null) { if (($v === '' || $v === null) && !$fielddef->need) return true; switch ($fielddef->type) { case TYPE_INCREMENT: // 自增变量 case TYPE_INT: // 整数 if (filter_var($v, FILTER_VALIDATE_INT) === false) return "错误的的整数格式"; return true; case TYPE_UINT: // 无符号整数 if (filter_var($v, FILTER_VALIDATE_INT) === false) return "错误的的整数格式"; if ($v < 0) return "输入值是负数"; break; case TYPE_FLOAT: // 浮点数 if (filter_var($v, FILTER_VALIDATE_FLOAT) === false) return "错误的的整数格式"; break; case TYPE_DATE: // 日期 if (date('Y-m-d', strtotime($v)) != $v) return "错误的的日期格式"; break; case TYPE_DATETIME: // 日期时间 if (date('Y-m-d H:i:s', strtotime($v)) != $v) return "错误的的日期格式"; break; case TYPE_TIMESTAMP: // 时间戳 if (filter_var($v, FILTER_VALIDATE_INT) === false) return "错误的的时间戳格式"; break; case TYPE_USER: if ($v != 'ALL' && filter_var($v, FILTER_VALIDATE_EMAIL) === false) return "错误的用户名"; break; case TYPE_EMAIL: if (filter_var($v, FILTER_VALIDATE_EMAIL) === false) return "错误的邮箱"; break; case TYPE_URL: if (filter_var($v, FILTER_VALIDATE_URL) === false) return "错误的邮箱"; break; case TYPE_IMAGE: if (filter_var($v, FILTER_VALIDATE_URL) === false) return "错误的邮箱"; break; case TYPE_IP: if (filter_var($v, FILTER_VALIDATE_IP) === false) return "错误的邮箱"; break; } switch ($fielddef->type) { case TYPE_INT: case TYPE_UINT: case TYPE_FLOAT: case TYPE_TIMESTAMP: case TYPE_INCREMENT: if (isset($fielddef->max) && $v > $fielddef->max) return "超过了最大值" . $fielddef->max; if (isset($fielddef->min) && $v < $fielddef->min) return "小于最小值" . $fielddef->min; break; default: if (isset($fielddef->maxlen) && $fielddef->maxlen > 0 && strlen($v) > $fielddef->maxlen) return "超过了最大长度" . $fielddef->maxlen; if (isset($raw["maxlen"]) && strlen($v) > $raw["maxlen"]) return "超过了系统存储大小" . $raw["maxlen"]; if (isset($fielddef->minlen) && $fielddef->minlen > 0 && strlen($v) < $fielddef->minlen) return "小于最大长度" . $fielddef->minlen; break; } if ($raw["type"] == 'set' || $raw["type"] == 'enum') { if (array_search($v, $raw["enums"]) === false) return "字段取值只能为(" . $this->converEnums($raw["enums"]) . ")"; } if ($limit) { $name = $fielddef->name; $validator = Validator::make([$name => $v], [$name => $limit]); if ($validator->fails()) return json_encode($validator->errors()->all()); } return true; } private function converType($elm) { $datatype = [ 'name' => $elm->COLUMN_NAME, 'default_val' => $elm->COLUMN_DEFAULT, 'extra' => $elm->EXTRA, 'comment' => $elm->COLUMN_COMMENT, 'key' => $elm->COLUMN_KEY ]; switch ($elm->DATA_TYPE) { case 'bit': $datatype['type'] = 'int'; $bits = 1; if (preg_match('/bit\(([0-9]+)\)/', $elm->COLUMN_TYPE, $matchs) == 1) $bits = (int)$matchs[2]; $datatype['min'] = 0; if ($bits < 64) $datatype['max'] = 1 << ($bits - 1); else $datatype['max'] = "18446744073709551615"; break; case 'tinyint': $datatype['type'] = 'int'; if (strstr($elm->COLUMN_TYPE, "unsigned")) { $datatype['min'] = 0; $datatype['max'] = 255; } else { $datatype['min'] = -128; $datatype['max'] = 127; } break; case 'smallint': $datatype['type'] = 'int'; if (strstr($elm->COLUMN_TYPE, "unsigned")) { $datatype['min'] = 0; $datatype['max'] = 65535; } else { $datatype['min'] = -32768; $datatype['max'] = 32767; } break; case 'mediumint': $datatype['type'] = 'int'; if (strstr($elm->COLUMN_TYPE, "unsigned")) { $datatype['min'] = 0; $datatype['max'] = 16777215; } else { $datatype['min'] = -8388608; $datatype['max'] = 8388607; } break; case 'int': $datatype['type'] = 'int'; if (strstr($elm->COLUMN_TYPE, "unsigned")) { $datatype['min'] = 0; $datatype['max'] = 4294967295; } else { $datatype['min'] = -2147483648; $datatype['max'] = 2147483647; } break; case 'bigint': $datatype['type'] = 'int'; if (strstr($elm->COLUMN_TYPE, "unsigned")) { $datatype['min'] = 0; $datatype['max'] = "18446744073709551615"; } else { $datatype['min'] = "-9223372036854775808"; $datatype['max'] = "9223372036854775807"; } break; case 'float': $datatype['type'] = 'float'; if (strstr($elm->COLUMN_TYPE, "unsigned")) { $datatype['min'] = 0; $datatype['max'] = 3.402823466E+38; } else { $datatype['min'] = -3.402823466E+38; $datatype['max'] = 3.402823466E+38; } break; case 'decimal': case 'double': $datatype['type'] = 'float'; if (strstr($elm->COLUMN_TYPE, "unsigned")) { $datatype['min'] = 0; $datatype['max'] = 1.7976931348623157e308; } else { $datatype['min'] = -1.7976931348623157e308; $datatype['max'] = 1.7976931348623157e308; } break; case 'date': $datatype['type'] = 'date'; break; case 'datetime': $datatype['type'] = 'datetime'; break; case 'timestamp': $datatype['type'] = 'datetime'; break; case 'year': $datatype['type'] = 'year'; break; case 'time': $datatype['type'] = 'time'; break; case 'varchar': case 'binary': case 'tinyblob': case 'tinytext': case 'blob': case 'text': case 'mediumblob': case 'mediumtext': case 'longblob': case 'longtext': default: $datatype['type'] = 'string'; $datatype['maxlen'] = $elm->CHARACTER_MAXIMUM_LENGTH; break; case 'enum': $datatype['type'] = 'enum'; $datatypes['enums'] = $this->enumValues($elm->COLUMN_TYPE); $datatype['maxlen'] = $elm->CHARACTER_MAXIMUM_LENGTH; break; case 'set': $datatype['type'] = 'set'; $datatypes['enums'] = $this->enumValues($elm->COLUMN_TYPE); $datatype['maxlen'] = $elm->CHARACTER_MAXIMUM_LENGTH; break; }; return $datatype; } public function schemaByTable($key, $dbname, $table) { $sql = "SELECT * FROM information_schema.columns WHERE TABLE_SCHEMA=? AND TABLE_NAME=?"; $fields = []; foreach (DB::connection($key)->select($sql, [$dbname, $table]) as $elm) $fields[$elm->COLUMN_NAME] = $this->converType($elm); if (count($fields) == 0) { $msg = "找不到数据表 " . $dbname . '.' . $table; throw new Exception($msg, 404); } return $fields; } // 获取表结构 public function getSchema($host, $port, $user, $passwd, $dbname, $table) { $confname = $host . ':' . $port; $conn = [ 'driver' => 'mysql', 'host' => $host, 'port' => $port, 'database' => 'information_schema', 'username' => $user, 'password' => $passwd, 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', ]; Config::set('database.connections.' . $confname, $conn); return $this->schemaByTable($confname, $dbname, $table); } public function CheckTableconfig(&$input, $key, $config, $table_id=null) { $err = []; $raw_fileds = null; if (!isset($input->host) || !isset($input->port) || !isset($input->username) || !isset($input->passwd) || !isset($input->dbname) || !isset($input->tablename)) { $err->host = "数据库配置错误"; } else { try { $ret = DB::connection($key)->table($config->dbname . '.' . $config->tablename) ->where('host', $input->host) ->where('port', $input->port) ->where('username', $input->username) ->where('passwd', $input->passwd) ->where('dbname', $input->dbname) ->where('tablename', $input->tablename)->get(); if ($ret && count($ret) > 0) { // if ($table_id === null || $ret[0]->table_id != $table_id) //return ["errcode" => 400, "errmsg" => "同配置id为" . $ret[0]->table_id . "的数据库配置相同, 不要重复创建配置" ]; } $raw_fileds = $this->getSchema($input->host, $input->port, $input->username, $input->passwd, $input->dbname, $input->tablename); } catch (Exception $e) { } } if (!$raw_fileds) { $err["host"] = "数据库配置错误"; return ["errcode" => 400, "errmsg" => "配置错误", "error" => $err]; } // 检查keyfield; foreach ($raw_fileds as $elm) { if ($elm["extra"] != 'auto_increment') continue; if (!isset($input->keyfield)) break; if (!in_array($elm["name"], $input->keyfield)) break; if (count($input->keyfield) > 1) { $err["keyfield"] = "自增字段不是唯一主键字段"; break; } } $fnames = ["keyfield", "sortfield", "groupfield"]; foreach ($fnames as $fname) { if (!isset($input->$fname)) continue; foreach ($input->$fname as $field) { if (!isset($raw_fileds[$field]) || !isset($input->fields->$field)) { $err[$field] = "包含不存在的字段名"; continue 2; } } } if (isset($input->keyfield) && count($input->keyfield) > 0) { foreach ($input->keyfield as $kfield) { if (isset($input->fields->$kfield) && !$input->fields->$kfield->need) { $err["fields"] = "主键字段" . $kfield . "没有设置为必填"; break; } } } if (!isset($input->fields)) { $err["fields"] = "配置为空"; return ["errcode" => 400, "errmsg" => "配置错误", "error" => $err]; } foreach ($input->fields as $name => &$field) { if (!isset($field->name) || $name != $field->name) { $err["fields"] = "字段". $name . "配置错误"; break; } if (!isset($field->type)) { $err["fields"] = "字段". $name . "类型配置错误"; break; } if (!isset($raw_fileds[$name])) { $err["fields"] = "字段". $name . "不存在"; break; } $raw = $raw_fileds[$name]; if ($raw["extra"] == "auto_increment" && $field->type != TYPE_INCREMENT) { $err["fields"] = "字段". $name . "数据库类型为自增变量,配置类型也需要为自增变量"; break; } // 检查字典 if (isset($field->dict)) { foreach ($field->dict as $dict) { if (!isset($dict->key)) { $err["fileds"] = "字段". $name . "的字典配置错误"; break 2; } } } $comint = true; if ($raw["type"] == 'enum' || $raw["type"] == 'set') { if (!isset($field->dict)) { foreach ($raw["enums"] as $enu) $field->dict[] = ["key"=> $enu, "name"=> $enu]; } else { foreach ($field->dict as $dict) { if (!in_array($dict->key, $raw["enums"])) { $err["fileds"] = "字段". $name . "的字典配置和数据库定义不匹配"; break 2; } } } foreach ($raw["enums"] as $enu) { if (!preg_match('/^[0-9]\+$/', $enu)) { $comint = false; break; } } } // 检查类型是否匹配 if (isset($field->islist) && $field->islist && ($raw["type"] != 'string' && $raw["type"] != 'set')) { $err["fileds"] = "字段". $name . "错误的设置为列表"; break; } switch ($field->type) { case TYPE_INCREMENT: // 自增 case TYPE_INT: // 整数 case TYPE_BOOL: // bool case TYPE_UINT: // 无符号整数 if ($raw["type"] != 'string' && $raw["type"] != 'int' && !(($raw["type"] == 'enum' || $raw["type"] == 'set') && $comint)) $err["fileds"] = "字段". $name . "类型错误, 和数据库的字段类型不匹配"; break; case TYPE_FLOAT: // 浮点数 if ($raw["type"] != 'string' && $raw["type"] != 'float') $err["fileds"] = "字段". $name . "类型错误, 和数据库的字段类型不匹配"; break; case TYPE_TIMESTAMP: // 时间戳 if ($raw["type"] != 'string' && $raw["type"] != 'int') $err["fileds"] = "字段". $name . "类型错误, 和数据库的字段类型不匹配"; break; case TYPE_DATE: // 日期 if ($raw["type"] != 'string' && $raw["type"] != 'datetime' && $raw["type"] != 'date') $err["fileds"] = "字段". $name . "类型错误, 和数据库的字段类型不匹配"; break; case TYPE_DATETIME: // 日期时间 case TYPE_MTIME: // 修改时间 case TYPE_CTIME: // 创建时间 if ($raw["type"] != 'string' && $raw["type"] != 'datetime') $err["fileds"] = "字段". $name . "类型错误, 和数据库的字段类型不匹配"; break; case TYPE_STRING: // 字符串 if ($raw["type"] != 'string' && $raw["type"] != 'enum' && $raw["type"] != 'set') $err["fileds"] = "字段". $name . "类型错误, 和数据库的字段类型不匹配"; break; case TYPE_JSON: // Json $field->ishide = true; case TYPE_TEXT: // 长文本 $field->ishide = true; case TYPE_USER: // 用户名 case TYPE_MUSER: // 修改者 case TYPE_CUSER: // 创建者 case TYPE_COMPLATE: // 自动补全 case TYPE_URL: case TYPE_IMAGE: case TYPE_EMAIL: case TYPE_IP: if ($raw["type"] != 'string') $err["fileds"] = "字段". $name . "类型错误, 和数据库的字段类型不匹配"; break; default: $err["fileds"] = "字段". $name . "未知类型"; break 2; }; } if (count($err) > 0) return ["errcode" => 400, "errmsg" => "配置错误", "error" => $err]; $adv = new AdvConfig(); $err = $adv->checkAdvConfig($input, $raw_fileds); if ($err !== true) return ["errcode" => 400, "errmsg" => "高级配置有错误", "error" => $err]; return true; } // 获取最大的id private function getMaxId($key, $config, $field) { return DB::connection($key)->table($config->dbname . '.' . $config->tablename)->max($field) + 1; } private function checkByJsonSchema($schema, $json) { $validator = new \JsonSchema\Validator(); $validator->check($json, $schema); if (!$validator->isValid()) return ['errcode' => 400, 'errmsg' => 'Bad Request: error data param', "errors" => $validator->getErrors()]; return true; } // 检查输入参数 public function checkData(Request $request, $input, $config, &$data, $raw_fileds, $create, $key) { $err = []; foreach ($config->fields as $fielddef) { $name = $fielddef->name; $limit = (isset($config->adv_config) && $config->adv_config && isset($config->adv_config->rules) && isset($config->adv_config->rules->$name)) ? $config->adv_config->rules->$name : null; switch ($fielddef->type) { case TYPE_MTIME: $data[$name] = date("Y-m-d H:i:s"); continue 2; case TYPE_CTIME: // 创建时间不允许修改 if ($create) $data[$name] = date("Y-m-d H:i:s"); continue 2; case TYPE_MUSER: $data[$name] = $request->user->email; continue 2; case TYPE_CUSER: // 创建人不允许修改 if ($create) $data[$name] = $request->user->email; continue 2; case TYPE_INCREMENT: // 自增量 if ($create) { if ($raw_fileds[$name]["extra"] != 'auto_increment') $data[$name] = $this->getMaxId($key, $config, $name); continue 2; } break; } if (!isset($input->$name)) { if (isset($fielddef->need) && $fielddef->need) $err[$name] = "必填"; continue; } $v = $input->$name; if ($fielddef->islist) { if (!is_array($v)) { $err[$name] = "数组格式错误"; continue; } if (isset($config->adv_config) && isset($config->adv_config->arraySizeLimit) && isset($config->adv_config->arraySizeLimit->$name)) { $count = $config->adv_config->arraySizeLimit->$name; if (isset($count->max) && count($v) > $count->max) $err[$name] = "数组个数超出了最大个数限制,最多为{$count->max}个元素"; if (isset($count->min) && count($v) < $count->min) $err[$name] = "数组个数超出了最小个数限制,最小为{$count->min}个元素"; } foreach ($v as $item) { $r = $this->checkInputFormt($fielddef, $item, $raw_fileds[$name], $limit); if ($r !== true) { $err[$name] = $r; continue 2; } } if ($raw_fileds[$name]["type"] == 'set') { $v = $this->converEnums($v); } else if ($raw_fileds[$name]["type"] == 'enum') { return "和数据库存储类型不匹配"; } else { $v = json_encode($v); if (strlen($v) > $raw_fileds[$name]["maxlen"]) { $err[$name] = "超出了存储长度"; continue; } } } else if ($fielddef->type == TYPE_JSON) { if (!is_array($v) && !is_object($v)) { $err[$name] = "对象格式错误"; continue; } if (isset($config->adv_config) && isset($config->adv_config->jsonSchema) && isset($config->adv_config->jsonSchema->$name)) { $e = $this->checkByJsonSchema($config->adv_config->jsonSchema->$name, $v); if ($e !== true) { $err[$name] = $e; continue; } } $v = json_encode($v); if (strlen($v) > $raw_fileds[$name]["maxlen"]) { $err[$name] = "超出了存储长度"; continue; } } else { $r = $this->checkInputFormt($fielddef, $v, $raw_fileds[$name], $limit); if ($r !== true) { $err[$name] = $r; continue; } } $data[$name] = $v; } if ($config->table_id == 0) $data["last_author"] = $request->user->email; if (count($err) > 0) return ["errcode" => 400, "errmsg" => "参数错误", "error" => $err]; return true; } public function dataDecodeItem(&$data, $names) { foreach ($names as $name) { if (isset($data->$name)) $data->$name = json_decode($data->$name); } } public function dataDecode(&$datas, $fields) { $names = []; foreach ($fields as $field) { if ($field->type == TYPE_JSON || $field->islist) $names[] = $field->name; } if (!is_array($datas)) { $this->dataDecodeItem($datas, $names); } else { foreach ($datas as &$data) $this->dataDecodeItem($data, $names); } } static function checkHistoryDepends($schema) { $type = 0; foreach ($schema['fields'] as $f => $def) { if ($def->type == TYPE_IMAGE) { $type |= 1; } else if ($def->type == TYPE_JSON) { if (isset($schema['adv_config']) && isset($schema['adv_config']->jsonSchema) && isset($schema['adv_config']->jsonSchema->$f)) { $type |= 2; } else { $type |= 4; } } } if ($schema['table_id'] == 1 || $schema['table_id'] == 0) $type |= 4; return $type; } static function checkScriptDepends($schema) { $type = 0; foreach ($schema->fields as $f => $def) { if ($def->type == TYPE_IMAGE) { $type |= 1; } else if ($def->type == TYPE_JSON) { if (isset($schema->adv_config) && isset($schema->adv_config->jsonSchema) && isset($schema->adv_config->jsonSchema->$f)) { $type |= 2; } else { $type |= 4; } } } if ($schema->table_id == 1 || $schema->table_id == 0) $type |= 4; return $type; } static function tableScriptDepends() { return 4; } };