初始化本地仓库
This commit is contained in:
@@ -0,0 +1,690 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator;
|
||||
|
||||
use core\generator\factory\GeneratorFactory;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
use core\generator\utils\PathResolver;
|
||||
use support\Log;
|
||||
|
||||
/**
|
||||
* 代码生成器引擎
|
||||
* 负责协调各个生成器,处理整体生成流程
|
||||
*/
|
||||
class GeneratorEngine
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var GeneratorFactory 生成器工厂
|
||||
*/
|
||||
private GeneratorFactory $generatorFactory;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* @var PathResolver 路径解析器
|
||||
*/
|
||||
private PathResolver $pathResolver;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array|object $config 配置信息(支持数组或对象)
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
// 解析配置数据
|
||||
$this->config = $this->parseConfig($config);
|
||||
|
||||
// 合并默认场景配置
|
||||
$this->mergeDefaultScenes();
|
||||
|
||||
$this->generatorFactory = new GeneratorFactory();
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
$this->pathResolver = new PathResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析配置数据
|
||||
* @param array|object $config 配置信息
|
||||
* @return array 解析后的配置数组
|
||||
*/
|
||||
private function parseConfig($config): array
|
||||
{
|
||||
if (is_object($config)) {
|
||||
// 如果是对象,转换为数组
|
||||
$parsedConfig = (array) $config;
|
||||
} elseif (is_array($config)) {
|
||||
// 如果是数组,直接使用
|
||||
$parsedConfig = $config;
|
||||
} else {
|
||||
// 其他类型,返回空数组
|
||||
return [];
|
||||
}
|
||||
|
||||
// 如果有 basic 字段,使用其中的值
|
||||
if (isset($parsedConfig['basic']) && is_array($parsedConfig['basic'])) {
|
||||
$basic = $parsedConfig['basic'];
|
||||
if (isset($basic['table_name']) && !empty($basic['table_name'])) {
|
||||
$parsedConfig['package_name'] = $basic['table_name'];
|
||||
} elseif (isset($basic['module_name']) && !empty($basic['module_name'])) {
|
||||
$parsedConfig['package_name'] = $basic['module_name'];
|
||||
}
|
||||
if (isset($basic['class_name']) && !empty($basic['class_name'])) {
|
||||
$parsedConfig['class_name'] = $basic['class_name'];
|
||||
} elseif (isset($basic['table_name']) && !empty($basic['table_name'])) {
|
||||
$parsedConfig['class_name'] = $basic['table_name'];
|
||||
}
|
||||
if (isset($basic['table_comment']) && !empty($basic['table_comment'])) {
|
||||
$parsedConfig['table_comment'] = $basic['table_comment'];
|
||||
}
|
||||
if (isset($basic['class_name_comment']) && !empty($basic['class_name_comment'])) {
|
||||
$parsedConfig['class_name_comment'] = $basic['class_name_comment'];
|
||||
}
|
||||
if (isset($basic['plugin_name']) && !empty($basic['plugin_name'])) {
|
||||
$parsedConfig['plugin_name'] = $basic['plugin_name'];
|
||||
$parsedConfig['namespace'] = $basic['plugin_name'];
|
||||
}
|
||||
}
|
||||
|
||||
// 如果顶层有 table_name 字段,使用它作为 package_name
|
||||
if (isset($parsedConfig['table_name']) && !empty($parsedConfig['table_name']) && !isset($parsedConfig['package_name'])) {
|
||||
$parsedConfig['package_name'] = $parsedConfig['table_name'];
|
||||
} elseif (isset($parsedConfig['module_name']) && !empty($parsedConfig['module_name']) && !isset($parsedConfig['package_name'])) {
|
||||
$parsedConfig['package_name'] = $parsedConfig['module_name'];
|
||||
}
|
||||
|
||||
// 如果顶层有 table_name 字段,使用它作为 class_name(如果没有设置 class_name)
|
||||
if (isset($parsedConfig['table_name']) && !empty($parsedConfig['table_name']) && !isset($parsedConfig['class_name'])) {
|
||||
$parsedConfig['class_name'] = $parsedConfig['table_name'];
|
||||
}
|
||||
|
||||
// 处理 template 参数:如果没有传入 template,但传入了 plugin_name,则使用 plugin_name 作为 template 值
|
||||
if (!isset($parsedConfig['template']) && isset($parsedConfig['plugin_name']) && !empty($parsedConfig['plugin_name'])) {
|
||||
$parsedConfig['template'] = $parsedConfig['plugin_name'];
|
||||
$parsedConfig['namespace'] = $parsedConfig['plugin_name'];
|
||||
} elseif (!isset($parsedConfig['template'])) {
|
||||
// 如果都没有传入,则使用默认值 app
|
||||
$parsedConfig['template'] = 'app';
|
||||
} elseif ($parsedConfig['template'] !== 'app') {
|
||||
// 如果传入了 template 且不是 app,则使用 template 作为 namespace
|
||||
$parsedConfig['namespace'] = $parsedConfig['template'];
|
||||
}
|
||||
// 如果传入了 template,则直接使用它,优先级高于 plugin_name
|
||||
|
||||
// 确保 class_name 是大驼峰格式
|
||||
if (isset($parsedConfig['class_name']) && !empty($parsedConfig['class_name'])) {
|
||||
$parsedConfig['class_name'] = str_replace(' ', '', ucwords(str_replace('_', ' ', $parsedConfig['class_name'])));
|
||||
}
|
||||
|
||||
// 验证必须参数
|
||||
$this->validateRequiredParams($parsedConfig);
|
||||
|
||||
return $parsedConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证必须参数
|
||||
* @param array $config 配置数组
|
||||
* @throws Exception 如果缺少必须参数
|
||||
*/
|
||||
private function validateRequiredParams(array &$config): void
|
||||
{
|
||||
// 验证 package_name(模块名)
|
||||
if (!isset($config['package_name']) || empty($config['package_name'])) {
|
||||
throw new \Exception('Missing required parameter: package_name (table_name)');
|
||||
}
|
||||
|
||||
// 验证 class_name(类名)
|
||||
if (!isset($config['class_name']) || empty($config['class_name'])) {
|
||||
throw new \Exception('Missing required parameter: class_name');
|
||||
}
|
||||
|
||||
// 验证 template
|
||||
if (!isset($config['template']) || empty($config['template'])) {
|
||||
$config['template'] = 'app';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并默认场景配置
|
||||
* @return void
|
||||
*/
|
||||
private function mergeDefaultScenes(): void
|
||||
{
|
||||
// 如果未设置场景类型,默认同时生成前端和后端代码
|
||||
if (!isset($this->config['scene_types']) && !isset($this->config['scene_type'])) {
|
||||
$this->config['scene_types'] = ['backend', 'admin'];
|
||||
}
|
||||
|
||||
// 如果未设置文件类型映射,设置默认值
|
||||
if (!isset($this->config['file_types_map'])) {
|
||||
$this->config['file_types_map'] = [
|
||||
'backend' => ['controller', 'model', 'service', 'dao', 'validate', 'request_form', 'request_query', 'response'],
|
||||
'admin' => ['api', 'api_model', 'view', 'view_schema', 'lang'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览代码生成
|
||||
* @return array 预览结果
|
||||
*/
|
||||
public function preview(): array
|
||||
{
|
||||
$result = [];
|
||||
$sceneTypes = $this->config['scene_types'] ?? [$this->config['scene_type'] ?? 'backend'];
|
||||
$fileTypesMap = $this->config['file_types_map'] ?? [
|
||||
'backend' => ['controller', 'model', 'service', 'dao', 'validate', 'request_form', 'request_query', 'response'],
|
||||
'admin' => ['api', 'api_model', 'view', 'view_schema', 'lang'],
|
||||
];
|
||||
|
||||
// 生成每种场景类型
|
||||
foreach ($sceneTypes as $sceneType) {
|
||||
try {
|
||||
// 获取场景生成器
|
||||
$sceneGenerator = $this->generatorFactory->createSceneGenerator($sceneType, $this->config);
|
||||
|
||||
// 获取当前场景的文件类型
|
||||
$fileTypes = $fileTypesMap[$sceneType] ?? $this->config['file_types'] ?? ['controller', 'model', 'service', 'dao', 'validate', 'request_form', 'request_query','response'];
|
||||
|
||||
// 生成每种文件类型
|
||||
foreach ($fileTypes as $fileType) {
|
||||
try {
|
||||
// 获取文件类型生成器
|
||||
$fileGenerator = $this->generatorFactory->createFileGenerator($fileType, $this->config);
|
||||
|
||||
// 生成文件内容
|
||||
$content = $fileGenerator->generateContent();
|
||||
|
||||
// 获取文件扩展名
|
||||
$extension = $fileGenerator->getFileExtension();
|
||||
|
||||
// 生成文件路径
|
||||
$filePath = $sceneGenerator->generateFilePath($fileType, $extension);
|
||||
|
||||
// 添加到结果中
|
||||
$result[] = [
|
||||
'name' => basename($filePath),
|
||||
'type' => $this->getFileType($filePath),
|
||||
'content' => $content,
|
||||
'file_dir' => $this->getRelativePath(dirname($filePath), $sceneType),
|
||||
'scene_type' => $sceneType,
|
||||
];
|
||||
|
||||
// 处理 DTO 特殊情况:同时预览 QueryRequest
|
||||
// if ($fileType === 'request_query' && method_exists($fileGenerator, 'getQueryRequestContent')) {
|
||||
// try {
|
||||
// // 获取 QueryRequest 内容
|
||||
// $queryContent = $fileGenerator->getQueryRequestContent();
|
||||
//
|
||||
// if (!empty($queryContent)) {
|
||||
// // 生成 QueryRequest 文件路径(与 FormRequest 在同一目录)
|
||||
// $formDir = dirname($filePath);
|
||||
// $fileName = basename($filePath);
|
||||
// $queryFileName = str_replace('FormRequest', 'QueryRequest', $fileName);
|
||||
// $queryFilePath = $formDir . DS . $queryFileName;
|
||||
//
|
||||
// // 添加到结果中
|
||||
// $result[] = [
|
||||
// 'name' => basename($queryFilePath),
|
||||
// 'type' => $this->getFileType($queryFilePath),
|
||||
// 'content' => $queryContent,
|
||||
// 'file_dir' => $this->getRelativePath(dirname($queryFilePath), $sceneType),
|
||||
// 'scene_type' => $sceneType,
|
||||
// ];
|
||||
// }
|
||||
// } catch (\Exception $e) {
|
||||
// // 记录错误但继续执行
|
||||
// $result[] = [
|
||||
// 'name' => 'request_query',
|
||||
// 'type' => 'error',
|
||||
// 'content' => 'Error generating QueryRequest: ' . $e->getMessage(),
|
||||
// 'file_dir' => '',
|
||||
// 'scene_type' => $sceneType,
|
||||
// ];
|
||||
// }
|
||||
// }
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但继续执行
|
||||
$result[] = [
|
||||
'name' => $fileType,
|
||||
'type' => 'error',
|
||||
'content' => 'Error: ' . $e->getMessage(),
|
||||
'file_dir' => '',
|
||||
'scene_type' => $sceneType,
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录场景错误
|
||||
$result[] = [
|
||||
'name' => $sceneType,
|
||||
'type' => 'error',
|
||||
'content' => 'Scene Error: ' . $e->getMessage(),
|
||||
'file_dir' => '',
|
||||
'scene_type' => $sceneType,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署代码生成
|
||||
* @return array 部署结果
|
||||
*/
|
||||
public function deploy(): array
|
||||
{
|
||||
$result = [];
|
||||
$sceneTypes = $this->config['scene_types'] ?? [$this->config['scene_type'] ?? 'backend'];
|
||||
$fileTypesMap = $this->config['file_types_map'] ?? [
|
||||
'backend' => ['controller', 'model', 'service', 'dao', 'validate', 'response', 'request_form','request_query'],
|
||||
'admin' => ['api', 'api_model', 'view', 'view_schema', 'lang'],
|
||||
];
|
||||
|
||||
// 生成每种场景类型
|
||||
foreach ($sceneTypes as $sceneType) {
|
||||
try {
|
||||
// 获取场景生成器
|
||||
$sceneGenerator = $this->generatorFactory->createSceneGenerator($sceneType, $this->config);
|
||||
|
||||
// 获取当前场景的文件类型
|
||||
$fileTypes = $fileTypesMap[$sceneType] ?? $this->config['file_types'] ?? ['controller', 'model', 'service', 'dao', 'validate', 'schema', 'dto'];
|
||||
|
||||
// 生成每种文件类型
|
||||
foreach ($fileTypes as $fileType) {
|
||||
try {
|
||||
// 获取文件类型生成器
|
||||
$fileGenerator = $this->generatorFactory->createFileGenerator($fileType, $this->config);
|
||||
|
||||
// 生成文件内容
|
||||
$content = $fileGenerator->generateContent();
|
||||
|
||||
// 验证内容是否为空
|
||||
if (empty($content)) {
|
||||
throw new \Exception('Generated content is empty for file type: ' . $fileType);
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
$extension = $fileGenerator->getFileExtension();
|
||||
|
||||
// 生成文件路径
|
||||
$filePath = $sceneGenerator->generateFilePath($fileType, $extension);
|
||||
|
||||
// 确保目录存在
|
||||
$dirPath = dirname($filePath);
|
||||
if (!is_dir($dirPath)) {
|
||||
mkdir($dirPath, 0777, true);
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
$written = file_put_contents($filePath, $content);
|
||||
if ($written === false) {
|
||||
throw new \Exception('Failed to write file: ' . $filePath);
|
||||
}
|
||||
|
||||
// 添加到结果中
|
||||
$result[] = [
|
||||
'file_type' => $fileType,
|
||||
'file_path' => $filePath,
|
||||
'status' => 'success',
|
||||
'scene_type' => $sceneType,
|
||||
];
|
||||
|
||||
// 处理 DTO 特殊情况:同时生成 QueryRequest
|
||||
// if ($fileType === 'dto' && method_exists($fileGenerator, 'getQueryRequestContent')) {
|
||||
// try {
|
||||
// // 获取 QueryRequest 内容
|
||||
// $queryContent = $fileGenerator->getQueryRequestContent();
|
||||
//
|
||||
// if (!empty($queryContent)) {
|
||||
// // 生成 QueryRequest 文件路径(与 FormRequest 在同一目录)
|
||||
// $formDir = dirname($filePath);
|
||||
// $fileName = basename($filePath);
|
||||
// $queryFileName = str_replace('FormRequest', 'QueryRequest', $fileName);
|
||||
// $queryFilePath = $formDir . DS . $queryFileName;
|
||||
//
|
||||
// // 确保目录存在
|
||||
// if (!is_dir($formDir)) {
|
||||
// mkdir($formDir, 0777, true);
|
||||
// }
|
||||
//
|
||||
// // 写入文件
|
||||
// $queryWritten = file_put_contents($queryFilePath, $queryContent);
|
||||
// if ($queryWritten !== false) {
|
||||
// $result[] = [
|
||||
// 'file_type' => 'dto_query',
|
||||
// 'file_path' => $queryFilePath,
|
||||
// 'status' => 'success',
|
||||
// 'scene_type' => $sceneType,
|
||||
// ];
|
||||
// }
|
||||
// }
|
||||
// } catch (\Exception $e) {
|
||||
// // 记录错误但继续执行
|
||||
// $result[] = [
|
||||
// 'file_type' => 'dto_query',
|
||||
// 'status' => 'error',
|
||||
// 'message' => 'Error generating QueryRequest: ' . $e->getMessage(),
|
||||
// 'scene_type' => $sceneType,
|
||||
// ];
|
||||
// }
|
||||
// }
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但继续执行
|
||||
$result[] = [
|
||||
'file_type' => $fileType,
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage(),
|
||||
'scene_type' => $sceneType,
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录场景错误
|
||||
$result[] = [
|
||||
'file_type' => $sceneType,
|
||||
'status' => 'error',
|
||||
'message' => 'Scene Error: ' . $e->getMessage(),
|
||||
'scene_type' => $sceneType,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件类型
|
||||
* @param string $filePath 文件路径
|
||||
* @return string 文件类型
|
||||
*/
|
||||
private function getFileType(string $filePath): string
|
||||
{
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
return $extension ?: 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取相对路径
|
||||
* @param string $fullPath 完整路径
|
||||
* @param string $sceneType 场景类型
|
||||
* @return string 相对路径
|
||||
*/
|
||||
private function getRelativePath(string $fullPath, string $sceneType): string
|
||||
{
|
||||
if ($sceneType === 'admin') {
|
||||
// 前端场景
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
// 无论是 app 模式还是插件模式,前端代码都统一生成在 frontend/admin 目录下
|
||||
$frontendRoot = dirname(base_path()) . DS . 'frontend' . DS . 'admin';
|
||||
$path = 'frontend/admin' . str_replace($frontendRoot, '', $fullPath);
|
||||
} else {
|
||||
// 后端场景,使用当前后端根目录名称作为前缀
|
||||
$backendRootName = basename(base_path());
|
||||
$path = $backendRootName . str_replace(base_path(), '', $fullPath);
|
||||
}
|
||||
|
||||
// 调整斜杠为 `/`
|
||||
$path = str_replace('\\', '/', $path);
|
||||
// 确保路径末尾有 `/`
|
||||
if (!empty($path) && substr($path, -1) !== '/') {
|
||||
$path .= '/';
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件下载包
|
||||
*
|
||||
* @return array 下载包信息
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function download(): array
|
||||
{
|
||||
// 验证模块命名
|
||||
$this->validateModuleName($this->config);
|
||||
|
||||
$result = [];
|
||||
$sceneTypes = $this->config['scene_types'] ?? [$this->config['scene_type'] ?? 'backend'];
|
||||
$fileTypesMap = $this->config['file_types_map'] ?? [
|
||||
'backend' => ['controller', 'model', 'service', 'dao', 'validate', 'request_form', 'request_query','response'],
|
||||
'admin' => ['api', 'api_model', 'view', 'view_schema', 'lang'],
|
||||
];
|
||||
|
||||
// 创建临时目录
|
||||
$tempDir = sys_get_temp_dir() . DS . 'generator_' . uniqid();
|
||||
if (!mkdir($tempDir, 0777, true)) {
|
||||
throw new \Exception('Failed to create temporary directory');
|
||||
}
|
||||
|
||||
// 生成每种场景类型的文件
|
||||
foreach ($sceneTypes as $sceneType) {
|
||||
try {
|
||||
// 获取场景生成器
|
||||
$sceneGenerator = $this->generatorFactory->createSceneGenerator($sceneType, $this->config);
|
||||
|
||||
// 获取当前场景的文件类型
|
||||
$fileTypes = $fileTypesMap[$sceneType] ?? $this->config['file_types'] ?? ['controller', 'model', 'service', 'dao', 'validate', 'schema', 'dto'];
|
||||
|
||||
// 生成每种文件类型
|
||||
foreach ($fileTypes as $fileType) {
|
||||
try {
|
||||
// 获取文件类型生成器
|
||||
$fileGenerator = $this->generatorFactory->createFileGenerator($fileType, $this->config);
|
||||
|
||||
// 生成文件内容
|
||||
$content = $fileGenerator->generateContent();
|
||||
|
||||
// 验证内容是否为空
|
||||
if (empty($content)) {
|
||||
throw new \Exception('Generated content is empty for file type: ' . $fileType);
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
$extension = $fileGenerator->getFileExtension();
|
||||
|
||||
// 生成文件路径
|
||||
$filePath = $sceneGenerator->generateFilePath($fileType, $extension);
|
||||
|
||||
// 构建临时文件路径
|
||||
$relativePath = str_replace(dirname(base_path()), '', $filePath);
|
||||
$relativePath = ltrim($relativePath, DS);
|
||||
$tempFilePath = $tempDir . DS . $relativePath;
|
||||
|
||||
// 确保目录存在
|
||||
$tempDirPath = dirname($tempFilePath);
|
||||
if (!is_dir($tempDirPath)) {
|
||||
mkdir($tempDirPath, 0777, true);
|
||||
}
|
||||
|
||||
// 写入临时文件
|
||||
if (file_put_contents($tempFilePath, $content) === false) {
|
||||
throw new \Exception('Failed to write temporary file: ' . $tempFilePath);
|
||||
}
|
||||
|
||||
// 处理 DTO 特殊情况:同时生成 QueryRequest
|
||||
if ($fileType === 'dto' && method_exists($fileGenerator, 'getQueryRequestContent')) {
|
||||
try {
|
||||
// 获取 QueryRequest 内容
|
||||
$queryContent = $fileGenerator->getQueryRequestContent();
|
||||
|
||||
if (!empty($queryContent)) {
|
||||
// 生成 QueryRequest 文件路径
|
||||
$formDir = dirname($filePath);
|
||||
$fileName = basename($filePath);
|
||||
$queryFileName = str_replace('FormRequest', 'QueryRequest', $fileName);
|
||||
$queryFilePath = $formDir . DS . $queryFileName;
|
||||
|
||||
// 构建临时文件路径(与 FormRequest 处理方式一致)
|
||||
$queryRelativePath = str_replace(dirname(base_path()), '', $queryFilePath);
|
||||
$queryRelativePath = ltrim($queryRelativePath, DS);
|
||||
$tempQueryFilePath = $tempDir . DS . $queryRelativePath;
|
||||
|
||||
// 确保目录存在
|
||||
$tempQueryDir = dirname($tempQueryFilePath);
|
||||
if (!is_dir($tempQueryDir)) {
|
||||
mkdir($tempQueryDir, 0777, true);
|
||||
}
|
||||
|
||||
// 写入临时文件
|
||||
if (file_put_contents($tempQueryFilePath, $queryContent) === false) {
|
||||
throw new \Exception('Failed to write temporary QueryRequest file: ' . $tempQueryFilePath);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error generating QueryRequest: ' . $e->getMessage(), [
|
||||
'file_type' => $fileType,
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error generating file: ' . $e->getMessage(), [
|
||||
'file_type' => $fileType,
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error in scene generation: ' . $e->getMessage(), [
|
||||
'scene_type' => $sceneType,
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 zip 文件
|
||||
$zipFileName = 'generator_' . uniqid() . '.zip';
|
||||
$zipFilePath = sys_get_temp_dir() . DS . $zipFileName;
|
||||
|
||||
if ($this->createZip($tempDir, $zipFilePath)) {
|
||||
// 清理临时目录
|
||||
$this->cleanupDir($tempDir);
|
||||
|
||||
// 确保返回的路径是绝对路径且格式正确
|
||||
$zipFilePath = realpath($zipFilePath);
|
||||
if (!$zipFilePath) {
|
||||
throw new \Exception('Failed to get real path for zip file');
|
||||
}
|
||||
|
||||
// 检查zip文件大小,确保不是空文件
|
||||
$fileSize = filesize($zipFilePath);
|
||||
if ($fileSize < 22) { // 空zip文件的最小大小约为22字节
|
||||
throw new \Exception('Generated zip file is empty');
|
||||
}
|
||||
|
||||
// 确保路径使用Windows兼容的反斜杠格式
|
||||
$zipFilePath = str_replace('/', '\\', $zipFilePath);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'file_path' => $zipFilePath,
|
||||
'file_name' => $zipFileName,
|
||||
'file_size' => $fileSize,
|
||||
];
|
||||
} else {
|
||||
// 清理临时目录
|
||||
$this->cleanupDir($tempDir);
|
||||
|
||||
throw new \Exception('Failed to create zip file');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 zip 文件
|
||||
* @param string $sourceDir 源目录
|
||||
* @param string $zipFilePath zip 文件路径
|
||||
* @return bool 是否成功
|
||||
*/
|
||||
private function createZip(string $sourceDir, string $zipFilePath): bool
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($zipFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($sourceDir),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (!$file->isDir()) {
|
||||
$filePath = $file->getRealPath();
|
||||
$relativePath = substr($filePath, strlen($sourceDir) + 1);
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return $zip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理目录
|
||||
* @param string $dir 目录路径
|
||||
*/
|
||||
private function cleanupDir(string $dir): void
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = $dir . DS . $file;
|
||||
if (is_dir($path)) {
|
||||
$this->cleanupDir($path);
|
||||
} else {
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证模块命名
|
||||
* @param array $config 生成配置
|
||||
* @throws \Exception 验证失败时抛出异常
|
||||
*/
|
||||
private function validateModuleName(array $config): void
|
||||
{
|
||||
// 直接读取配置文件
|
||||
$configFile = __DIR__ . '/config/app.php';
|
||||
$restrictions = file_exists($configFile) ? include $configFile : [];
|
||||
// 获取模块命名限制配置
|
||||
$restrictions = $restrictions['module_name_restrictions'] ?? [];
|
||||
// 如果未启用验证,直接返回
|
||||
if (!isset($restrictions['enabled']) || !$restrictions['enabled']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取模块名
|
||||
$moduleName = $config['package_name'] ?? null;
|
||||
|
||||
if (!$moduleName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否为保留名称
|
||||
if (isset($restrictions['reserved_names']) && is_array($restrictions['reserved_names'])) {
|
||||
if (in_array(strtolower($moduleName), array_map('strtolower', $restrictions['reserved_names']))) {
|
||||
throw new \Exception(sprintf('Module name "%s" is reserved and cannot be used', $moduleName));
|
||||
}
|
||||
}
|
||||
|
||||
// 检查命名格式
|
||||
if (isset($restrictions['pattern']) && $restrictions['pattern']) {
|
||||
if (!preg_match($restrictions['pattern'], $moduleName)) {
|
||||
throw new \Exception(sprintf('Module name "%s" is invalid. It should match pattern: %s', $moduleName, $restrictions['pattern']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
return [
|
||||
'enable' => true,
|
||||
// 模块命名限制
|
||||
'module_name_restrictions' => [
|
||||
'enabled' => true,
|
||||
'reserved_names' => [
|
||||
'system',
|
||||
'role',
|
||||
'admin',
|
||||
'auth',
|
||||
'user',
|
||||
'permission',
|
||||
'menu',
|
||||
'dept',
|
||||
'post',
|
||||
'dict',
|
||||
'config',
|
||||
'log',
|
||||
'monitor',
|
||||
'plugin',
|
||||
'dev',
|
||||
'test',
|
||||
'generator'
|
||||
],
|
||||
'pattern' => '/^[a-z0-9_-]+$/'
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"id": "272716236746915840",
|
||||
"table_name": "sys_admin_dept",
|
||||
"table_content": "用户与部门关联表",
|
||||
"module_name": "sys_admin_dept",
|
||||
"class_name": "sys_admin_dept",
|
||||
"edit_type": 1,
|
||||
"plugin_name": "",
|
||||
"order_type": 1,
|
||||
"parent_menu": "",
|
||||
"relations": [],
|
||||
"push_sync_count": 0,
|
||||
"created_at": "2026-01-22T05:16:57.000000Z",
|
||||
"updated_at": "2026-01-22T05:16:57.000000Z",
|
||||
"basic": {
|
||||
"table_name": "sys_admin_dept",
|
||||
"table_content": "用户与部门关联表",
|
||||
"plugin_name": "",
|
||||
"module_name": "sys_admin_dept",
|
||||
"class_name": "sys_admin_dept"
|
||||
},
|
||||
"config": {
|
||||
"is_delete": 1,
|
||||
"delete_column_name": "dept_id",
|
||||
"edit_type": 1,
|
||||
"order_column_name": "dept_id",
|
||||
"order_type": 1,
|
||||
"parent_menu": "",
|
||||
"plugin_name": "",
|
||||
"relations": [],
|
||||
"is_detail": 0
|
||||
},
|
||||
"created_date": "2026-01-22 13:16:57",
|
||||
"updated_date": "2026-01-22 13:16:57",
|
||||
"columns": [
|
||||
{
|
||||
"id": "272716237116014592",
|
||||
"table_id": "272716236746915840",
|
||||
"column_name": "dept_id",
|
||||
"column_comment": "角色主键",
|
||||
"column_type": "int",
|
||||
"is_required": 0,
|
||||
"is_pk": 1,
|
||||
"is_insert": 1,
|
||||
"is_update": 1,
|
||||
"is_lists": 1,
|
||||
"is_query": 1,
|
||||
"is_search": 1,
|
||||
"query_type": "=",
|
||||
"view_type": "input",
|
||||
"dict_type": "",
|
||||
"plugin": "",
|
||||
"model": "",
|
||||
"label_key": "",
|
||||
"value_key": "",
|
||||
"is_delete": 1,
|
||||
"is_order": 1,
|
||||
"validate_type": "",
|
||||
"created_at": "2026-01-22T05:16:57.000000Z",
|
||||
"updated_at": "2026-01-22T05:16:57.000000Z",
|
||||
"sort": 0,
|
||||
"created_date": "2026-01-22 13:16:57",
|
||||
"updated_date": "2026-01-22 13:16:57"
|
||||
},
|
||||
{
|
||||
"id": "272716236931465216",
|
||||
"table_id": "272716236746915840",
|
||||
"column_name": "admin_id",
|
||||
"column_comment": "用户主键",
|
||||
"column_type": "int",
|
||||
"is_required": 0,
|
||||
"is_pk": 1,
|
||||
"is_insert": 1,
|
||||
"is_update": 1,
|
||||
"is_lists": 1,
|
||||
"is_query": 1,
|
||||
"is_search": 1,
|
||||
"query_type": "=",
|
||||
"view_type": "input",
|
||||
"dict_type": "",
|
||||
"plugin": "",
|
||||
"model": "",
|
||||
"label_key": "",
|
||||
"value_key": "",
|
||||
"is_delete": 0,
|
||||
"is_order": 0,
|
||||
"validate_type": "",
|
||||
"created_at": "2026-01-22T05:16:57.000000Z",
|
||||
"updated_at": "2026-01-22T05:16:57.000000Z",
|
||||
"sort": 0,
|
||||
"created_date": "2026-01-22 13:16:57",
|
||||
"updated_date": "2026-01-22 13:16:57"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\factory;
|
||||
|
||||
use core\generator\interfaces\SceneGeneratorInterface;
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
|
||||
/**
|
||||
* 生成器工厂
|
||||
* 负责创建不同类型的生成器
|
||||
*/
|
||||
class GeneratorFactory
|
||||
{
|
||||
/**
|
||||
* 创建场景生成器
|
||||
*
|
||||
* @param string $sceneType 场景类型
|
||||
* @param array $config 配置信息
|
||||
*
|
||||
* @return SceneGeneratorInterface 场景生成器
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createSceneGenerator(string $sceneType, array $config): SceneGeneratorInterface
|
||||
{
|
||||
$className = "core\\generator\\scene\\" . ucfirst($sceneType) . "SceneGenerator";
|
||||
if (!class_exists($className)) {
|
||||
throw new \Exception('Scene generator not found: ' . $sceneType);
|
||||
}
|
||||
|
||||
return new $className($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件类型生成器
|
||||
*
|
||||
* @param string $fileType 文件类型
|
||||
* @param array $config 配置信息
|
||||
*
|
||||
* @return FileGeneratorInterface 文件类型生成器
|
||||
*/
|
||||
public function createFileGenerator(string $fileType, array $config): FileGeneratorInterface
|
||||
{
|
||||
// 处理特殊文件类型
|
||||
$fileTypeMap = [
|
||||
'api_model' => 'ApiModel',
|
||||
'view_schema' => 'ViewSchema',
|
||||
'request_form' => 'RequestForm',
|
||||
'request_query' => 'RequestQuery',
|
||||
|
||||
];
|
||||
|
||||
if (isset($fileTypeMap[$fileType])) {
|
||||
$className = "core\\generator\\file\\" . $fileTypeMap[$fileType] . "Generator";
|
||||
} else {
|
||||
$className = "core\\generator\\file\\" . ucfirst($fileType) . "Generator";
|
||||
}
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new \Exception('File generator not found: ' . $fileType);
|
||||
}
|
||||
|
||||
return new $className($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* API 生成器
|
||||
* 负责生成前端 API 文件内容
|
||||
*/
|
||||
class ApiGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
$moduleName = $this->config['package_name'] ?? 'default';
|
||||
|
||||
// 生成带类名的接口类型名称
|
||||
$rowTypeName = $className . 'Row';
|
||||
|
||||
// 生成baseUrl,使用连字符(-)而不是下划线(_)
|
||||
$basePath = strtolower(str_replace('_', '-', $moduleName));
|
||||
$classPath = strtolower(str_replace('_', '-', $className));
|
||||
$baseUrl = $this->config['base_url'] ?? '/' . $basePath . '/' . $classPath;
|
||||
|
||||
$content = $this->templateRenderer->render('admin/api/index.stub', [
|
||||
'class_name' => $className,
|
||||
'module_name' => $moduleName,
|
||||
'base_url' => $baseUrl,
|
||||
'row_type_name' => $rowTypeName,
|
||||
]);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'ts';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* API 模型生成器
|
||||
* 负责生成前端 API 模型文件内容
|
||||
*/
|
||||
class ApiModelGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
$moduleName = $this->config['package_name'] ?? 'default';
|
||||
|
||||
// 生成带类名的接口类型名称
|
||||
$rowTypeName = $className . 'Row';
|
||||
|
||||
// 生成字段类型映射
|
||||
$fields = '';
|
||||
if (isset($this->config['columns']) && is_array($this->config['columns'])) {
|
||||
foreach ($this->config['columns'] as $column) {
|
||||
if (isset($column['column_name']) && isset($column['column_type'])) {
|
||||
$fieldName = $column['column_name'];
|
||||
$fieldType = $this->getColumnType($column['column_type']);
|
||||
$fieldComment = isset($column['column_comment']) ? $column['column_comment'] : '';
|
||||
|
||||
// 如果有注释,添加注释行
|
||||
if (!empty($fieldComment)) {
|
||||
$fields .= " /** {$fieldComment} */\n";
|
||||
}
|
||||
|
||||
$fields .= " {$fieldName}: {$fieldType};\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$content = $this->templateRenderer->render('admin/api/types.stub', [
|
||||
'class_name' => $className,
|
||||
'module_name' => $moduleName,
|
||||
'row_type_name' => $rowTypeName,
|
||||
'fields' => $fields,
|
||||
]);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据库字段类型获取 TypeScript 类型
|
||||
* @param string $columnType 数据库字段类型
|
||||
* @return string TypeScript 类型
|
||||
*/
|
||||
private function getColumnType(string $columnType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'int' => 'number',
|
||||
'integer' => 'number',
|
||||
'tinyint' => 'number',
|
||||
'smallint' => 'number',
|
||||
'mediumint' => 'number',
|
||||
'bigint' => 'number',
|
||||
'float' => 'number',
|
||||
'double' => 'number',
|
||||
'decimal' => 'number',
|
||||
'string' => 'string',
|
||||
'varchar' => 'string',
|
||||
'char' => 'string',
|
||||
'text' => 'string',
|
||||
'mediumtext' => 'string',
|
||||
'longtext' => 'string',
|
||||
'date' => 'string',
|
||||
'datetime' => 'string',
|
||||
'timestamp' => 'string',
|
||||
'time' => 'string',
|
||||
'year' => 'number',
|
||||
'boolean' => 'boolean',
|
||||
'bool' => 'boolean',
|
||||
'json' => 'any',
|
||||
'array' => 'any[]',
|
||||
];
|
||||
|
||||
// 转换为小写并获取基础类型
|
||||
$columnType = strtolower($columnType);
|
||||
foreach ($typeMap as $key => $value) {
|
||||
if (strpos($columnType, $key) === 0) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认类型
|
||||
return 'any';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'ts';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* 控制器生成器
|
||||
* 负责生成控制器文件内容
|
||||
*/
|
||||
class ControllerGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
*
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
|
||||
// 生成连字符格式的名称(用于路由path)
|
||||
$packageNameDash = str_replace('_', '-', $packageName);
|
||||
$classNameDash = str_replace('_', '-', strtolower($className));
|
||||
|
||||
// 生成小写下划线格式的名称(用于权限码)
|
||||
$packageNameLower = strtolower($packageName);
|
||||
$classNameLower = strtolower(str_replace('_', '', $className));
|
||||
|
||||
// 生成命名空间
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\controller\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\controller\\{$packageName}";
|
||||
$schemaRequestNamespace = "plugin\\{$pluginName}\\app\\schema\\request\\{$packageName}";
|
||||
$validateNamespace = "plugin\\{$pluginName}\\app\\validate\\{$packageName}";
|
||||
$schemaResponseNamespace = "plugin\\{$pluginName}\\app\\schema\\response\\{$packageName}";
|
||||
$serviceNamespace = "plugin\\{$pluginName}\\app\\service\\{$packageName}";
|
||||
} else {
|
||||
// App 模式
|
||||
$namespace = "app\\adminapi\\controller\\{$packageName}";
|
||||
$schemaRequestNamespace = "app\\adminapi\\schema\\request\\{$packageName}";
|
||||
$validateNamespace = "app\\adminapi\\validate\\{$packageName}";
|
||||
$schemaResponseNamespace = "app\\adminapi\\schema\\response\\{$packageName}";
|
||||
$serviceNamespace = "app\\service\\admin\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $className,
|
||||
'package_name' => $packageName,
|
||||
'package_name_dash' => $packageNameDash,
|
||||
'class_name_dash' => $classNameDash,
|
||||
'package_name_lower' => $packageNameLower,
|
||||
'class_name_lower' => $classNameLower,
|
||||
'table_content' => $this->config['table_content'] ?? '默认模型',
|
||||
'camel_class_name' => lcfirst($className),
|
||||
'namespace' => $namespace,
|
||||
'schema_request_namespace' => $schemaRequestNamespace,
|
||||
'validate_namespace' => $validateNamespace,
|
||||
'schema_response_namespace' => $schemaResponseNamespace,
|
||||
'service_namespace' => $serviceNamespace,
|
||||
];
|
||||
|
||||
return $this->templateRenderer->render('server/controller/controller.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* DAO 生成器
|
||||
* 负责生成 DAO 文件内容
|
||||
*/
|
||||
class DaoGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成命名空间
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\dao\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\dao\\{$packageName}";
|
||||
$modelNamespace = "plugin\\{$pluginName}\\app\\model\\{$packageName}";
|
||||
} else {
|
||||
// App 模式:app\dao\{package_name}
|
||||
$namespace = "app\\dao\\{$packageName}";
|
||||
$modelNamespace = "app\\model\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $this->config['class_name'] ?? 'DefaultModel',
|
||||
'package_name' => $this->config['package_name'] ?? 'default',
|
||||
'table_name' => $this->config['table_name'] ?? 'default_table',
|
||||
'camel_class_name' => lcfirst($this->config['class_name'] ?? 'DefaultModel'),
|
||||
'namespace' => $namespace,
|
||||
'model_namespace' => $modelNamespace,
|
||||
];
|
||||
|
||||
return $this->templateRenderer->render('server/dao/dao.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* 语言包生成器
|
||||
* 负责生成前端语言包文件内容
|
||||
*/
|
||||
class LangGenerator implements FileGeneratorInterface
|
||||
{
|
||||
private array $config;
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
public function generateContent(): string
|
||||
{
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
$moduleName = $this->config['package_name'] ?? 'default';
|
||||
$columns = $this->config['columns'] ?? [];
|
||||
|
||||
$langKey = $this->generateLangKey($moduleName, $className);
|
||||
$fields = $this->generateFieldsTranslations($columns, $langKey);
|
||||
$classComment = $this->getClassComment($columns);
|
||||
|
||||
$content = $this->templateRenderer->render('admin/lang/zh-cn/index.stub', [
|
||||
'module_name' => $moduleName,
|
||||
'class_name' => $className,
|
||||
'lang_key' => $langKey,
|
||||
'class_name_comment' => $classComment,
|
||||
'fields' => $fields,
|
||||
]);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function generateLangKey(string $moduleName, string $className): string
|
||||
{
|
||||
$classKey = strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1_$2', $className));
|
||||
return "{$classKey}";
|
||||
}
|
||||
|
||||
private function generateFieldsTranslations(array $columns, string $langKey): string
|
||||
{
|
||||
$listFields = [];
|
||||
$formFields = [];
|
||||
$searchFields = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$fieldName = $column['column_name'];
|
||||
$comment = $column['column_comment'] ?? $fieldName;
|
||||
|
||||
if (isset($column['is_lists']) && $column['is_lists'] == 1) {
|
||||
$listFields[] = " \"{$fieldName}\": \"{$comment}\"";
|
||||
}
|
||||
|
||||
if ((isset($column['is_insert']) && $column['is_insert'] == 1) ||
|
||||
(isset($column['is_update']) && $column['is_update'] == 1)) {
|
||||
$formFields[] = " \"{$fieldName}\": \"{$comment}\"";
|
||||
}
|
||||
|
||||
if (isset($column['is_search']) && $column['is_search'] == 1) {
|
||||
$searchFields[] = " \"{$fieldName}\": \"{$comment}\"";
|
||||
}
|
||||
}
|
||||
|
||||
$result = '';
|
||||
|
||||
if (!empty($listFields)) {
|
||||
$result .= " \"list\": {\n" . implode(",\n", $listFields) . "\n },\n";
|
||||
}
|
||||
|
||||
if (!empty($formFields)) {
|
||||
$result .= " \"form\": {\n" . implode(",\n", $formFields) . "\n },\n";
|
||||
}
|
||||
|
||||
if (!empty($searchFields)) {
|
||||
$result .= " \"search\": {\n" . implode(",\n", $searchFields) . "\n }";
|
||||
}
|
||||
|
||||
return rtrim($result, ",\n");
|
||||
}
|
||||
|
||||
private function getClassComment(array $columns): string
|
||||
{
|
||||
return $this->config['class_name_comment'] ?? $this->config['table_comment'] ?? $this->config['class_name'];
|
||||
}
|
||||
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'json';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* 模型生成器
|
||||
* 负责生成模型文件内容
|
||||
*/
|
||||
class ModelGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
*
|
||||
* @return string 文件内容
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成命名空间
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\model\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\model\\{$packageName}";
|
||||
} else {
|
||||
// App 模式:app\model\{package_name}
|
||||
$namespace = "app\\model\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $this->config['class_name'] ?? 'DefaultModel',
|
||||
'package_name' => $this->config['package_name'] ?? 'default',
|
||||
'table_name' => $this->config['table_name'] ?? 'default_table',
|
||||
'table_content' => $this->config['table_content'] ?? '默认模型',
|
||||
'camel_class_name' => lcfirst($this->config['class_name'] ?? 'DefaultModel'),
|
||||
'namespace' => $namespace,
|
||||
];
|
||||
|
||||
// 查找主键字段
|
||||
$primaryKey = 'id';
|
||||
if (isset($this->config['columns']) && is_array($this->config['columns'])) {
|
||||
foreach ($this->config['columns'] as $column) {
|
||||
if (isset($column['column_name']) && isset($column['is_pk']) && $column['is_pk']) {
|
||||
$primaryKey = $column['column_name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加主键字段到数据中
|
||||
$data['primary_key'] = $primaryKey;
|
||||
|
||||
// 生成首字母大写的主键字段名,处理下划线的情况
|
||||
$ucfirstPrimaryKey = ucfirst($primaryKey);
|
||||
$ucfirstPrimaryKey = str_replace('_', '', ucwords($ucfirstPrimaryKey, '_'));
|
||||
$data['ucfirst_primary_key'] = $ucfirstPrimaryKey;
|
||||
|
||||
// 生成可填充字段
|
||||
$fillableFields = '';
|
||||
if (isset($this->config['columns']) && is_array($this->config['columns'])) {
|
||||
foreach ($this->config['columns'] as $column) {
|
||||
if (isset($column['column_name'])) {
|
||||
$fillableFields .= " '{$column['column_name']}',\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到数据中
|
||||
$data['fillable_fields'] = $fillableFields;
|
||||
|
||||
// 生成基于is_search字段的搜索器
|
||||
$searchers = '';
|
||||
if (isset($this->config['columns']) && is_array($this->config['columns'])) {
|
||||
foreach ($this->config['columns'] as $column) {
|
||||
if (isset($column['column_name']) && isset($column['is_search']) && $column['is_search']) {
|
||||
$columnName = $column['column_name'];
|
||||
// 生成首字母大写的字段名,处理下划线的情况
|
||||
$ucfirstColumnName = ucfirst($columnName);
|
||||
$ucfirstColumnName = str_replace('_', '', ucwords($ucfirstColumnName, '_'));
|
||||
|
||||
// 确定搜索运算符
|
||||
$operator = 'like';
|
||||
if (isset($column['query_type']) && !empty($column['query_type'])) {
|
||||
$operator = strtoupper($column['query_type']);
|
||||
}
|
||||
|
||||
// 生成搜索器方法
|
||||
$searchers .= " /**\n";
|
||||
$searchers .= " * 搜索器:{$columnName}\n";
|
||||
$searchers .= " */\n";
|
||||
$searchers .= " public function scope{$ucfirstColumnName}(\$query, \$value)\n";
|
||||
$searchers .= " {\n";
|
||||
$searchers .= " if (\$value) {\n";
|
||||
|
||||
// 根据不同的运算符生成不同的搜索逻辑
|
||||
switch ($operator) {
|
||||
case '=':
|
||||
case '!=':
|
||||
case '>':
|
||||
case '>=':
|
||||
case '<':
|
||||
case '<=':
|
||||
$searchers .= " \$query->where('{$columnName}', '{$operator}', \$value);\n";
|
||||
break;
|
||||
case 'LIKE':
|
||||
case 'NOT LIKE':
|
||||
$searchers .= " \$query->where('{$columnName}', '{$operator}', '%' . \$value . '%');\n";
|
||||
break;
|
||||
case 'IN':
|
||||
$searchers .= " if (is_array(\$value)) {\n";
|
||||
$searchers .= " \$query->whereIn('{$columnName}', \$value);\n";
|
||||
$searchers .= " }\n";
|
||||
break;
|
||||
case 'NOT IN':
|
||||
$searchers .= " if (is_array(\$value)) {\n";
|
||||
$searchers .= " \$query->whereNotIn('{$columnName}', \$value);\n";
|
||||
$searchers .= " }\n";
|
||||
break;
|
||||
case 'BETWEEN':
|
||||
$searchers .= " if (is_array(\$value) && count(\$value) == 2) {\n";
|
||||
$searchers .= " \$query->whereBetween('{$columnName}', \$value);\n";
|
||||
$searchers .= " }\n";
|
||||
break;
|
||||
case 'NOT BETWEEN':
|
||||
$searchers .= " if (is_array(\$value) && count(\$value) == 2) {\n";
|
||||
$searchers .= " \$query->whereNotBetween('{$columnName}', \$value);\n";
|
||||
$searchers .= " }\n";
|
||||
break;
|
||||
default:
|
||||
$searchers .= " \$query->where('{$columnName}', 'LIKE', '%' . \$value . '%');\n";
|
||||
break;
|
||||
}
|
||||
|
||||
$searchers .= " }\n";
|
||||
$searchers .= " return \$query;\n";
|
||||
$searchers .= " }\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到数据中
|
||||
$data['searchers'] = $searchers;
|
||||
|
||||
// 生成关联方法
|
||||
$relations = '';
|
||||
if (isset($this->config['relations']) && !empty($this->config['relations'])) {
|
||||
foreach ($this->config['relations'] as $relation) {
|
||||
if (isset($relation['type']) && isset($relation['model']) && isset($relation['foreign_key']) && isset($relation['local_key'])) {
|
||||
$relationType = $relation['type'];
|
||||
$relationModel = $relation['model'];
|
||||
$foreignKey = $relation['foreign_key'];
|
||||
$localKey = $relation['local_key'];
|
||||
// 优化关联方法名称,只使用模型类名的小写形式
|
||||
$modelParts = explode('\\', $relationModel);
|
||||
$methodName = lcfirst(end($modelParts));
|
||||
|
||||
// 如果是hasMany关系,添加s后缀
|
||||
if ($relationType === 'hasMany') {
|
||||
$methodName .= 's';
|
||||
}
|
||||
|
||||
// 生成关联方法
|
||||
$relations .= " /**\n";
|
||||
$relations .= " * 关联 {$relationModel}\n";
|
||||
$relations .= " */\n";
|
||||
$relations .= " public function {$methodName}()\n";
|
||||
$relations .= " {\n";
|
||||
$relations .= " return \$this->{$relationType}('{$relationModel}', '{$foreignKey}', '{$localKey}');\n";
|
||||
$relations .= " }\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成软删除功能
|
||||
$softDelete = '';
|
||||
$softDeleteUse = '';
|
||||
$softDeleteTrait = '';
|
||||
$config = $this->config['config'] ?? [];
|
||||
if (isset($config['is_delete']) && $config['is_delete'] == 1) {
|
||||
$deleteColumn = $config['delete_column_name'] ?? 'deleted_at';
|
||||
$softDeleteUse = "use Illuminate\\Database\\Eloquent\\SoftDeletes;\n";
|
||||
$softDeleteTrait = " /**\n";
|
||||
$softDeleteTrait .= " * 启用软删除\n";
|
||||
$softDeleteTrait .= " */\n";
|
||||
$softDeleteTrait .= " use SoftDeletes;\n\n";
|
||||
$softDelete .= " /**\n";
|
||||
$softDelete .= " * 软删除字段\n";
|
||||
$softDelete .= " */\n";
|
||||
$softDelete .= " const DELETED_AT = '{$deleteColumn}';\n\n";
|
||||
}
|
||||
|
||||
// 添加到数据中
|
||||
$data['soft_delete_use'] = $softDeleteUse;
|
||||
$data['soft_delete_trait'] = $softDeleteTrait;
|
||||
$data['soft_delete'] = $softDelete;
|
||||
$data['relations'] = $relations;
|
||||
|
||||
return $this->templateRenderer->render('server/model/model.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* DTO 生成器
|
||||
* 负责生成 DTO 文件内容
|
||||
*/
|
||||
class RequestFormGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
*
|
||||
* @return string 文件内容
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成命名空间
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\schema\request\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\schema\\request\\{$packageName}";
|
||||
} else {
|
||||
// App 模式:app\adminapi\schema\\request\{package_name}
|
||||
$namespace = "app\\adminapi\\schema\\request\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $this->config['class_name'] ?? 'DefaultModel',
|
||||
'package_name' => $this->config['package_name'] ?? 'default',
|
||||
'columns' => $this->config['columns'] ?? [],
|
||||
'namespace' => $namespace,
|
||||
];
|
||||
|
||||
// 生成字段注解
|
||||
$fields = '';
|
||||
if (isset($data['columns']) && is_array($data['columns'])) {
|
||||
foreach ($data['columns'] as $column) {
|
||||
// 跳过主键字段
|
||||
if (isset($column['is_pk']) && $column['is_pk']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 生成字段注解
|
||||
$fields .= $this->generateFieldAnnotation($column);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到数据中
|
||||
$data['fields'] = $fields;
|
||||
|
||||
return $this->templateRenderer->render('server/schema/request/form.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字段注解
|
||||
* @param array $column 字段信息
|
||||
* @return string 字段注解代码
|
||||
*/
|
||||
private function generateFieldAnnotation(array $column): string
|
||||
{
|
||||
$columnName = $column['column_name'];
|
||||
$columnComment = $column['column_comment'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
// 确定字段类型
|
||||
$phpType = $this->getPhpType($columnType);
|
||||
$nullableType = !$isRequired ? '?' . $phpType : $phpType;
|
||||
$defaultValue = !$isRequired ? ' = null' : '';
|
||||
|
||||
// 生成 OA\Property 注解
|
||||
$annotation = " #[OA\\Property(\n";
|
||||
$annotation .= " description: '{$columnComment}',\n";
|
||||
$annotation .= " type: '{$this->getOpenApiType($columnType)}',\n";
|
||||
$annotation .= " example: '{$this->getExampleValue($columnType)}',\n";
|
||||
$annotation .= " nullable: " . ($isRequired ? 'false' : 'true') . "\n";
|
||||
$annotation .= " )]\n";
|
||||
|
||||
// 生成 ValidationRules 注解
|
||||
$rules = $this->generateValidationRules($column);
|
||||
$annotation .= " #[ValidationRules(rules: '{$rules}')]\n";
|
||||
|
||||
// 生成字段声明
|
||||
$annotation .= " public {$nullableType} $" . "{$columnName}{$defaultValue};\n\n";
|
||||
|
||||
return $annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 PHP 类型
|
||||
* @param string $columnType 数据库字段类型
|
||||
* @return string PHP 类型
|
||||
*/
|
||||
private function getPhpType(string $columnType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'int' => 'int',
|
||||
'integer' => 'int',
|
||||
'string' => 'string',
|
||||
'varchar' => 'string',
|
||||
'text' => 'string',
|
||||
'boolean' => 'bool',
|
||||
'bool' => 'bool',
|
||||
'float' => 'float',
|
||||
'double' => 'float',
|
||||
'array' => 'array',
|
||||
'json' => 'array',
|
||||
];
|
||||
|
||||
return $typeMap[strtolower($columnType)] ?? 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 OpenAPI 类型
|
||||
* @param string $columnType 数据库字段类型
|
||||
* @return string OpenAPI 类型
|
||||
*/
|
||||
private function getOpenApiType(string $columnType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'int' => 'integer',
|
||||
'integer' => 'integer',
|
||||
'string' => 'string',
|
||||
'varchar' => 'string',
|
||||
'text' => 'string',
|
||||
'boolean' => 'boolean',
|
||||
'bool' => 'boolean',
|
||||
'float' => 'number',
|
||||
'double' => 'number',
|
||||
'array' => 'array',
|
||||
'json' => 'object',
|
||||
];
|
||||
|
||||
return $typeMap[strtolower($columnType)] ?? 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取示例值
|
||||
* @param string $columnType 数据库字段类型
|
||||
* @return string 示例值
|
||||
*/
|
||||
private function getExampleValue(string $columnType): string
|
||||
{
|
||||
$exampleMap = [
|
||||
'int' => '1',
|
||||
'integer' => '1',
|
||||
'string' => '示例值',
|
||||
'varchar' => '示例值',
|
||||
'text' => '示例文本',
|
||||
'boolean' => 'true',
|
||||
'bool' => 'true',
|
||||
'float' => '1.0',
|
||||
'double' => '1.0',
|
||||
'array' => '[]',
|
||||
'json' => '{"key": "value"}',
|
||||
];
|
||||
|
||||
return $exampleMap[strtolower($columnType)] ?? '示例值';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证规则
|
||||
* @param array $column 字段信息
|
||||
* @return string 验证规则
|
||||
*/
|
||||
private function generateValidationRules(array $column): string
|
||||
{
|
||||
$columnName = $column['column_name'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
$rules = [];
|
||||
|
||||
// 必填规则
|
||||
if ($isRequired) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
// 类型规则
|
||||
switch (strtolower($columnType)) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
$rules[] = 'integer';
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
$rules[] = 'numeric';
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
$rules[] = 'boolean';
|
||||
break;
|
||||
case 'array':
|
||||
$rules[] = 'array';
|
||||
break;
|
||||
default:
|
||||
$rules[] = 'string';
|
||||
break;
|
||||
}
|
||||
|
||||
return implode('|', $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* DTO 生成器
|
||||
* 负责生成 DTO 文件内容
|
||||
*/
|
||||
class RequestQueryGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
*
|
||||
* @return string 文件内容
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成命名空间
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\schema\request\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\schema\\request\\{$packageName}";
|
||||
} else {
|
||||
// App 模式:app\adminapi\schema\request\{package_name}
|
||||
$namespace = "app\\adminapi\\schema\\request\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $this->config['class_name'] ?? 'DefaultModel',
|
||||
'package_name' => $this->config['package_name'] ?? 'default',
|
||||
'columns' => $this->config['columns'] ?? [],
|
||||
'namespace' => $namespace,
|
||||
];
|
||||
|
||||
// 生成字段注解
|
||||
$fields = '';
|
||||
if (isset($data['columns']) && is_array($data['columns'])) {
|
||||
foreach ($data['columns'] as $column) {
|
||||
// 跳过主键字段
|
||||
if (isset($column['is_pk']) && $column['is_pk']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 生成字段注解
|
||||
$fields .= $this->generateFieldAnnotation($column);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到数据中
|
||||
$data['fields'] = $fields;
|
||||
|
||||
return $this->templateRenderer->render('server/schema/request/query.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字段注解
|
||||
*
|
||||
* @param array $column 字段信息
|
||||
*
|
||||
* @return string 字段注解代码
|
||||
*/
|
||||
private function generateFieldAnnotation(array $column): string
|
||||
{
|
||||
$columnName = $column['column_name'];
|
||||
$columnComment = $column['column_comment'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
// 确定字段类型
|
||||
$phpType = $this->getPhpType($columnType);
|
||||
$nullableType = !$isRequired ? '?' . $phpType : $phpType;
|
||||
$defaultValue = !$isRequired ? ' = null' : '';
|
||||
|
||||
// 生成 OA\Property 注解
|
||||
$annotation = " #[OA\\Property(\n";
|
||||
$annotation .= " description: '{$columnComment}',\n";
|
||||
$annotation .= " type: '{$this->getOpenApiType($columnType)}',\n";
|
||||
$annotation .= " example: '{$this->getExampleValue($columnType)}',\n";
|
||||
$annotation .= " nullable: " . ($isRequired ? 'false' : 'true') . "\n";
|
||||
$annotation .= " )]\n";
|
||||
|
||||
// 生成 ValidationRules 注解
|
||||
$rules = $this->generateValidationRules($column);
|
||||
$annotation .= " #[ValidationRules(rules: '{$rules}')]\n";
|
||||
|
||||
// 生成字段声明
|
||||
$annotation .= " public {$nullableType} $" . "{$columnName}{$defaultValue};\n\n";
|
||||
|
||||
return $annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 PHP 类型
|
||||
*
|
||||
* @param string $columnType 数据库字段类型
|
||||
*
|
||||
* @return string PHP 类型
|
||||
*/
|
||||
private function getPhpType(string $columnType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'int' => 'int',
|
||||
'integer' => 'int',
|
||||
'string' => 'string',
|
||||
'varchar' => 'string',
|
||||
'text' => 'string',
|
||||
'boolean' => 'bool',
|
||||
'bool' => 'bool',
|
||||
'float' => 'float',
|
||||
'double' => 'float',
|
||||
'array' => 'array',
|
||||
'json' => 'array',
|
||||
];
|
||||
|
||||
return $typeMap[strtolower($columnType)] ?? 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 OpenAPI 类型
|
||||
*
|
||||
* @param string $columnType 数据库字段类型
|
||||
*
|
||||
* @return string OpenAPI 类型
|
||||
*/
|
||||
private function getOpenApiType(string $columnType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'int' => 'integer',
|
||||
'integer' => 'integer',
|
||||
'string' => 'string',
|
||||
'varchar' => 'string',
|
||||
'text' => 'string',
|
||||
'boolean' => 'boolean',
|
||||
'bool' => 'boolean',
|
||||
'float' => 'number',
|
||||
'double' => 'number',
|
||||
'array' => 'array',
|
||||
'json' => 'object',
|
||||
];
|
||||
|
||||
return $typeMap[strtolower($columnType)] ?? 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取示例值
|
||||
*
|
||||
* @param string $columnType 数据库字段类型
|
||||
*
|
||||
* @return string 示例值
|
||||
*/
|
||||
private function getExampleValue(string $columnType): string
|
||||
{
|
||||
$exampleMap = [
|
||||
'int' => '1',
|
||||
'integer' => '1',
|
||||
'string' => '示例值',
|
||||
'varchar' => '示例值',
|
||||
'text' => '示例文本',
|
||||
'boolean' => 'true',
|
||||
'bool' => 'true',
|
||||
'float' => '1.0',
|
||||
'double' => '1.0',
|
||||
'array' => '[]',
|
||||
'json' => '{"key": "value"}',
|
||||
];
|
||||
|
||||
return $exampleMap[strtolower($columnType)] ?? '示例值';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证规则
|
||||
*
|
||||
* @param array $column 字段信息
|
||||
*
|
||||
* @return string 验证规则
|
||||
*/
|
||||
private function generateValidationRules(array $column): string
|
||||
{
|
||||
$columnName = $column['column_name'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
$rules = [];
|
||||
|
||||
// 必填规则
|
||||
if ($isRequired) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
// 类型规则
|
||||
switch (strtolower($columnType)) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
$rules[] = 'integer';
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
$rules[] = 'numeric';
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
$rules[] = 'boolean';
|
||||
break;
|
||||
case 'array':
|
||||
$rules[] = 'array';
|
||||
break;
|
||||
default:
|
||||
$rules[] = 'string';
|
||||
break;
|
||||
}
|
||||
|
||||
return implode('|', $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* Schema 生成器
|
||||
* 负责生成 Schema 文件内容
|
||||
*/
|
||||
class ResponseGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成命名空间
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\schema\response\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\schema\\response\\{$packageName}";
|
||||
} else {
|
||||
// App 模式:app\adminapi\\schema\{package_name}
|
||||
$namespace = "app\\adminapi\\schema\\response\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $this->config['class_name'] ?? 'DefaultModel',
|
||||
'package_name' => $this->config['package_name'] ?? 'default',
|
||||
'columns' => $this->config['columns'] ?? [],
|
||||
'namespace' => $namespace,
|
||||
];
|
||||
|
||||
// 生成字段注解
|
||||
$fields = '';
|
||||
if (isset($data['columns']) && is_array($data['columns'])) {
|
||||
foreach ($data['columns'] as $column) {
|
||||
// 跳过主键字段
|
||||
if (isset($column['is_pk']) && $column['is_pk']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 生成字段注解
|
||||
$fields .= $this->generateFieldAnnotation($column);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到数据中
|
||||
$data['fields'] = $fields;
|
||||
|
||||
return $this->templateRenderer->render('server/schema/response/schema.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字段注解
|
||||
* @param array $column 字段信息
|
||||
* @return string 字段注解代码
|
||||
*/
|
||||
private function generateFieldAnnotation(array $column): string
|
||||
{
|
||||
$columnName = $column['column_name'];
|
||||
$columnComment = $column['column_comment'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
// 确定字段类型
|
||||
$phpType = $this->getPhpType($columnType);
|
||||
$nullableType = !$isRequired ? '?' . $phpType : $phpType;
|
||||
$defaultValue = !$isRequired ? ' = null' : '';
|
||||
|
||||
// 生成 OA\Property 注解
|
||||
$annotation = " #[OA\Property(\n";
|
||||
$annotation .= " description: '{$columnComment}',\n";
|
||||
$annotation .= " type: '{$this->getOpenApiType($columnType)}',\n";
|
||||
$annotation .= " example: '{$this->getExampleValue($columnType)}',\n";
|
||||
$annotation .= " nullable: " . ($isRequired ? 'false' : 'true') . "\n";
|
||||
$annotation .= " )]\n";
|
||||
|
||||
// 生成字段声明(Schema定义不需要验证规则)
|
||||
$annotation .= " public {$nullableType} $" . "{$columnName}{$defaultValue};\n\n";
|
||||
|
||||
return $annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 PHP 类型
|
||||
* @param string $columnType 数据库字段类型
|
||||
* @return string PHP 类型
|
||||
*/
|
||||
private function getPhpType(string $columnType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'int' => 'int',
|
||||
'integer' => 'int',
|
||||
'string' => 'string',
|
||||
'varchar' => 'string',
|
||||
'text' => 'string',
|
||||
'boolean' => 'bool',
|
||||
'bool' => 'bool',
|
||||
'float' => 'float',
|
||||
'double' => 'float',
|
||||
'array' => 'array',
|
||||
'json' => 'array',
|
||||
];
|
||||
|
||||
return $typeMap[strtolower($columnType)] ?? 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 OpenAPI 类型
|
||||
* @param string $columnType 数据库字段类型
|
||||
* @return string OpenAPI 类型
|
||||
*/
|
||||
private function getOpenApiType(string $columnType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'int' => 'integer',
|
||||
'integer' => 'integer',
|
||||
'string' => 'string',
|
||||
'varchar' => 'string',
|
||||
'text' => 'string',
|
||||
'boolean' => 'boolean',
|
||||
'bool' => 'boolean',
|
||||
'float' => 'number',
|
||||
'double' => 'number',
|
||||
'array' => 'array',
|
||||
'json' => 'object',
|
||||
];
|
||||
|
||||
return $typeMap[strtolower($columnType)] ?? 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取示例值
|
||||
* @param string $columnType 数据库字段类型
|
||||
* @return string 示例值
|
||||
*/
|
||||
private function getExampleValue(string $columnType): string
|
||||
{
|
||||
$exampleMap = [
|
||||
'int' => '1',
|
||||
'integer' => '1',
|
||||
'string' => '示例值',
|
||||
'varchar' => '示例值',
|
||||
'text' => '示例文本',
|
||||
'boolean' => 'true',
|
||||
'bool' => 'true',
|
||||
'float' => '1.0',
|
||||
'double' => '1.0',
|
||||
'array' => '[]',
|
||||
'json' => '{"key": "value"}',
|
||||
];
|
||||
|
||||
return $exampleMap[strtolower($columnType)] ?? '示例值';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证规则
|
||||
* @param array $column 字段信息
|
||||
* @return string 验证规则
|
||||
*/
|
||||
private function generateValidationRules(array $column): string
|
||||
{
|
||||
$columnName = $column['column_name'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
$rules = [];
|
||||
|
||||
// 必填规则
|
||||
if ($isRequired) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
// 类型规则
|
||||
switch (strtolower($columnType)) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
$rules[] = 'integer';
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
$rules[] = 'numeric';
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
$rules[] = 'boolean';
|
||||
break;
|
||||
case 'array':
|
||||
$rules[] = 'array';
|
||||
break;
|
||||
default:
|
||||
$rules[] = 'string';
|
||||
break;
|
||||
}
|
||||
|
||||
return implode('|', $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* 服务生成器
|
||||
* 负责生成服务文件内容
|
||||
*/
|
||||
class ServiceGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成命名空间
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\service\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\service\\{$packageName}";
|
||||
} else {
|
||||
// App 模式:app\service\admin\{package_name}
|
||||
$namespace = "app\\service\\admin\\{$packageName}";
|
||||
}
|
||||
|
||||
// 生成 DAO 命名空间
|
||||
if ($isPlugin) {
|
||||
$daoNamespace = "plugin\\{$pluginName}\\app\\dao\\{$packageName}";
|
||||
} else {
|
||||
$daoNamespace = "app\\dao\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $this->config['class_name'] ?? 'DefaultModel',
|
||||
'package_name' => $this->config['package_name'] ?? 'default',
|
||||
'table_content' => $this->config['table_content'] ?? '默认模型',
|
||||
'camel_class_name' => lcfirst($this->config['class_name'] ?? 'DefaultModel'),
|
||||
'namespace' => $namespace,
|
||||
'dao_namespace' => $daoNamespace,
|
||||
];
|
||||
|
||||
return $this->templateRenderer->render('server/service/service.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* 验证器生成器
|
||||
* 负责生成验证器文件内容
|
||||
*/
|
||||
class ValidateGenerator implements FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var TemplateRenderer 模板渲染器
|
||||
*/
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成命名空间
|
||||
$packageName = $this->config['package_name'] ?? 'default';
|
||||
if ($isPlugin) {
|
||||
// 插件模式:plugin\{plugin_name}\app\validate\{package_name}
|
||||
$namespace = "plugin\\{$pluginName}\\app\\validate\\{$packageName}";
|
||||
} else {
|
||||
// App 模式:app\adminapi\validate\{package_name}
|
||||
$namespace = "app\\adminapi\\validate\\{$packageName}";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class_name' => $this->config['class_name'] ?? 'DefaultModel',
|
||||
'package_name' => $this->config['package_name'] ?? 'default',
|
||||
'columns' => $this->config['columns'] ?? [],
|
||||
'namespace' => $namespace,
|
||||
];
|
||||
|
||||
// 生成场景字段
|
||||
$storeFields = [];
|
||||
$updateFields = [];
|
||||
$rules = [];
|
||||
$messages = [];
|
||||
|
||||
if (isset($data['columns']) && is_array($data['columns'])) {
|
||||
foreach ($data['columns'] as $column) {
|
||||
$columnName = $column['column_name'];
|
||||
$columnComment = $column['column_comment'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$isInsert = isset($column['is_insert']) && $column['is_insert'];
|
||||
$isUpdate = isset($column['is_update']) && $column['is_update'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
// 生成验证规则
|
||||
$validationRule = $this->generateValidationRule($column);
|
||||
if (!empty($validationRule)) {
|
||||
$rules[] = "'{$columnName}' => '{$validationRule}'";
|
||||
|
||||
// 生成错误信息
|
||||
$errorMessages = $this->generateErrorMessage($column);
|
||||
foreach ($errorMessages as $rule => $message) {
|
||||
$messages[] = "'{$columnName}.{$rule}' => '{$message}'";
|
||||
}
|
||||
|
||||
// 添加到场景
|
||||
$isPk = isset($column['is_pk']) && $column['is_pk'];
|
||||
if ($isInsert && !$isPk) {
|
||||
$storeFields[] = "'{$columnName}'";
|
||||
}
|
||||
if ($isUpdate) {
|
||||
$updateFields[] = "'{$columnName}'";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到数据中
|
||||
$data['store_fields'] = implode(",\n ", $storeFields);
|
||||
$data['update_fields'] = implode(",\n ", $updateFields);
|
||||
$data['rules'] = implode(",\n ", $rules);
|
||||
$data['messages'] = implode(",\n ", $messages);
|
||||
|
||||
return $this->templateRenderer->render('server/validate/validation.stub', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证规则
|
||||
* @param array $column 字段信息
|
||||
* @return string 验证规则
|
||||
*/
|
||||
private function generateValidationRule(array $column): string
|
||||
{
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
$isPk = isset($column['is_pk']) && $column['is_pk'];
|
||||
|
||||
$rules = [];
|
||||
|
||||
// 必填规则
|
||||
if ($isRequired) {
|
||||
$rules[] = 'required';
|
||||
}
|
||||
|
||||
// 类型规则
|
||||
switch (strtolower($columnType)) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
// 主键字段支持int和string两种类型(如雪花ID)
|
||||
if ($isPk) {
|
||||
$rules[] = 'integer|string';
|
||||
} else {
|
||||
$rules[] = 'integer';
|
||||
}
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
$rules[] = 'numeric';
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
$rules[] = 'boolean';
|
||||
break;
|
||||
case 'array':
|
||||
$rules[] = 'array';
|
||||
break;
|
||||
case 'email':
|
||||
$rules[] = 'email';
|
||||
break;
|
||||
default:
|
||||
$rules[] = 'string';
|
||||
break;
|
||||
}
|
||||
|
||||
return implode('|', $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成错误信息
|
||||
* @param array $column 字段信息
|
||||
* @return array 错误信息
|
||||
*/
|
||||
private function generateErrorMessage(array $column): array
|
||||
{
|
||||
$columnName = $column['column_name'];
|
||||
$columnComment = $column['column_comment'];
|
||||
$isRequired = isset($column['is_required']) && $column['is_required'];
|
||||
$columnType = $column['column_type'] ?? 'string';
|
||||
|
||||
$messages = [];
|
||||
|
||||
// 必填错误信息
|
||||
if ($isRequired) {
|
||||
$messages['required'] = "{$columnComment}必须填写";
|
||||
}
|
||||
|
||||
$isPk = isset($column['is_pk']) && $column['is_pk'];
|
||||
|
||||
// 类型错误信息
|
||||
switch (strtolower($columnType)) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
// 主键字段支持int和string两种类型(如雪花ID)
|
||||
if ($isPk) {
|
||||
$messages['integer'] = "{$columnComment}必须是整数或字符串";
|
||||
$messages['string'] = "{$columnComment}必须是整数或字符串";
|
||||
} else {
|
||||
$messages['integer'] = "{$columnComment}必须是整数";
|
||||
}
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
$messages['numeric'] = "{$columnComment}必须是数字";
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'bool':
|
||||
$messages['boolean'] = "{$columnComment}必须是布尔值";
|
||||
break;
|
||||
case 'array':
|
||||
$messages['array'] = "{$columnComment}必须是数组";
|
||||
break;
|
||||
case 'email':
|
||||
$messages['email'] = "{$columnComment}格式不正确";
|
||||
break;
|
||||
default:
|
||||
$messages['string'] = "{$columnComment}必须是字符串";
|
||||
break;
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* 视图生成器
|
||||
* 负责生成前端视图文件内容
|
||||
*/
|
||||
class ViewGenerator implements FileGeneratorInterface
|
||||
{
|
||||
private array $config;
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
public function generateContent(): string
|
||||
{
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
$moduleName = $this->config['package_name'] ?? 'default';
|
||||
$dummyClassName = strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1_$2', $className));
|
||||
$serviceName = $className . 'Service';
|
||||
$kebabModuleName = str_replace('_', '-', $moduleName);
|
||||
$kebabClassName = strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1-$2', $className));
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成 API import 路径
|
||||
if ($isPlugin) {
|
||||
// 插件模式: @/apps/{plugin}/api/{kebab_module_name}
|
||||
$apiImportPath = '@/apps/' . $pluginName . '/api/' . $kebabModuleName;
|
||||
} else {
|
||||
// 应用模式: @/api/{kebab_module_name}
|
||||
$apiImportPath = '@/api/' . $kebabModuleName;
|
||||
}
|
||||
|
||||
$langKey = $this->generateLangKey($moduleName, $className);
|
||||
$langImportPath = $this->generateLangImportPath($moduleName);
|
||||
|
||||
$content = $this->templateRenderer->render('admin/views/index.stub', [
|
||||
'module_name' => $moduleName,
|
||||
'class_name' => $className,
|
||||
'dummy_class_name' => $dummyClassName,
|
||||
'service_name' => $serviceName,
|
||||
'kebab_module_name' => $kebabModuleName,
|
||||
'kebab_class_name' => $kebabClassName,
|
||||
'api_import_path' => $apiImportPath,
|
||||
'lang_key' => $langKey,
|
||||
'lang_import_path' => $langImportPath,
|
||||
]);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function generateLangKey(string $moduleName, string $className): string
|
||||
{
|
||||
$moduleKey = strtolower(str_replace('-', '_', $moduleName));
|
||||
$classKey = strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1_$2', $className));
|
||||
return "{$moduleKey}_{$classKey}";
|
||||
}
|
||||
|
||||
private function generateLangImportPath(string $moduleName): string
|
||||
{
|
||||
return "@/lang/{$moduleName}";
|
||||
}
|
||||
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'vue';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\file;
|
||||
|
||||
use core\generator\interfaces\FileGeneratorInterface;
|
||||
use core\generator\utils\TemplateRenderer;
|
||||
|
||||
/**
|
||||
* 视图 Schema 生成器
|
||||
* 负责生成前端视图 Schema 文件内容
|
||||
*/
|
||||
class ViewSchemaGenerator implements FileGeneratorInterface
|
||||
{
|
||||
private array $config;
|
||||
private TemplateRenderer $templateRenderer;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->templateRenderer = new TemplateRenderer();
|
||||
}
|
||||
|
||||
public function generateContent(): string
|
||||
{
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
$moduleName = $this->config['package_name'] ?? 'default';
|
||||
$dummyClassName = strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1_$2', $className));
|
||||
$serviceName = $className . 'Service';
|
||||
$kebabModuleName = str_replace('_', '-', $moduleName);
|
||||
$columns = $this->config['columns'] ?? [];
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$isPlugin = $template !== 'app';
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
|
||||
// 生成 API import 路径
|
||||
if ($isPlugin) {
|
||||
// 插件模式: @/apps/{plugin}/api/{kebab_module_name}
|
||||
$apiImportPath = '@/apps/' . $pluginName . '/api/' . $kebabModuleName;
|
||||
} else {
|
||||
// 应用模式: @/api/{kebab_module_name}
|
||||
$apiImportPath = '@/api/' . $kebabModuleName;
|
||||
}
|
||||
|
||||
$langKey = $this->generateLangKey($moduleName, $className);
|
||||
$langImportPath = $this->generateLangImportPath($moduleName);
|
||||
|
||||
$tableColumns = $this->processTableColumns($columns, $langKey);
|
||||
$searchFormSchema = $this->processSearchFormSchema($columns, $langKey);
|
||||
$formSchema = $this->processFormSchema($columns, $langKey);
|
||||
|
||||
$dialogType = '';
|
||||
if (isset($this->config['config']['edit_type']) && $this->config['config']['edit_type'] == 2) {
|
||||
$dialogType = 'dialogType: "drawer"';
|
||||
}
|
||||
|
||||
$dialogWidth = $this->config['config']['dialog_width'] ?? '50%';
|
||||
|
||||
$content = $this->templateRenderer->render('admin/views/schemas/index.stub', [
|
||||
'class_name' => $className,
|
||||
'module_name' => $moduleName,
|
||||
'dummy_class_name' => $dummyClassName,
|
||||
'service_name' => $serviceName,
|
||||
'kebab_module_name' => $kebabModuleName,
|
||||
'api_import_path' => $apiImportPath,
|
||||
'lang_key' => $langKey,
|
||||
'lang_import_path' => $langImportPath,
|
||||
'table_columns' => $tableColumns,
|
||||
'search_form_schema' => $searchFormSchema,
|
||||
'form_schema' => $formSchema,
|
||||
'use_crud' => isset($this->config['config']['use_crud']) ? (bool)$this->config['config']['use_crud'] : true,
|
||||
'has_add' => isset($this->config['config']['has_add']) ? (bool)$this->config['config']['has_add'] : true,
|
||||
'has_remove' => isset($this->config['config']['has_remove']) ? (bool)$this->config['config']['has_remove'] : true,
|
||||
'has_edit' => isset($this->config['config']['has_edit']) ? (bool)$this->config['config']['has_edit'] : true,
|
||||
'has_view' => isset($this->config['config']['has_view']) ? (bool)$this->config['config']['has_view'] : false,
|
||||
'dialog_type' => $dialogType,
|
||||
'dialog_width' => $dialogWidth,
|
||||
]);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function generateLangKey(string $moduleName, string $className): string
|
||||
{
|
||||
$classKey = strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1_$2', $className));
|
||||
$kebabModuleName = strtolower(str_replace('_', '-', $moduleName));
|
||||
return "{$kebabModuleName}.{$classKey}";
|
||||
}
|
||||
|
||||
private function generateLangImportPath(string $moduleName): string
|
||||
{
|
||||
return "@/lang/{$moduleName}";
|
||||
}
|
||||
|
||||
private function processTableColumns(array $columns, string $langKey): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($columns as $column) {
|
||||
if (isset($column['is_lists']) && $column['is_lists'] == 1) {
|
||||
$tableItem = [
|
||||
'prop' => $column['column_name'],
|
||||
'label' => '$t(\'' . $langKey . '.list.' . $column['column_name'] . '\')'
|
||||
];
|
||||
if(isset($column['view_type']) && $column['view_type'] == 'ApiDict') {
|
||||
$tableItem['component'] = 'ApiDict';
|
||||
$tableItem['componentProps'] = [
|
||||
'code' => $column['dict_type'],
|
||||
];
|
||||
}
|
||||
|
||||
$result[] = $this->arrayToCode($tableItem);
|
||||
}
|
||||
}
|
||||
return implode(",\n ", $result);
|
||||
}
|
||||
|
||||
private function arrayToCode(array $array): string
|
||||
{
|
||||
$items = [];
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$items[] = $key . ': ' . $this->arrayToCode($value);
|
||||
} elseif (is_bool($value)) {
|
||||
$items[] = $key . ': ' . ($value ? 'true' : 'false');
|
||||
} elseif (is_int($value) || is_float($value)) {
|
||||
$items[] = $key . ': ' . $value;
|
||||
} elseif (strpos($value, '$t(') === 0 || strpos($value, '[') === 0) {
|
||||
$items[] = $key . ': ' . $value;
|
||||
} else {
|
||||
$items[] = $key . ': "' . $value . '"';
|
||||
}
|
||||
}
|
||||
return '{' . implode(', ', $items) . '}';
|
||||
}
|
||||
|
||||
private function processSearchFormSchema(array $columns, string $langKey): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($columns as $column) {
|
||||
if (isset($column['is_search']) && $column['is_search'] == 1) {
|
||||
$prop = $column['column_name'];
|
||||
if (strpos(strtoupper($prop), 'LIKE_') === 0) {
|
||||
$prop = substr($prop, 5);
|
||||
} elseif (strpos(strtoupper($prop), 'EQ_') === 0) {
|
||||
$prop = substr($prop, 3);
|
||||
}
|
||||
|
||||
$searchItem = [
|
||||
'component' => $this->getComponent($column),
|
||||
'prop' => $prop,
|
||||
'label' => '$t(\'' . $langKey . '.search.' . $prop . '\')',
|
||||
'componentProps' => [
|
||||
'placeholder' => '$t(\'' . $langKey . '.search.' . $prop . '\')'
|
||||
],
|
||||
'colSpan' => 6
|
||||
];
|
||||
|
||||
if (isset($column['label_key']) && $column['label_key']) {
|
||||
$searchItem['labelKey'] = $column['label_key'];
|
||||
}
|
||||
if (isset($column['value_key']) && $column['value_key']) {
|
||||
$searchItem['valueKey'] = $column['value_key'];
|
||||
}
|
||||
|
||||
if (isset($column['api']) && $column['api']) {
|
||||
$searchItem['componentProps']['api'] = $column['api'];
|
||||
}
|
||||
|
||||
if (isset($column['dict_type']) && $column['dict_type']) {
|
||||
$searchItem['componentProps']['code'] = $column['dict_type'];
|
||||
}
|
||||
|
||||
$result[] = $this->arrayToCode($searchItem);
|
||||
}
|
||||
}
|
||||
return implode(",\n ", $result);
|
||||
}
|
||||
|
||||
private function processFormSchema(array $columns, string $langKey): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($columns as $column) {
|
||||
if (isset($column['is_pk']) && $column['is_pk'] == 1) {
|
||||
$item = [
|
||||
'label' => '$t(\'' . $langKey . '.form.' . $column['column_name'] . '\')',
|
||||
'prop' => $column['column_name'],
|
||||
'component' => $this->getComponent($column),
|
||||
'colSpan' => 24,
|
||||
'show' => false
|
||||
];
|
||||
$result[] = $this->arrayToCode($item);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((isset($column['is_insert']) && $column['is_insert'] == 1) || (isset($column['is_update']) && $column['is_update'] == 1)) {
|
||||
$item = [
|
||||
'label' => '$t(\'' . $langKey . '.form.' . $column['column_name'] . '\')',
|
||||
'prop' => $column['column_name'],
|
||||
'component' => $this->getComponent($column),
|
||||
'colSpan' => 24
|
||||
];
|
||||
|
||||
if (isset($column['show']) && $column['show'] == 1) {
|
||||
$item['show'] = true;
|
||||
}
|
||||
|
||||
if (isset($column['ifDetail']) && $column['ifDetail'] == 1) {
|
||||
$item['ifDetail'] = true;
|
||||
}
|
||||
|
||||
if (isset($column['validate_type']) && $column['validate_type']) {
|
||||
$item['rules'] = $this->getValidationRules($column['validate_type']);
|
||||
}
|
||||
|
||||
if (isset($column['label_key']) && $column['label_key']) {
|
||||
$item['labelKey'] = $column['label_key'];
|
||||
}
|
||||
if (isset($column['value_key']) && $column['value_key']) {
|
||||
$item['valueKey'] = $column['value_key'];
|
||||
}
|
||||
|
||||
if (isset($column['api']) && $column['api']) {
|
||||
$item['componentProps'] = [
|
||||
'api' => $column['api']
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($column['dict_type']) && $column['dict_type']) {
|
||||
if (!isset($item['componentProps'])) {
|
||||
$item['componentProps'] = [];
|
||||
}
|
||||
$item['componentProps']['code'] = $column['dict_type'];
|
||||
}
|
||||
|
||||
$result[] = $this->arrayToCode($item);
|
||||
}
|
||||
}
|
||||
return implode(",\n ", $result);
|
||||
}
|
||||
|
||||
private function getComponent(array $column): string
|
||||
{
|
||||
if (isset($column['dict_type']) && $column['dict_type']) {
|
||||
return 'ApiDict';
|
||||
}
|
||||
|
||||
if (isset($column['view_type']) && $column['view_type']) {
|
||||
$viewType = $column['view_type'];
|
||||
return $viewType;
|
||||
}
|
||||
return 'Input';
|
||||
}
|
||||
|
||||
private function getValidationRules(string $validateType): string
|
||||
{
|
||||
$rules = [];
|
||||
$validateTypes = explode(',', $validateType);
|
||||
foreach ($validateTypes as $type) {
|
||||
switch (trim($type)) {
|
||||
case 'required':
|
||||
$rules[] = '{"required":true,"message":"此项为必填项","trigger":"blur"}';
|
||||
break;
|
||||
case 'email':
|
||||
$rules[] = '{"type":"email","message":"请输入有效的邮箱地址","trigger":"blur"}';
|
||||
break;
|
||||
case 'url':
|
||||
$rules[] = '{"type":"url","message":"请输入有效的URL地址","trigger":"blur"}';
|
||||
break;
|
||||
case 'number':
|
||||
$rules[] = '{"type":"number","message":"请输入数字","trigger":"blur"}';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return '[' . implode(',', $rules) . ']';
|
||||
}
|
||||
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'tsx';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\interfaces;
|
||||
|
||||
/**
|
||||
* 文件类型生成器接口
|
||||
* 负责生成特定类型的文件内容
|
||||
*/
|
||||
interface FileGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(): string;
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @return string 文件扩展名
|
||||
*/
|
||||
public function getFileExtension(): string;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\interfaces;
|
||||
|
||||
/**
|
||||
* 场景生成器接口
|
||||
* 负责生成特定场景的文件路径
|
||||
*/
|
||||
interface SceneGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* 生成文件路径
|
||||
* @param string $fileType 文件类型
|
||||
* @param string $extension 文件扩展名
|
||||
* @return string 文件路径
|
||||
*/
|
||||
public function generateFilePath(string $fileType, string $extension = 'php'): string;
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @param string $fileType 文件类型
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(string $fileType): string;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\scene;
|
||||
|
||||
use core\generator\interfaces\SceneGeneratorInterface;
|
||||
use core\generator\utils\PathResolver;
|
||||
|
||||
/**
|
||||
* 前端场景生成器
|
||||
* 负责生成前端文件路径
|
||||
*/
|
||||
class AdminSceneGenerator implements SceneGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var PathResolver 路径解析器
|
||||
*/
|
||||
private PathResolver $pathResolver;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->pathResolver = new PathResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件路径
|
||||
* @param string $fileType 文件类型
|
||||
* @param string $extension 文件扩展名
|
||||
* @return string 文件路径
|
||||
*/
|
||||
public function generateFilePath(string $fileType, string $extension = 'php'): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$moduleName = $this->config['package_name'] ?? 'default';
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
|
||||
// 根据模板类型选择不同的基础路径和路径生成逻辑
|
||||
if ($template === 'app') {
|
||||
$basePath = $this->getBasePath() . DS . 'src';
|
||||
$path = $this->pathResolver->generatePath($basePath, $moduleName, $className, $fileType, $extension);
|
||||
} else {
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
$basePath = $this->getBasePath() . DS . 'src' . DS . 'apps' . DS . $pluginName;
|
||||
$path = $this->pathResolver->generatePath($basePath, $moduleName, $className, $fileType, $extension);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
* @param string $fileType 文件类型
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(string $fileType): string
|
||||
{
|
||||
// 内容生成由文件类型生成器负责
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础路径
|
||||
* @return string 基础路径
|
||||
*/
|
||||
private function getBasePath(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
|
||||
// 无论是 app 模式还是插件模式,前端代码都统一生成在 frontend 目录下的 admin 目录中
|
||||
// 前端目录结构:project_root/frontend/admin 或 project_root/frontend/web 等
|
||||
return dirname(base_path()) . DS . 'frontend' . DS . 'admin';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\scene;
|
||||
|
||||
use core\generator\interfaces\SceneGeneratorInterface;
|
||||
use core\generator\utils\PathResolver;
|
||||
|
||||
/**
|
||||
* 后端场景生成器
|
||||
* 负责生成后端文件路径
|
||||
*/
|
||||
class BackendSceneGenerator implements SceneGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var array 配置信息
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* @var PathResolver 路径解析器
|
||||
*/
|
||||
private PathResolver $pathResolver;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param array $config 配置信息
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->pathResolver = new PathResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件路径
|
||||
*
|
||||
* @param string $fileType 文件类型
|
||||
* @param string $extension 文件扩展名
|
||||
*
|
||||
* @return string 文件路径
|
||||
*/
|
||||
public function generateFilePath(string $fileType, string $extension = 'php'): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
$moduleName = $this->config['package_name'] ?? 'default';
|
||||
$className = $this->config['class_name'] ?? 'DefaultModel';
|
||||
$isPlugin = $template !== 'app';
|
||||
|
||||
// 处理特殊文件类型
|
||||
// if ($fileType === 'request') {
|
||||
// // 请求需要生成两个文件:FormRequest 和 QueryRequest
|
||||
// $dtoBasePath = $this->getBasePathForFileType($fileType);
|
||||
// $formPath = $this->pathResolver->generatePath($dtoBasePath, $moduleName, $className, 'request_form', $extension, $isPlugin);
|
||||
// $queryPath = $this->pathResolver->generatePath($dtoBasePath, $moduleName, $className, 'request_query', $extension, $isPlugin);
|
||||
//
|
||||
// // 返回表单请求路径,查询请求路径会在生成器中单独处理
|
||||
// return $formPath;
|
||||
// }
|
||||
|
||||
$basePath = $this->getBasePathForFileType($fileType);
|
||||
$path = $this->pathResolver->generatePath($basePath, $moduleName, $className, $fileType, $extension, $isPlugin);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件内容
|
||||
*
|
||||
* @param string $fileType 文件类型
|
||||
*
|
||||
* @return string 文件内容
|
||||
*/
|
||||
public function generateContent(string $fileType): string
|
||||
{
|
||||
// 内容生成由文件类型生成器负责
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础路径
|
||||
*
|
||||
* @return string 基础路径
|
||||
*/
|
||||
private function getBasePath(): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
|
||||
if ($template === 'app') {
|
||||
return base_path() . DS . 'app';
|
||||
} else {
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
return base_path() . DS . 'plugin' . DS . $pluginName . DS . 'app';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件类型获取基础路径
|
||||
*
|
||||
* @param string $fileType 文件类型
|
||||
*
|
||||
* @return string 基础路径
|
||||
*/
|
||||
private function getBasePathForFileType(string $fileType): string
|
||||
{
|
||||
$template = $this->config['template'] ?? 'app';
|
||||
|
||||
// 插件模式下,所有文件都在插件的 app 目录下
|
||||
if ($template !== 'app') {
|
||||
$pluginName = $this->config['namespace'] ?? '';
|
||||
return base_path() . DS . 'plugin' . DS . $pluginName . DS . 'app';
|
||||
}
|
||||
|
||||
// app 模式下,根据文件类型选择不同的基础路径
|
||||
$adminApiFileTypes = ['controller', 'validate', 'request_form', 'request_query','response'];
|
||||
if (in_array($fileType, $adminApiFileTypes)) {
|
||||
return base_path() . DS . 'app' . DS . 'adminapi';
|
||||
} else {
|
||||
return base_path() . DS . 'app';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import type {{row_type_name}} from './types';
|
||||
|
||||
const baseUrl = '{base_url}';
|
||||
|
||||
export const {class_name}Service = {
|
||||
...BaseService<{row_type_name}>({ baseUrl }),
|
||||
};
|
||||
|
||||
export default {class_name}Service;
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface {row_type_name} {
|
||||
{fields}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"{module_name}": {
|
||||
"common": {
|
||||
"columns": {
|
||||
"index": "序号"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"{dummy_class_name}": {
|
||||
"title": "{class_name_comment}",
|
||||
"search": {
|
||||
"keywords": "关键字",
|
||||
"keywords_placeholder": "请输入{class_name_comment}关键字"
|
||||
},
|
||||
"form": {
|
||||
"create": "新增{class_name_comment}",
|
||||
"edit": "编辑{class_name_comment}",
|
||||
"view": "查看{class_name_comment}"
|
||||
},
|
||||
"messages": {
|
||||
"create_success": "{class_name_comment}创建成功",
|
||||
"create_fail": "{class_name_comment}创建失败",
|
||||
"update_success": "{class_name_comment}更新成功",
|
||||
"update_fail": "{class_name_comment}更新失败",
|
||||
"delete_success": "{class_name_comment}删除成功",
|
||||
"delete_fail": "{class_name_comment}删除失败"
|
||||
},
|
||||
"columns": {
|
||||
{fields}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="{kebab_module_name}-{kebab_class_name}-page art-full-height">
|
||||
<BasicCrud />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { useCrud } from '@/components/crud'
|
||||
import { crudSchema } from './schemas'
|
||||
|
||||
defineOptions({ name: '{class_name}List' })
|
||||
|
||||
const [BasicCrud] = useCrud({
|
||||
...crudSchema(),
|
||||
toolbarActions: [],
|
||||
dropDownToolbarActions: [],
|
||||
tableActions: [],
|
||||
dropDownActions: [],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.{kebab_module_name}-{kebab_class_name}-page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { CrudSchema } from '@/components/crud'
|
||||
import { {service_name} } from '{api_import_path}'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
// crud配置
|
||||
export const crudSchema = (): CrudSchema => {
|
||||
return {
|
||||
api: {service_name}.list,
|
||||
dialogTitle: $t('{lang_key}.title'),
|
||||
exportFileName: $t('{lang_key}.title'),
|
||||
crudApi: {
|
||||
add: {service_name}.create,
|
||||
edit: {service_name}.update,
|
||||
remove: {service_name}.remove,
|
||||
view: {service_name}.get
|
||||
},
|
||||
useCrud: {use_crud},
|
||||
hasAdd: {has_add},
|
||||
hasRemove: {has_remove},
|
||||
hasEdit: {has_edit},
|
||||
hasView: {has_view},
|
||||
addAuth: '{module_name}:{dummy_class_name}:create',
|
||||
removeAuth: '{module_name}:{dummy_class_name}:delete',
|
||||
editAuth: '{module_name}:{dummy_class_name}:update',
|
||||
viewAuth: '{module_name}:{dummy_class_name}:read',
|
||||
rowKey: 'id',
|
||||
|
||||
useSearchForm: true,
|
||||
|
||||
columns: [
|
||||
{
|
||||
type: 'selection'
|
||||
},
|
||||
{table_columns}
|
||||
],
|
||||
|
||||
searchFormSchema: {
|
||||
schema: [
|
||||
{search_form_schema}
|
||||
]
|
||||
},
|
||||
|
||||
dialogProps: {
|
||||
width: '{dialog_width}',
|
||||
{dialog_type}
|
||||
},
|
||||
|
||||
formSchema: {
|
||||
showDefaultActions: false,
|
||||
labelWidth: '100px',
|
||||
schema: [
|
||||
{form_schema}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use app\adminapi\controller\Crud;
|
||||
use app\schema\request\BatchDeleteRequest;
|
||||
use {schema_request_namespace}\{class_name}FormRequest as {class_name}FormRequest;
|
||||
use {schema_request_namespace}\{class_name}QueryRequest as {class_name}QueryRequest;
|
||||
use app\adminapi\middleware\AccessTokenMiddleware;
|
||||
use app\adminapi\middleware\OperationMiddleware;
|
||||
use app\adminapi\middleware\PermissionMiddleware;
|
||||
use {validate_namespace}\{class_name}Validate as {class_name}Validate;
|
||||
use {schema_response_namespace}\{class_name}Response as {class_name}Response;
|
||||
use app\schema\request\IdRequest;
|
||||
use {service_namespace}\{class_name}Service as {class_name}Service;
|
||||
use core\exception\handler\AdminException;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\DataResponse;
|
||||
use madong\swagger\annotation\response\PageResponse;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\Permission;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Request;
|
||||
use support\annotation\Middleware;
|
||||
use WebmanTech\Swagger\DTO\SchemaConstants;
|
||||
|
||||
#[OA\Info(version: '1.0.0', title: 'My App')]
|
||||
#[OA\Server(url: '/adminapi', description: 'localhost')]
|
||||
#[Middleware(AccessTokenMiddleware::class, PermissionMiddleware::class, OperationMiddleware::class)]
|
||||
final class {class_name}Controller extends Crud
|
||||
{
|
||||
public function __construct({class_name}Service $service, {class_name}Validate $validate)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->validate = $validate;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/{package_name_dash}/{class_name_dash}',
|
||||
summary: '列表',
|
||||
tags: ['{class_name}'],
|
||||
x: [
|
||||
SchemaConstants::X_SCHEMA_REQUEST => {class_name}QueryRequest::class,
|
||||
]
|
||||
)]
|
||||
#[Permission(code: '{package_name_lower}:{class_name_lower}:list')]
|
||||
#[PageResponse(schema:{class_name}Response::class,example:[])]
|
||||
public function index(Request $request): \support\Response
|
||||
{
|
||||
return parent::index($request);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/{package_name_dash}/{class_name_dash}/{id}',
|
||||
summary: '详情',
|
||||
tags: ['{class_name}'],
|
||||
x: [
|
||||
SchemaConstants::X_PROPERTY_IN => 'id',
|
||||
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
|
||||
]
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: 'id',
|
||||
description: '{class_name}ID',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', example: 1)
|
||||
)]
|
||||
#[Permission(code: '{package_name_lower}:{class_name_lower}:read')]
|
||||
#[DataResponse(schema: {class_name}Response::class,example:[])]
|
||||
public function show(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$id = $request->route->param('id');
|
||||
$data = $this->service->get($id);
|
||||
if (empty($data)) {
|
||||
throw new AdminException('数据未找到', 400);
|
||||
}
|
||||
|
||||
$dataArray = $data->toArray();
|
||||
return Json::success('ok', $dataArray);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/{package_name_dash}/{class_name_dash}',
|
||||
summary: '添加',
|
||||
tags: ['{class_name}'],
|
||||
x: [
|
||||
SchemaConstants::X_SCHEMA_REQUEST => {class_name}FormRequest::class,
|
||||
]
|
||||
)]
|
||||
#[Permission(code: '{package_name_lower}:{class_name_lower}:create')]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function store(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$data = $request->all();
|
||||
|
||||
if (isset($this->validate) && $this->validate) {
|
||||
if (!$this->validate->scene('store')->check($data)) {
|
||||
throw new \Exception($this->validate->getError());
|
||||
}
|
||||
}
|
||||
$model = $this->service->save($data);
|
||||
if (empty($model)) {
|
||||
throw new AdminException('插入失败');
|
||||
}
|
||||
$pk = $model->getPk();
|
||||
return Json::success('ok', [$pk => $model->getData($pk)]);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Put(
|
||||
path: '/{package_name_dash}/{class_name_dash}/{id}',
|
||||
summary: '更新',
|
||||
tags: ['{class_name}'],
|
||||
x: [
|
||||
SchemaConstants::X_SCHEMA_REQUEST => {class_name}FormRequest::class,
|
||||
]
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: 'id',
|
||||
description: '{class_name}ID',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', example: 1)
|
||||
)]
|
||||
#[Permission(code: '{package_name_lower}:{class_name_lower}:update')]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function update(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$id = $request->route->param('id');
|
||||
$data = $request->all();
|
||||
if (isset($this->validate) && $this->validate) {
|
||||
if (!$this->validate->scene('update')->check($data)) {
|
||||
throw new \Exception($this->validate->getError());
|
||||
}
|
||||
}
|
||||
$this->service->update($id, $data);
|
||||
return Json::success('ok', []);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/{package_name_dash}/{class_name_dash}/export',
|
||||
summary: '导出',
|
||||
tags: ['{class_name}']
|
||||
)]
|
||||
#[Permission(code: '{package_name_lower}:{class_name_lower}:list')]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function export(Request $request): \support\Response
|
||||
{
|
||||
return parent::export($request);
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
path: '/{package_name_dash}/{class_name_dash}/{id}',
|
||||
summary: '删除',
|
||||
tags: ['{class_name}'],
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: 'id',
|
||||
description: '{class_name}ID',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', example: 1)
|
||||
)]
|
||||
#[Permission(code: '{package_name_lower}:{class_name_lower}:delete')]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function destroy(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$id = $request->route->param('id');
|
||||
$data = $request->input('data', []);
|
||||
$data = !empty($id) && $id !== '0' ? $id : $data;
|
||||
if (empty($data)) {
|
||||
throw new AdminException('参数错误');
|
||||
}
|
||||
$this->service->destroy($data);
|
||||
return Json::success('操作成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
path: '/{package_name_dash}/{class_name_dash}',
|
||||
summary: '批量删除',
|
||||
tags: ['{class_name}'],
|
||||
x: [
|
||||
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
|
||||
]
|
||||
)]
|
||||
#[Permission(code: '{package_name_lower}:{class_name_lower}:delete')]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function batchDelete(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$data = $request->input('ids', []);
|
||||
if (empty($data)) {
|
||||
throw new AdminException('参数错误');
|
||||
}
|
||||
$this->service->destroy($data);
|
||||
return Json::success('操作成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use {model_namespace}\{class_name};
|
||||
use core\base\BaseDao;
|
||||
|
||||
/**
|
||||
* {class_name}数据访问层
|
||||
*
|
||||
* @author Mr.April
|
||||
* @since 1.0
|
||||
*/
|
||||
class {class_name}Dao extends BaseDao
|
||||
{
|
||||
|
||||
protected function setModel(): string
|
||||
{
|
||||
return {class_name}::class;
|
||||
}
|
||||
|
||||
protected function getKeywordFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use core\base\BaseModel;
|
||||
{soft_delete_use}
|
||||
|
||||
/**
|
||||
* {class_name}模型
|
||||
*
|
||||
* @author Mr.April
|
||||
* @since 1.0
|
||||
*/
|
||||
class {class_name} extends BaseModel
|
||||
{
|
||||
{soft_delete_trait}
|
||||
|
||||
protected $table = '{table_name}';
|
||||
|
||||
/**
|
||||
* 指示是否自动维护时间戳
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
{soft_delete}
|
||||
|
||||
protected $appends = ['created_date', 'updated_date'];
|
||||
|
||||
protected $fillable = [
|
||||
{fillable_fields}
|
||||
];
|
||||
|
||||
{searchers}
|
||||
|
||||
{relations}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
use app\schema\request\BaseFormRequest;
|
||||
use WebmanTech\DTO\Attributes\ValidationRules;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '{class_name}表单',
|
||||
description: '{class_name}创建和编辑接口共用的表单请求参数'
|
||||
)]
|
||||
class {class_name}FormRequest extends BaseFormRequest
|
||||
{
|
||||
{fields}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
use app\schema\request\BaseQueryRequest;
|
||||
use WebmanTech\DTO\Attributes\ValidationRules;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '{class_name}查询',
|
||||
description: '{class_name}列表查询接口的请求参数'
|
||||
)]
|
||||
class {class_name}QueryRequest extends BaseQueryRequest
|
||||
{
|
||||
|
||||
{fields}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
use madong\swagger\schema\BaseResponseDTO;
|
||||
|
||||
|
||||
#[OA\Schema(
|
||||
title: '{class_name}详情响应模型',
|
||||
description: '{class_name}详情接口的返回数据结构'
|
||||
)]
|
||||
class {class_name}Response extends BaseResponseDTO
|
||||
{
|
||||
{fields}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use {dao_namespace}\{class_name}Dao;
|
||||
use core\base\BaseService;
|
||||
|
||||
/**
|
||||
* {class_name}服务层
|
||||
*
|
||||
* @author Mr.April
|
||||
* @since 1.0
|
||||
*/
|
||||
class {class_name}Service extends BaseService
|
||||
{
|
||||
public function __construct({class_name}Dao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use core\base\BaseValidate;
|
||||
|
||||
/**
|
||||
* {class_name}验证器
|
||||
*
|
||||
* @author Mr.April
|
||||
* @since 1.0
|
||||
*/
|
||||
class {class_name}Validate extends BaseValidate
|
||||
{
|
||||
/**
|
||||
* 验证规则
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
{rules}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $message = [
|
||||
{messages}
|
||||
];
|
||||
|
||||
/**
|
||||
* 场景
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $scene = [
|
||||
'store' => [
|
||||
{store_fields}
|
||||
],
|
||||
'update' => [
|
||||
{update_fields}
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\utils;
|
||||
|
||||
/**
|
||||
* 路径解析器
|
||||
* 负责处理文件路径的解析和生成
|
||||
*/
|
||||
class PathResolver
|
||||
{
|
||||
/**
|
||||
* 生成文件路径
|
||||
* @param string $basePath 基础路径
|
||||
* @param string $moduleName 模块名称
|
||||
* @param string $className 类名
|
||||
* @param string $fileType 文件类型
|
||||
* @param string $extension 文件扩展名
|
||||
* @param bool $isPlugin 是否是插件模式
|
||||
* @return string 文件路径
|
||||
*/
|
||||
public function generatePath(string $basePath, string $moduleName, string $className, string $fileType, string $extension = 'php', bool $isPlugin = false): string
|
||||
{
|
||||
// 前端文件类型
|
||||
$frontendFileTypes = ['api', 'api_model', 'view', 'view_schema', 'lang'];
|
||||
|
||||
// 根据文件类型处理模块名格式
|
||||
$processedModuleName = $moduleName;
|
||||
if (in_array($fileType, $frontendFileTypes)) {
|
||||
// 前端使用连字符格式
|
||||
$processedModuleName = str_replace('_', '-', $moduleName);
|
||||
} else {
|
||||
// 后端使用下划线格式
|
||||
$processedModuleName = str_replace('-', '_', $moduleName);
|
||||
}
|
||||
|
||||
// 确保 className 是大驼峰格式(处理下划线分隔的情况)
|
||||
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', $className)));
|
||||
|
||||
// 插件模式下,service 路径不包含 admin 层级
|
||||
$servicePath = $isPlugin ? 'service/' : 'service/admin/';
|
||||
|
||||
$pathMap = [
|
||||
// 后端文件路径映射
|
||||
'controller' => 'controller/' . ($processedModuleName ? $processedModuleName . '/' : '') . $className . 'Controller.' . $extension,
|
||||
'model' => 'model/' . ($processedModuleName ? $processedModuleName . '/' : '') . $className . '.' . $extension,
|
||||
'service' => $servicePath . ($processedModuleName ? $processedModuleName . '/' : '') . $className . 'Service.' . $extension,
|
||||
'dao' => 'dao/' . ($processedModuleName ? $processedModuleName . '/' : '') . $className . 'Dao.' . $extension,
|
||||
'validate' => 'validate/' . ($processedModuleName ? $processedModuleName . '/' : '') . $className . 'Validate.' . $extension,
|
||||
'response' => 'schema/response/' . ($processedModuleName ? $processedModuleName . '/' : '') . $className . 'Response.' . $extension,
|
||||
'request_form' => 'schema/request/' . ($processedModuleName ? $processedModuleName . '/' : '') . $className . 'FormRequest.' . $extension,
|
||||
'request_query' => 'schema/request/' . ($processedModuleName ? $processedModuleName . '/' : '') . $className . 'QueryRequest.' . $extension,
|
||||
|
||||
// 前端文件路径映射
|
||||
'api' => 'api/' . ($processedModuleName ? $processedModuleName . '/' : '') . 'index.ts',
|
||||
'api_model' => 'api/' . ($processedModuleName ? $processedModuleName . '/' : '') . 'types.ts',
|
||||
'view' => 'views/' . ($processedModuleName ? $processedModuleName . '/' : '') . 'index.vue',
|
||||
'view_schema' => 'views/' . ($processedModuleName ? $processedModuleName . '/' : '') . 'schemas/index.tsx',
|
||||
'lang' => 'lang/zh-cn/' . $processedModuleName . '.json',
|
||||
];
|
||||
|
||||
$relativePath = $pathMap[$fileType] ?? $fileType . '/' . $className . '.' . $extension;
|
||||
return $basePath . DS . $relativePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保路径存在
|
||||
* @param string $path 路径
|
||||
* @return void
|
||||
*/
|
||||
public function ensurePathExists(string $path): void
|
||||
{
|
||||
$dirPath = dirname($path);
|
||||
|
||||
if (!is_dir($dirPath)) {
|
||||
mkdir($dirPath, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取相对路径
|
||||
* @param string $fullPath 完整路径
|
||||
* @param string $basePath 基础路径
|
||||
* @return string 相对路径
|
||||
*/
|
||||
public function getRelativePath(string $fullPath, string $basePath): string
|
||||
{
|
||||
return str_replace($basePath . DS, '', $fullPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace core\generator\utils;
|
||||
|
||||
// 定义目录分隔符常量
|
||||
defined('DS') or define('DS', DIRECTORY_SEPARATOR);
|
||||
|
||||
/**
|
||||
* 模板渲染器
|
||||
* 负责处理模板文件的渲染
|
||||
*/
|
||||
class TemplateRenderer
|
||||
{
|
||||
/**
|
||||
* @var string 模板目录路径
|
||||
*/
|
||||
private string $templateDir;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->templateDir = base_path() . DS . 'core' . DS . 'generator' . DS . 'stubs';
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染模板
|
||||
*
|
||||
* @param string $templatePath 模板路径
|
||||
* @param array $data 模板数据
|
||||
*
|
||||
* @return string 渲染后的内容
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function render(string $templatePath, array $data): string
|
||||
{
|
||||
$templateFile = $this->templateDir . DS . $templatePath;
|
||||
|
||||
if (!file_exists($templateFile)) {
|
||||
throw new \Exception('Template file not found: ' . $templateFile);
|
||||
}
|
||||
|
||||
// 读取模板内容
|
||||
$content = file_get_contents($templateFile);
|
||||
|
||||
// 替换变量
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_bool($value)) {
|
||||
// 对于布尔值,转换为 TypeScript 布尔字面量
|
||||
$boolValue = $value ? 'true' : 'false';
|
||||
// 支持 {key} 格式的占位符
|
||||
$content = str_replace('{' . $key . '}', $boolValue, $content);
|
||||
// 支持 {$key} 格式的占位符
|
||||
$content = str_replace('{$' . $key . '}', $boolValue, $content);
|
||||
} elseif (is_scalar($value)) {
|
||||
// 支持 {key} 格式的占位符
|
||||
$content = str_replace('{' . $key . '}', $value, $content);
|
||||
// 支持 {$key} 格式的占位符
|
||||
$content = str_replace('{$' . $key . '}', $value, $content);
|
||||
} elseif (is_array($value)) {
|
||||
// 处理数组类型的数据,转换为 TypeScript 对象数组
|
||||
$arrayString = $this->arrayToTypeScript($value);
|
||||
$content = str_replace('{{' . $key . '}}', $arrayString, $content);
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 PHP 数组转换为 TypeScript 对象数组字符串
|
||||
* @param array $array PHP 数组
|
||||
* @return string TypeScript 对象数组字符串
|
||||
*/
|
||||
private function arrayToTypeScript(array $array): string
|
||||
{
|
||||
if (empty($array)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($array as $item) {
|
||||
if (is_array($item)) {
|
||||
$items[] = $this->objectToTypeScript($item);
|
||||
}
|
||||
}
|
||||
|
||||
return implode(",\n ", $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 PHP 关联数组转换为 TypeScript 对象字符串
|
||||
* @param array $object PHP 关联数组
|
||||
* @return string TypeScript 对象字符串
|
||||
*/
|
||||
private function objectToTypeScript(array $object): string
|
||||
{
|
||||
$properties = [];
|
||||
foreach ($object as $key => $value) {
|
||||
if (is_bool($value)) {
|
||||
// 对于布尔值,转换为 TypeScript 布尔字面量
|
||||
$properties[] = $key . ': ' . ($value ? 'true' : 'false');
|
||||
} elseif (is_string($value)) {
|
||||
$properties[] = $key . ': \'' . addslashes($value) . '\'';
|
||||
} elseif (is_numeric($value)) {
|
||||
$properties[] = $key . ': ' . $value;
|
||||
} elseif (is_array($value)) {
|
||||
$properties[] = $key . ': ' . $this->objectToTypeScript($value);
|
||||
}
|
||||
}
|
||||
|
||||
return '{' . implode(', ', $properties) . '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模板是否存在
|
||||
* @param string $templatePath 模板路径
|
||||
* @return bool 是否存在
|
||||
*/
|
||||
public function templateExists(string $templatePath): bool
|
||||
{
|
||||
$templateFile = $this->templateDir . DS . $templatePath;
|
||||
return file_exists($templateFile);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user