Files
nl-mall-api/app/Service/core/CodeGenerationService.php
lq 035ab1105c
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
商品、订单部分相关代码生成,下一步先把商品搞完再搞订单
2025-05-20 16:52:47 +08:00

497 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Service\core;
use App\Models\CodeGenerationModel;
use App\Service\common\UtilsService;
use App\Service\MenuService;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class CodeGenerationService
{
const TEXT_TYPE = [
'LONGTEXT',
'TEXT',
'longtext',
'text',
];
const INT_TYPE = [
'INT',
'TINYINT',
'BIGINT',
'SMALLINT',
'FLOAT',
'DECIMAL',
'int',
'tinyint',
'bigint',
'smallint',
'float',
'decimal',
];
private static mixed $_instance;
private ?UtilsService $utils;
private string $uniqid = '';
private string $tempDir = '';
// 大驼峰
private string $upperCamelCase = '';
// 小驼峰
private string $lowerCamelCase = '';
// 下划线
private string $underLineCase = '';
private string $classComment = '';
// - 连接符
private string $dashCase = '';
/**
* @var string 建表sql语句
*/
private string $sql = '';
private array $param = [];
private string $selectField = '"id"';
private string $insertField = '';
private string $updateField = '';
private string $viewForm = '';
private string $viewSearch = '';
private string $searchField = '';
private string $viewData = '';
private array $tmpFile = [];
public function __construct()
{
$this->utils = UtilsService::getInstance();
}
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 下载文件
* @param $id
* @return BinaryFileResponse
* @throws Exception
*/
public function download($id)
{
$model = CodeGenerationModel::find($id);
if (!$model) {
$this->utils->errorThrow('文件不存在');
}
// 返回下载 $path 文件流
return response()->download($model['path'], $model['file_name'], ['Content-Type' => 'application/zip']);
}
/**
* 下载文件信息
* @param $id
* @return mixed
* @throws Exception
*/
public function downloadInfo($id)
{
$model = CodeGenerationModel::find($id);
if (!$model) {
$this->utils->errorThrow('文件不存在');
}
// 返回下载 $path 文件流
return $model;
}
/**
* 创建临时文件夹,复制模板文件,压缩并文件信息
*/
public function downloadUrl()
{
try {
// 将后端模板文件内容写入临时目录中
$tempFilePath = "{$this->tempDir}/api/{$this->upperCamelCase}Controller.php";
Storage::put($tempFilePath, $this->tmpFile['server']['controller']);
$tempFilePath = "{$this->tempDir}/api/{$this->upperCamelCase}Service.php";
Storage::put($tempFilePath, $this->tmpFile['server']['service']);
$tempFilePath = "{$this->tempDir}/api/{$this->upperCamelCase}Model.php";
Storage::put($tempFilePath, $this->tmpFile['server']['model']);
// 创建ZipService实例
$zip = new ZipService();
// 生成压缩文件的路径
$zipFile = storage_path("/app/public/zip/code-generation/{$this->classComment}-{$this->uniqid}.zip");
// 被压缩文件夹的路径
$path = Storage::path($this->tempDir);
// 执行文件压缩
$zip->zip($zipFile, $path);
$insertModel = CodeGenerationModel::create([
'title' => $this->classComment,
'file_name' => "{$this->classComment}-{$this->uniqid}.zip",
'path' => $zipFile,
'params' => json_encode($this->param),
'created_at' => time(),
'updated_at' => time(),
]);
// 删除Store/app/www
Storage::deleteDirectory('www');
return [
'id' => $insertModel->id,
'file_name' => $insertModel->file_name
];
} catch (\Exception $e) {
return response()->json([
'error' => '生成模板文件失败',
'message' => $e->getMessage()
], 500);
}
}
/**
* 初始化参数
* @return void
*/
private function init()
{
// 生成uuid
$this->uniqid = uniqid();
// 处理类名、表名
$this->upperCamelCase = ucfirst($this->param['class_name']);
$this->lowerCamelCase = lcfirst($this->param['class_name']);
$this->underLineCase = strtolower(preg_replace('/([A-Z])/', '_$1', $this->param['class_name']));
$this->dashCase = str_replace('_', '-', $this->underLineCase);
$this->classComment = $this->param['class_comment'];
// 生成文件
// 检查/app/public/zip/code-generation/目录是否存在,如果不存在则创建
if (!Storage::exists('zip/code-generation')) {
Storage::makeDirectory('zip/code-generation');
}
// 生成唯一的临时目录名
$this->tempDir = 'temp/templates/' . $this->upperCamelCase . '-' . $this->uniqid;
Storage::makeDirectory($this->tempDir);
}
/**
* 代码生成主逻辑
*
* @param $params
* @return mixed
* @throws Exception
*/
public function generate($params): mixed
{
$this->param = $params;
DB::beginTransaction();
try {
// 初始化信息
$this->init();
// 生成数据库
$this->genDatabase();
// 生成控制器
$this->genController();
// 生成服务层
$this->genService();
// 生成模型层
$this->genModel();
// 生成路由
$this->genRoute();
// 执行sql
$this->querySql();
// 生成视图
$this->genViews();
// 生成菜单
$this->genMenu();
} catch (Exception $e) {
DB::rollBack();
// ds([
// 'error' => '生成代码失败',
// 'message' => $e->getMessage(),
// 'line' => $e->getLine(),
// 'file' => $e->getFile(),
// ]);
$this->utils->errorThrow($e->getMessage());
}
return $this->downloadUrl();
}
/**
* 生成 数据库表
*
* @return true
* @throws Exception
*/
public function genDatabase(): bool
{
$tableName = config('database.prefix', 'nl_') . $this->underLineCase;
// 默认sql语句
$sql = "CREATE TABLE `{$tableName}` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', ";
$sqlTime =
" `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态 0正常 1禁用',"
. " `created_at` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',"
. "`updated_at` int(11) NOT NULL DEFAULT '0' COMMENT '修改时间',"
. "`deleted_at` int(11) NOT NULL DEFAULT '0' COMMENT '删除时间',";
$sqlEnd = "PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='{$this->classComment}表';";
foreach ($this->param['field'] as $v) {
// 处理所需的字段
$this->checkSelectField($v);
// 处理备注
if (empty($v['comment'])) $v['comment'] = "''";
// 处理是否为空
$v['not_null'] = !$v['not_null'] ? '' : 'NOT';
// 处理默认值
if (array_key_exists('default', $v) && isset($v['default'])) {
$v['default'] = 'DEFAULT ' . "'{$v['default']}'";
} else {
$v['default'] = (strpos($v['type'], 'INT') || in_array($v['type'], self::INT_TYPE)) ? 'DEFAULT ' . 0 : 'DEFAULT ' . '\'\'';
}
if (in_array($v['type'], self::TEXT_TYPE)) {
// 编写sql语句
$sql .= " {$v['name']} {$v['type']} {$v['not_null']} NULL COMMENT '{$v['comment']}', ";
} else {
// 编写sql语句
$sql .= " {$v['name']} {$v['type']}({$v['type_length']}) {$v['default']} {$v['not_null']} NULL COMMENT '{$v['comment']}', ";
}
}
$this->sql = $sql . $sqlTime . $sqlEnd;
return true;
}
/**
* 修改api配置文件
*
* @return void
* @throws Exception
*/
private function genRoute(): void
{
$apiPath = app_path('../routes/api.php');
$apiFile = file_get_contents($apiPath, 'y');
$newApi =
"'{$this->dashCase}' => \App\Http\Controllers\Api\CodeGeneration\\{$this->upperCamelCase}Controller::class, // {$this->classComment} \r\n // 需要登录的路由生成地址";
$updateNewFile = str_replace('// 需要登录的路由生成地址', $newApi, $apiFile);
$updateFile = file_put_contents($apiPath, $updateNewFile);
if (!$updateFile) $this->utils->errorThrow('写入api文件出现了错误');
}
/**
* 生成控制器文件
* @return void
*/
private function genController(): void
{
$this->strReplaceTmp(
app_path('template/api/TemplateController.php'),
app_path("Http/Controllers/Api/CodeGeneration/{$this->upperCamelCase}Controller.php"),
'server',
'controller'
);
}
/**
* 生成服务层文件
* @return void
*/
private function genService(): void
{
$this->strReplaceTmp(
app_path('template/api/TemplateService.php'),
app_path("Service/CodeGeneration/{$this->upperCamelCase}Service.php"),
'server',
'service'
);
}
/**
* 生成模型层文件
* @return void
*/
private function genModel(): void
{
$this->strReplaceTmp(
app_path('template/api/TemplateModel.php'),
app_path("Models/{$this->upperCamelCase}Model.php"),
'server',
'model'
);
}
/**
* 生成前端部分文件
* @return void
*/
private function genViews(): void
{
$appPath = app_path();
$viewList = [
'view-api' => 'api/index.ts',
'view-modal' => 'components/modal.vue',
'view-form' => 'config/form.ts',
'view-search' => 'config/search.ts',
'view-table' => 'config/table.ts',
'view-index' => 'index.vue',
];
foreach ($viewList as $k => $v) {
$this->strReplaceTmp(
$appPath. '/template/view/' . $v,
$this->tempDir. "/view/{$this->dashCase}/" . $v,
'view',
$k
);
}
}
/**
* 生成菜单/授权
*
* @return void
* @throws Exception
*/
private function genMenu(): void
{
MenuService::getInstance()->create([
'title' => $this->classComment,
'icon' => $this->param['icon']?? '',
'name' => $this->upperCamelCase,
'path' => "/{$this->dashCase}",
'component' => "/my-gen/{$this->dashCase}/index",
'pid' => $this->param['pid'],
'sort' => $this->param['sort'],
]);
}
/**
* 模板内容替换
*
* @param $template
* @param $path
* @param $type
* @param $fileKey
* @return void
*/
private function strReplaceTmp($template, $path, $type, $fileKey): void
{
$tmpFile = file_get_contents($template);
$newFile = str_replace('upperCamelCase', $this->upperCamelCase, $tmpFile);
$newFile = str_replace('lowerCamelCase', $this->lowerCamelCase, $newFile);
$newFile = str_replace('dashCase', $this->dashCase, $newFile);
$newFile = str_replace('模板名称', $this->classComment, $newFile);
switch ($fileKey) {
case 'service':
$newFile = str_replace("'tmpSelectField'", $this->selectField . ', "status", "created_at", "updated_at", "deleted_at"', $newFile);
$newFile = str_replace("tmpSearchField", $this->searchField . ',status=', $newFile);
break;
case 'controller':
$newFile = str_replace("'temInsertField'", '"' . $this->insertField . '"', $newFile);
$newFile = str_replace("'temUpdateField'", '"' . $this->updateField . '"', $newFile);
break;
// case 'export':
// $newFile = str_replace("'tmpExportField'", $this->exportField, $newFile);
// $newFile = str_replace('$row->tmpMapField,', $this->exportFieldRow, $newFile);
// break;
// case 'import':
// $newFile = str_replace("'tmpField' => \$row['tmpField'],", $this->importField, $newFile);
// break;
case 'model':
$newFile = str_replace('underLineCase', cc_camel_case_to_underscore($this->underLineCase), $newFile);
break;
case 'view-form':
$newFile = str_replace("// 生成的表单字段", "// 生成的表单字段 \r ". $this->viewForm, $newFile);
break;
case 'view-search':
$newFile = str_replace("// 生成的搜索字段", "// 生成的搜索字段 \r ". $this->viewSearch, $newFile);
break;
case 'view-table':
$newFile = str_replace("// 生成的列表字段", "// 生成的列表字段 \r ". $this->viewData, $newFile);
break;
}
$this->tmpFile[$type][$fileKey] = $newFile;
if (!file_exists($path) && in_array($fileKey, ['controller', 'service', 'model'])) {
$this->utils->createFile($path);
file_put_contents($path, $newFile);
} else {
Storage::put($path, $newFile);
}
}
/**
* 设置默认字段
*
* @param $item
* @return void
*/
private function checkSelectField($item): void
{
if (empty($item)) return;
// 设置列表字段
if ($item['tableShow']) {
$this->selectField .= ', "' . $item['name'] . '"';
}
// 设置表单字段
if ($item['formShow']) {
$isNull = $item['not_null'] == 1 ? "'required'" : "''";
$this->insertField .= !empty($this->insertField) ? '","' . $item['name'] : $item['name'];
$this->updateField .= !empty($this->updateField) ? '","' . $item['name'] : 'id","' . $item['name'];
$this->viewForm .= "{\n fieldName: '" . $item['name'] . "',\n label: '" . $item['comment'] . "',\n component: '" . $item['formType'] . "',\n rules: " . $isNull . ",\n },\n";
}
// 设置搜索字段
if ($item['search']) {
$this->viewSearch .= "{\n fieldName: '" . $item['name'] . "',\n label: '" . $item['comment'] . "',\n component: '" . $item['formType'] . "',\n },\n ";
$this->searchField .= !empty($this->searchField) ? "','" . $item['name'] . '\' => \'' . $item['searchValue'] : $item['name'] . '\' => \'' . $item['searchValue'];
}
// // 设置导出字段
// $this->exportField .= !empty($this->exportField) ? ',"' . $item['comment'] . '"' : '"' . $item['comment'] . '"';
// $this->exportFieldRow .= '$row->' . $item['name'] . ",\n ";
// // 设置导入字段
// $this->importField .= "'" . $item['name'] . "' => \$row['" . $item['comment'] . "'],\n ";
// 设置视图数据字段
$this->viewData .= "{ field: '{$item['name']}', title: '{$item['comment']}' },\n ";
}
/**
* 执行sql
*
* @return void
* @throws Exception
*/
private function querySql(): void
{
try {
DB::statement($this->sql);
} catch (Exception $e) {
$this->utils->errorThrow($e->getMessage());
}
}
}