mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-07 02:22:48 +08:00
feat(scheme): 增强 Scheme 与数据库同步机制并添加严格校验
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
namespace base\admin\service\curd;
|
||||
|
||||
use app\admin\service\curd\exceptions\TableException;
|
||||
use app\common\scheme\attribute\Table;
|
||||
use base\common\service\scheme\SchemeToDbService;
|
||||
use ReflectionClass;
|
||||
use think\exception\FileException;
|
||||
use think\facade\Db;
|
||||
use think\helper\Str;
|
||||
@@ -229,8 +232,9 @@ class BuildCurdServiceBase
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->tablePrefix = config('database.connections.mysql.prefix');
|
||||
$this->dbName = config('database.connections.mysql.database');
|
||||
$connection = config('database.default', 'mysql');
|
||||
$this->tablePrefix = config("database.connections.{$connection}.prefix");
|
||||
$this->dbName = config("database.connections.{$connection}.database");
|
||||
$this->dir = __DIR__;
|
||||
$this->rootDir = root_path();
|
||||
|
||||
@@ -258,36 +262,58 @@ class BuildCurdServiceBase
|
||||
public function setTable($table)
|
||||
{
|
||||
$this->table = $table;
|
||||
|
||||
$schemeClass = 'app\\admin\\scheme\\' . Str::studly($this->table);
|
||||
if (!class_exists($schemeClass)) {
|
||||
throw new TableException("未找到 {$schemeClass},请先执行:php think scheme:make -t {$this->table} 或手动创建 Scheme");
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取表列注释
|
||||
$colums = Db::query("SHOW FULL COLUMNS FROM {$this->tablePrefix}{$this->table}");
|
||||
|
||||
foreach ($colums as $vo) {
|
||||
$colum = [
|
||||
'type' => $vo['Type'],
|
||||
'comment' => !empty($vo['Comment']) ? $vo['Comment'] : $vo['Field'],
|
||||
'required' => $vo['Null'] == 'NO' ? true : false,
|
||||
'default' => $vo['Default'],
|
||||
'field' => $vo['Field'],
|
||||
];
|
||||
|
||||
// 格式化列数据
|
||||
$this->buildColum($colum);
|
||||
|
||||
$this->tableColumns[$vo['Field']] = $colum;
|
||||
|
||||
if ($vo['Field'] == 'delete_time') {
|
||||
$this->delete = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表名注释
|
||||
$tableSchema = Db::query("SELECT table_name,table_comment FROM information_schema.TABLES WHERE table_schema = 'ulthon_admin' AND table_name = '{$this->tablePrefix}{$this->table}'");
|
||||
$this->tableComment = (isset($tableSchema[0]['table_comment']) && !empty($tableSchema[0]['table_comment'])) ? $tableSchema[0]['table_comment'] : $this->table;
|
||||
} catch (\Exception $e) {
|
||||
$ref = new ReflectionClass($schemeClass);
|
||||
} catch (\Throwable $e) {
|
||||
throw new TableException($e->getMessage());
|
||||
}
|
||||
|
||||
$tableAttrs = $ref->getAttributes(Table::class);
|
||||
if (empty($tableAttrs)) {
|
||||
throw new TableException("{$schemeClass} 未设置 Table(name: ...) ,无法生成 CURD");
|
||||
}
|
||||
/** @var Table $tableAttr */
|
||||
$tableAttr = $tableAttrs[0]->newInstance();
|
||||
|
||||
$expectedTableName = "{$this->tablePrefix}{$this->table}";
|
||||
if ((string)$tableAttr->name !== $expectedTableName) {
|
||||
throw new TableException("Scheme 表名不匹配:{$schemeClass} => {$tableAttr->name},期望 {$expectedTableName}");
|
||||
}
|
||||
|
||||
$schemeService = new SchemeToDbService();
|
||||
try {
|
||||
$diffs = $schemeService->diff($schemeClass);
|
||||
} catch (\Throwable $e) {
|
||||
throw new TableException($e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($diffs)) {
|
||||
$message = "CURD 生成已拒绝:数据库结构与 Scheme 不一致({$expectedTableName})\n";
|
||||
$message .= implode("\n", array_slice($diffs, 0, 50));
|
||||
$message .= "\n\n请先执行:\n";
|
||||
$message .= "- 从数据库生成 Scheme:php think scheme:make -t {$this->table}\n";
|
||||
$message .= "- 或从 Scheme 同步数据库:php think scheme:sync\n\n";
|
||||
$message .= "详细文档请参考:https://doc.ulthon.com/read/augushong/ulthon_admin/curd-command/zh-cn/2.x.html\n";
|
||||
throw new TableException($message);
|
||||
}
|
||||
|
||||
$columns = $schemeService->getColumnsForCurd($schemeClass);
|
||||
foreach ($columns as $field => $colum) {
|
||||
$this->buildColum($colum);
|
||||
$this->tableColumns[$field] = $colum;
|
||||
if ($field == 'delete_time') {
|
||||
$this->delete = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->tableComment = !empty($tableAttr->comment) ? $tableAttr->comment : $this->table;
|
||||
|
||||
$this->controllerFilename = $this->getTableControllerName($this->table);
|
||||
|
||||
// 初始化默认模型名
|
||||
@@ -342,62 +368,92 @@ class BuildCurdServiceBase
|
||||
if (!isset($this->tableColumns[$foreignKey])) {
|
||||
throw new TableException("主表不存在外键字段:{$foreignKey}");
|
||||
}
|
||||
|
||||
$schemeClass = 'app\\admin\\scheme\\' . Str::studly($relationTable);
|
||||
if (!class_exists($schemeClass)) {
|
||||
throw new TableException("未找到 {$schemeClass},请先执行:php think scheme:make -t {$relationTable} 或手动创建 Scheme");
|
||||
}
|
||||
|
||||
try {
|
||||
$ref = new ReflectionClass($schemeClass);
|
||||
} catch (\Throwable $e) {
|
||||
throw new TableException($e->getMessage());
|
||||
}
|
||||
|
||||
$tableAttrs = $ref->getAttributes(Table::class);
|
||||
if (empty($tableAttrs)) {
|
||||
throw new TableException("{$schemeClass} 未设置 Table(name: ...) ,无法生成 CURD");
|
||||
}
|
||||
/** @var Table $tableAttr */
|
||||
$tableAttr = $tableAttrs[0]->newInstance();
|
||||
|
||||
$expectedTableName = "{$this->tablePrefix}{$relationTable}";
|
||||
if ((string)$tableAttr->name !== $expectedTableName) {
|
||||
throw new TableException("关联 Scheme 表名不匹配:{$schemeClass} => {$tableAttr->name},期望 {$expectedTableName}");
|
||||
}
|
||||
|
||||
$schemeService = new SchemeToDbService();
|
||||
try {
|
||||
$diffs = $schemeService->diff($schemeClass);
|
||||
} catch (\Throwable $e) {
|
||||
throw new TableException($e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($diffs)) {
|
||||
$message = "CURD 生成已拒绝:数据库结构与 Scheme 不一致({$expectedTableName})\n";
|
||||
$message .= implode("\n", array_slice($diffs, 0, 50));
|
||||
$message .= "\n\n请先执行:\n";
|
||||
$message .= "- 从数据库生成 Scheme:php think scheme:make -t {$relationTable}\n";
|
||||
$message .= "- 或从 Scheme 同步数据库:php think scheme:sync\n\n";
|
||||
$message .= "详细文档请参考:https://doc.ulthon.com/read/augushong/ulthon_admin/curd-command/zh-cn/2.x.html\n";
|
||||
throw new TableException($message);
|
||||
}
|
||||
|
||||
if (!empty($modelFilename)) {
|
||||
$modelFilename = str_replace('/', $this->DS, $modelFilename);
|
||||
}
|
||||
try {
|
||||
$colums = Db::query("SHOW FULL COLUMNS FROM {$this->tablePrefix}{$relationTable}");
|
||||
$formatColums = [];
|
||||
$delete = false;
|
||||
if (!empty($bindSelectField) && !in_array($bindSelectField, array_column($colums, 'Field'))) {
|
||||
throw new TableException("关联表{$relationTable}不存在该字段: {$bindSelectField}");
|
||||
}
|
||||
foreach ($colums as $vo) {
|
||||
if (empty($primaryKey) && $vo['Key'] == 'PRI') {
|
||||
$primaryKey = $vo['Field'];
|
||||
}
|
||||
if (!empty($onlyShowFileds) && !in_array($vo['Field'], $onlyShowFileds)) {
|
||||
continue;
|
||||
}
|
||||
$colum = [
|
||||
'type' => $vo['Type'],
|
||||
'comment' => $vo['Comment'],
|
||||
'default' => $vo['Default'],
|
||||
'field' => $vo['Field'],
|
||||
];
|
||||
|
||||
$this->buildColum($colum);
|
||||
|
||||
$formatColums[$vo['Field']] = $colum;
|
||||
if ($vo['Field'] == 'delete_time') {
|
||||
$delete = true;
|
||||
}
|
||||
}
|
||||
|
||||
$modelFilename = empty($modelFilename) ? Str::studly($relationTable) : $modelFilename;
|
||||
$modelArray = explode($this->DS, $modelFilename);
|
||||
$modelName = array_pop($modelArray);
|
||||
|
||||
$relation = [
|
||||
'modelFilename' => $modelFilename,
|
||||
'modelName' => $modelName,
|
||||
'foreignKey' => $foreignKey,
|
||||
'primaryKey' => $primaryKey,
|
||||
'bindSelectField' => $bindSelectField,
|
||||
'delete' => $delete,
|
||||
'tableColumns' => $formatColums,
|
||||
];
|
||||
if (!empty($bindSelectField)) {
|
||||
$relationArray = explode('\\', $modelFilename);
|
||||
$this->tableColumns[$foreignKey]['bindSelectField'] = $bindSelectField;
|
||||
$this->tableColumns[$foreignKey]['bindRelation'] = end($relationArray);
|
||||
}
|
||||
$this->relationArray[$relationTable] = $relation;
|
||||
$this->selectFileds[] = $foreignKey;
|
||||
} catch (\Exception $e) {
|
||||
throw new TableException($e->getMessage());
|
||||
$allColumns = $schemeService->getColumnsForCurd($schemeClass);
|
||||
if (!empty($bindSelectField) && !isset($allColumns[$bindSelectField])) {
|
||||
throw new TableException("关联表{$relationTable}不存在该字段: {$bindSelectField}");
|
||||
}
|
||||
|
||||
if (empty($primaryKey)) {
|
||||
$primaryKey = $schemeService->getPrimaryKey($schemeClass);
|
||||
}
|
||||
if (empty($primaryKey)) {
|
||||
throw new TableException("关联表{$relationTable}未找到主键字段");
|
||||
}
|
||||
|
||||
$formatColums = $schemeService->getColumnsForCurd($schemeClass, $onlyShowFileds);
|
||||
$delete = isset($allColumns['delete_time']);
|
||||
|
||||
foreach ($formatColums as $field => $colum) {
|
||||
$this->buildColum($colum);
|
||||
$formatColums[$field] = $colum;
|
||||
}
|
||||
|
||||
$modelFilename = empty($modelFilename) ? Str::studly($relationTable) : $modelFilename;
|
||||
$modelArray = explode($this->DS, $modelFilename);
|
||||
$modelName = array_pop($modelArray);
|
||||
|
||||
$relation = [
|
||||
'modelFilename' => $modelFilename,
|
||||
'modelName' => $modelName,
|
||||
'foreignKey' => $foreignKey,
|
||||
'primaryKey' => $primaryKey,
|
||||
'bindSelectField' => $bindSelectField,
|
||||
'delete' => $delete,
|
||||
'tableColumns' => $formatColums,
|
||||
];
|
||||
if (!empty($bindSelectField)) {
|
||||
$relationArray = explode('\\', $modelFilename);
|
||||
$this->tableColumns[$foreignKey]['bindSelectField'] = $bindSelectField;
|
||||
$this->tableColumns[$foreignKey]['bindRelation'] = end($relationArray);
|
||||
}
|
||||
$this->relationArray[$relationTable] = $relation;
|
||||
$this->selectFileds[] = $foreignKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user