|
|
@@ -5,18 +5,20 @@ namespace UCore\Commands;
|
|
|
use Illuminate\Console\Command;
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
+use Illuminate\Support\Facades\File;
|
|
|
use UCore\ModelCore;
|
|
|
|
|
|
/**
|
|
|
- * 自动生成Eloquent模型属性注释
|
|
|
+ * 自动生成Eloquent模型属性注释和表创建SQL
|
|
|
*
|
|
|
* 通过分析数据库表结构,自动生成模型属性的PHPDoc注释,
|
|
|
- * 并提供fillable字段自动维护功能。
|
|
|
+ * 并提供fillable字段自动维护功能。同时,为每个表生成创建SQL文件。
|
|
|
*
|
|
|
* 功能特性:
|
|
|
* 1. 自动识别字段类型并生成对应property注释
|
|
|
* 2. 特殊时间字段自动识别为Carbon类型
|
|
|
* 3. 自动维护模型$attrlist属性包含所有字段
|
|
|
+ * 4. 在模块的Databases/createsql目录下为每个表生成创建SQL文件
|
|
|
*
|
|
|
* 使用说明:
|
|
|
* 1. 在模型中添加标记注释:
|
|
|
@@ -34,32 +36,70 @@ class GenerateModelAnnotation extends Command
|
|
|
*
|
|
|
* @var string
|
|
|
*/
|
|
|
- protected $signature = 'ucore:generate-model-annotation';
|
|
|
+ protected $signature = 'ucore:generate-model-annotation
|
|
|
+ {--skip-sql : 跳过生成表创建SQL文件}
|
|
|
+ {--debug-info : 输出详细的调试信息}';
|
|
|
|
|
|
/**
|
|
|
* The console command description.
|
|
|
*
|
|
|
* @var string
|
|
|
*/
|
|
|
- protected $description = '生成模型property注释,使得IDE有模型属性提示!';
|
|
|
+ protected $description = '生成模型property注释和表创建SQL文件';
|
|
|
|
|
|
private $fillable = [];
|
|
|
|
|
|
+ /**
|
|
|
+ * 已处理的表名,用于避免重复生成SQL
|
|
|
+ *
|
|
|
+ * @var array
|
|
|
+ */
|
|
|
+ private $processedTables = [];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 成功处理的模型计数
|
|
|
+ *
|
|
|
+ * @var int
|
|
|
+ */
|
|
|
+ private $successCount = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 跳过的模型计数
|
|
|
+ *
|
|
|
+ * @var int
|
|
|
+ */
|
|
|
+ private $skippedCount = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 失败的模型计数
|
|
|
+ *
|
|
|
+ * @var int
|
|
|
+ */
|
|
|
+ private $failedCount = 0;
|
|
|
+
|
|
|
/**
|
|
|
* 主执行方法
|
|
|
*
|
|
|
- * 扫描指定目录下的模型文件并生成注释
|
|
|
+ * 扫描指定目录下的模型文件并生成注释和表创建SQL
|
|
|
*
|
|
|
* @return void
|
|
|
*/
|
|
|
public function handle()
|
|
|
{
|
|
|
+ // 显示是否跳过SQL生成的选项状态
|
|
|
+ if ($this->option('skip-sql')) {
|
|
|
+ $this->debug('已设置跳过生成表创建SQL文件', 'warning');
|
|
|
+ } else {
|
|
|
+ $this->debug('将为每个表生成创建SQL文件到模块的Databases/createsql目录');
|
|
|
+ }
|
|
|
+
|
|
|
// 扫描核心模型目录
|
|
|
$ucoreModelsDir = app_path('../UCore/Models');
|
|
|
if (is_dir($ucoreModelsDir)) {
|
|
|
+ $this->debug("扫描核心模型目录: $ucoreModelsDir");
|
|
|
$this->call1($ucoreModelsDir, '\UCore\Models\\');
|
|
|
} else {
|
|
|
- $this->warn("UCore Models 目录不存在: $ucoreModelsDir");
|
|
|
+ $this->simpleOutput("UCore Models 目录不存在: $ucoreModelsDir", 'warning');
|
|
|
}
|
|
|
|
|
|
// 扫描模块下的Models目录
|
|
|
@@ -74,17 +114,21 @@ class GenerateModelAnnotation extends Command
|
|
|
$modelsDir = "$modulesPath/$module/Models";
|
|
|
if (is_dir($modelsDir)) {
|
|
|
// 添加模块扫描日志
|
|
|
- $this->info("扫描模块目录: $modelsDir");
|
|
|
+ $this->debug("扫描模块目录: $modelsDir");
|
|
|
// 修复模块命名空间格式
|
|
|
$namespace = "App\\Module\\$module\\Models\\";
|
|
|
// 统一目录分隔符
|
|
|
$namespace = str_replace('/', '\\', $namespace);
|
|
|
// 添加自动加载提示
|
|
|
- $this->warn("请确保已配置composer自动加载: \"App\\Module\\$module\\Models\\\": \"app/Module/$module/Models/\"");
|
|
|
+ $this->debug("请确保已配置composer自动加载: \"App\\Module\\$module\\Models\\\": \"app/Module/$module/Models/\"", 'warning');
|
|
|
$this->call1($modelsDir, $namespace);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 显示处理结果
|
|
|
+ $this->simpleOutput("成功: {$this->successCount} | 跳过: {$this->skippedCount} | 失败: {$this->failedCount}" .
|
|
|
+ (!$this->option('skip-sql') ? " | SQL文件: " . count($this->processedTables) : ""));
|
|
|
}
|
|
|
|
|
|
// ... 保持原有call1、call2、getAnnotation方法不变 ...
|
|
|
@@ -96,7 +140,7 @@ class GenerateModelAnnotation extends Command
|
|
|
*/
|
|
|
public function call1($dir, $ns)
|
|
|
{
|
|
|
- $this->info("扫描目录: $dir");
|
|
|
+ $this->debug("扫描目录: $dir");
|
|
|
$list = scandir($dir);
|
|
|
foreach ($list as $item) {
|
|
|
if ($item === '.' || $item === '..') {
|
|
|
@@ -107,6 +151,12 @@ class GenerateModelAnnotation extends Command
|
|
|
|
|
|
// 递归处理子目录
|
|
|
if (is_dir($fullPath)) {
|
|
|
+ // 跳过Validator目录
|
|
|
+ if (basename($fullPath) === 'Validators' || basename($fullPath) === 'Validator') {
|
|
|
+ $this->debug("跳过Validator目录: $fullPath", 'warning');
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
$this->call1($fullPath, $ns . $item . '\\');
|
|
|
continue;
|
|
|
}
|
|
|
@@ -136,20 +186,23 @@ class GenerateModelAnnotation extends Command
|
|
|
// 检查类是否是抽象类
|
|
|
$reflectionClass = new \ReflectionClass($modelClass);
|
|
|
if ($reflectionClass->isAbstract()) {
|
|
|
- $this->output->warning(" model $modelClass 是抽象类,跳过");
|
|
|
+ $this->debug(" model $modelClass 是抽象类,跳过", 'warning');
|
|
|
+ $this->skippedCount++;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检查类是否是模型类
|
|
|
if (!$reflectionClass->isSubclassOf(\Illuminate\Database\Eloquent\Model::class)) {
|
|
|
- $this->output->warning(" model $modelClass 不是模型类,跳过");
|
|
|
+ $this->debug(" model $modelClass 不是模型类,跳过", 'warning');
|
|
|
+ $this->skippedCount++;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检查构造函数是否需要参数
|
|
|
$constructor = $reflectionClass->getConstructor();
|
|
|
if ($constructor && $constructor->getNumberOfRequiredParameters() > 0) {
|
|
|
- $this->output->warning(" model $modelClass 需要构造函数参数,跳过");
|
|
|
+ $this->debug(" model $modelClass 需要构造函数参数,跳过", 'warning');
|
|
|
+ $this->skippedCount++;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -159,40 +212,50 @@ class GenerateModelAnnotation extends Command
|
|
|
try {
|
|
|
$model = new $modelClass();
|
|
|
} catch (\Throwable $e) {
|
|
|
- $this->output->error(" model $modelClass 实例化失败: " . $e->getMessage());
|
|
|
+ $this->output->writeln("<fg=red>失败: " . $e->getMessage() . "</>");
|
|
|
+ $this->failedCount++;
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
if($model instanceof Model){
|
|
|
$co = $model->getConnection();
|
|
|
$tTablePrefix = $co->getTablePrefix();
|
|
|
- $table =$tTablePrefix.$model->getTable();
|
|
|
- $this->info("table $table ");
|
|
|
+ $table = $tTablePrefix.$model->getTable();
|
|
|
+ $this->output->write("表: $table ... ");
|
|
|
+
|
|
|
+ // 提取模块名称
|
|
|
+ $moduleName = $this->extractModuleNameFromNamespace($modelClass);
|
|
|
+
|
|
|
+ // 如果不跳过SQL生成且找到了模块名称,则生成表的创建SQL
|
|
|
+ if (!$this->option('skip-sql') && $moduleName) {
|
|
|
+ $this->generateTableSQLFile($table, $moduleName, $co, $modelClass);
|
|
|
+ }
|
|
|
|
|
|
$annotation = $this->getAnnotation($table,$co);
|
|
|
|
|
|
$string = file_get_contents($file);
|
|
|
|
|
|
// 输出文件内容的前200个字符,帮助调试
|
|
|
- $this->output->info("文件内容前200字符: " . substr($string, 0, 200) . "...");
|
|
|
+ $this->debug("文件内容前200字符: " . substr($string, 0, 200) . "...");
|
|
|
|
|
|
// 检查文件是否包含 field start/end 标识符
|
|
|
$hasFieldStart = strpos($string, 'field start') !== false;
|
|
|
$hasFieldEnd = strpos($string, 'field end') !== false;
|
|
|
|
|
|
- $this->output->info("包含 field start: " . ($hasFieldStart ? "是" : "否"));
|
|
|
- $this->output->info("包含 field end: " . ($hasFieldEnd ? "是" : "否"));
|
|
|
+ $this->debug("包含 field start: " . ($hasFieldStart ? "是" : "否"));
|
|
|
+ $this->debug("包含 field end: " . ($hasFieldEnd ? "是" : "否"));
|
|
|
|
|
|
// 使用正则表达式匹配 field start/end 标识符
|
|
|
$pattern = '/field\s+start[\s\S]+?field\s+end/';
|
|
|
- $this->output->info("使用正则表达式匹配 field start/end: " . $pattern);
|
|
|
+ $this->debug("使用正则表达式匹配 field start/end: " . $pattern);
|
|
|
|
|
|
// 尝试匹配
|
|
|
$matches = [];
|
|
|
$matchResult = preg_match($pattern, $string, $matches);
|
|
|
- $this->output->info("正则匹配结果: " . ($matchResult ? "成功" : "失败"));
|
|
|
+ $this->debug("正则匹配结果: " . ($matchResult ? "成功" : "失败"));
|
|
|
if ($matchResult) {
|
|
|
- $this->output->info("匹配到的内容长度: " . strlen($matches[0]));
|
|
|
- $this->output->info("匹配到的内容前50字符: " . substr($matches[0], 0, 50) . "...");
|
|
|
+ $this->debug("匹配到的内容长度: " . strlen($matches[0]));
|
|
|
+ $this->debug("匹配到的内容前50字符: " . substr($matches[0], 0, 50) . "...");
|
|
|
}
|
|
|
|
|
|
// 替换 field start/end 标识符
|
|
|
@@ -201,7 +264,7 @@ class GenerateModelAnnotation extends Command
|
|
|
|
|
|
// 强制替换成功
|
|
|
$replaced = true;
|
|
|
- $this->output->info("field start/end 替换结果: 成功");
|
|
|
+ $this->debug("field start/end 替换结果: 成功");
|
|
|
|
|
|
// 过滤系统默认字段
|
|
|
$filteredFillable = array_filter($this->fillable, function($field) {
|
|
|
@@ -219,20 +282,20 @@ class GenerateModelAnnotation extends Command
|
|
|
$hasAttrlistStart = strpos($string, 'attrlist start') !== false;
|
|
|
$hasAttrlistEnd = strpos($string, 'attrlist end') !== false;
|
|
|
|
|
|
- $this->output->info("包含 attrlist start: " . ($hasAttrlistStart ? "是" : "否"));
|
|
|
- $this->output->info("包含 attrlist end: " . ($hasAttrlistEnd ? "是" : "否"));
|
|
|
+ $this->debug("包含 attrlist start: " . ($hasAttrlistStart ? "是" : "否"));
|
|
|
+ $this->debug("包含 attrlist end: " . ($hasAttrlistEnd ? "是" : "否"));
|
|
|
|
|
|
// 使用正则表达式匹配 attrlist start/end 标识符
|
|
|
$pattern2 = '/\/\/\s*attrlist\s+start[\s\S]+?\/\/\s*attrlist\s+end/';
|
|
|
- $this->output->info("使用正则表达式匹配 attrlist start/end: " . $pattern2);
|
|
|
+ $this->debug("使用正则表达式匹配 attrlist start/end: " . $pattern2);
|
|
|
|
|
|
// 尝试匹配
|
|
|
$matches2 = [];
|
|
|
$matchResult2 = preg_match($pattern2, $result, $matches2);
|
|
|
- $this->output->info("正则匹配结果: " . ($matchResult2 ? "成功" : "失败"));
|
|
|
+ $this->debug("正则匹配结果: " . ($matchResult2 ? "成功" : "失败"));
|
|
|
if ($matchResult2) {
|
|
|
- $this->output->info("匹配到的内容长度: " . strlen($matches2[0]));
|
|
|
- $this->output->info("匹配到的内容前50字符: " . substr($matches2[0], 0, 50) . "...");
|
|
|
+ $this->debug("匹配到的内容长度: " . strlen($matches2[0]));
|
|
|
+ $this->debug("匹配到的内容前50字符: " . substr($matches2[0], 0, 50) . "...");
|
|
|
}
|
|
|
|
|
|
// 替换 attrlist start/end 标识符
|
|
|
@@ -241,16 +304,84 @@ class GenerateModelAnnotation extends Command
|
|
|
|
|
|
// 强制替换成功
|
|
|
$replaced2 = true;
|
|
|
- $this->output->info("attrlist start/end 替换结果: 成功");
|
|
|
+ $this->debug("attrlist start/end 替换结果: 成功");
|
|
|
|
|
|
// 强制写入文件
|
|
|
- $this->output->info(" model $modelClass file :$file annotation 成功 ");
|
|
|
file_put_contents($file,$result);
|
|
|
+ $this->successCount++;
|
|
|
+ $this->output->writeln("<fg=green>完成</>");
|
|
|
}else{
|
|
|
- $this->output->warning(" model $modelClass 不是继承 ModelBase");
|
|
|
+ $this->output->writeln("<fg=yellow>跳过: 不是继承 ModelBase</>");
|
|
|
+ $this->skippedCount++;
|
|
|
}
|
|
|
}else{
|
|
|
- $this->output->warning(" model $model 不存在");
|
|
|
+ $this->debug(" model $model 不存在", 'warning');
|
|
|
+ $this->skippedCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从模型命名空间中提取模块名称
|
|
|
+ *
|
|
|
+ * @param string $modelClass 模型类名
|
|
|
+ * @return string|null 模块名称,如果不是模块内的模型则返回null
|
|
|
+ */
|
|
|
+ protected function extractModuleNameFromNamespace($modelClass)
|
|
|
+ {
|
|
|
+ // 匹配 App\Module\{ModuleName}\Models 格式的命名空间
|
|
|
+ if (preg_match('/^App\\\\Module\\\\([^\\\\]+)\\\\Models/', $modelClass, $matches)) {
|
|
|
+ return $matches[1];
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 输出调试信息
|
|
|
+ *
|
|
|
+ * @param string $message 调试信息
|
|
|
+ * @param string $type 信息类型 (info, warning, error)
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ protected function debug($message, $type = 'info')
|
|
|
+ {
|
|
|
+ if ($this->option('debug-info')) {
|
|
|
+ switch ($type) {
|
|
|
+ case 'warning':
|
|
|
+ $this->output->warning($message);
|
|
|
+ break;
|
|
|
+ case 'error':
|
|
|
+ $this->output->error($message);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ $this->output->info($message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 输出简洁信息
|
|
|
+ *
|
|
|
+ * @param string $message 信息内容
|
|
|
+ * @param string $type 信息类型 (info, warning, error)
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ protected function simpleOutput($message, $type = 'info')
|
|
|
+ {
|
|
|
+ // 检查消息是否为空
|
|
|
+ if (empty(trim($message))) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch ($type) {
|
|
|
+ case 'warning':
|
|
|
+ $this->output->writeln("<fg=yellow>{$message}</>");
|
|
|
+ break;
|
|
|
+ case 'error':
|
|
|
+ $this->output->writeln("<fg=red>{$message}</>");
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ $this->output->writeln($message);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -304,4 +435,70 @@ class GenerateModelAnnotation extends Command
|
|
|
}
|
|
|
return $type;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取表的创建SQL语句
|
|
|
+ *
|
|
|
+ * @param string $tableName 表名
|
|
|
+ * @param \Illuminate\Database\Connection $connection 数据库连接
|
|
|
+ * @return string 创建表的SQL语句
|
|
|
+ */
|
|
|
+ protected function getCreateTableSQL($tableName, $connection)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $result = $connection->select("SHOW CREATE TABLE `{$tableName}`");
|
|
|
+ if (!empty($result) && isset($result[0]->{'Create Table'})) {
|
|
|
+ return $result[0]->{'Create Table'};
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $this->output->writeln("<fg=red>获取表 {$tableName} 的创建SQL失败: " . $e->getMessage() . "</>");
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 为表生成创建SQL文件
|
|
|
+ *
|
|
|
+ * @param string $tableName 表名
|
|
|
+ * @param string $moduleName 模块名
|
|
|
+ * @param \Illuminate\Database\Connection $connection 数据库连接
|
|
|
+ * @param string $modelClass 模型类名
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ protected function generateTableSQLFile($tableName, $moduleName, $connection, $modelClass = null)
|
|
|
+ {
|
|
|
+ // 如果已经处理过该表,则跳过
|
|
|
+ if (in_array($tableName, $this->processedTables)) {
|
|
|
+ $this->debug("表 {$tableName} 已处理过,跳过生成SQL");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 标记该表已处理
|
|
|
+ $this->processedTables[] = $tableName;
|
|
|
+
|
|
|
+ // 获取表的创建SQL
|
|
|
+ $createSQL = $this->getCreateTableSQL($tableName, $connection);
|
|
|
+ if (empty($createSQL)) {
|
|
|
+ $modelInfo = $modelClass ? " (Model: {$modelClass})" : "";
|
|
|
+ $this->output->writeln("<fg=yellow>SQL生成失败{$modelInfo}</>");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建模块的Databases/createsql目录
|
|
|
+ $sqlDir = app_path("Module/{$moduleName}/Databases/createsql");
|
|
|
+ if (!File::exists($sqlDir)) {
|
|
|
+ $this->debug("创建目录: {$sqlDir}");
|
|
|
+ File::makeDirectory($sqlDir, 0755, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成SQL文件
|
|
|
+ $sqlFile = "{$sqlDir}/{$tableName}.sql";
|
|
|
+ $sqlContent = "-- 表 {$tableName} 的创建SQL\n-- 自动生成于 " . date('Y-m-d H:i:s') . "\n\n";
|
|
|
+ $sqlContent .= "DROP TABLE IF EXISTS `{$tableName}`;\n";
|
|
|
+ $sqlContent .= $createSQL . ";\n";
|
|
|
+
|
|
|
+ File::put($sqlFile, $sqlContent);
|
|
|
+ $this->debug("已生成SQL: {$sqlFile}");
|
|
|
+ }
|
|
|
}
|