initialize(); } /** * 初始化MCP服务 */ protected function initialize() { // 确保 logger 不为 null try { $this->logger = Log::channel('mcp'); if (!$this->logger) { // 如果 mcp 通道不存在,使用默认通道 $this->logger = Log::channel('default'); } } catch (\Throwable $e) { // 如果所有通道都失败,创建一个简单的 logger $this->logger = Log::channel('default'); } // 读取MCP配置文件 $this->loadMcpConfig(); return $this; } /** * 加载MCP配置 */ protected function loadMcpConfig() { try { // 对于长时间运行的服务器,设置为无限制 ini_set('max_execution_time', 0); set_time_limit(0); // 忽略用户中断,保持服务器运行 ignore_user_abort(true); $mcpConfig = config('mcp', []); // 设置超时配置 if (isset($mcpConfig['timeout']) && $mcpConfig['timeout'] > 0) { $this->timeout = $mcpConfig['timeout']; } if (isset($mcpConfig['connect_timeout']) && $mcpConfig['connect_timeout'] > 0) { $this->connectTimeout = $mcpConfig['connect_timeout']; } if (isset($mcpConfig['read_timeout']) && $mcpConfig['read_timeout'] > 0) { $this->readTimeout = $mcpConfig['read_timeout']; } // 设置重试配置 if (isset($mcpConfig['retry_attempts']) && $mcpConfig['retry_attempts'] > 0) { $this->retryAttempts = $mcpConfig['retry_attempts']; } if (isset($mcpConfig['retry_delay']) && $mcpConfig['retry_delay'] > 0) { $this->retryDelay = $mcpConfig['retry_delay']; } // 设置调试模式 if (isset($mcpConfig['debug'])) { $this->debug = $mcpConfig['debug']; } // 设置内存限制 if (isset($mcpConfig['memory_limit'])) { ini_set('memory_limit', $mcpConfig['memory_limit']); } // 设置缓冲区大小 if (isset($mcpConfig['buffer_size'])) { $this->bufferSize = $mcpConfig['buffer_size']; } // 设置心跳配置 if (isset($mcpConfig['heartbeat_enabled'])) { $this->heartbeatEnabled = $mcpConfig['heartbeat_enabled']; } if (isset($mcpConfig['heartbeat_interval'])) { $this->heartbeatInterval = $mcpConfig['heartbeat_interval']; } Log::channel('mcp')->info('MCP配置加载成功', [ 'timeout' => $this->timeout, 'connect_timeout' => $this->connectTimeout, 'read_timeout' => $this->readTimeout, 'retry_attempts' => $this->retryAttempts, 'retry_delay' => $this->retryDelay, 'heartbeat_enabled' => $this->heartbeatEnabled ?? false, 'heartbeat_interval' => $this->heartbeatInterval ?? 30 ]); } catch (\Exception $e) { Log::warning('MCP配置加载失败,使用默认配置: ' . $e->getMessage()); } } /** * 启动心跳机制 */ protected function startHeartbeat() { if (!$this->heartbeatEnabled) { return; } // 在后台启动心跳线程 if (function_exists('pcntl_fork')) { $pid = pcntl_fork(); if ($pid == 0) { // 子进程执行心跳 $this->heartbeatLoop(); exit(0); } } else { // Windows系统使用定时器 $this->scheduleHeartbeat(); } } /** * 心跳循环 */ protected function heartbeatLoop() { while (true) { try { // 发送心跳信号 $this->sendHeartbeat(); // 等待下次心跳 sleep($this->heartbeatInterval); } catch (\Exception $e) { Log::channel('mcp')->error('心跳发送失败: ' . $e->getMessage()); sleep(5); // 失败后等待5秒再重试 } } } /** * 发送心跳信号 */ protected function sendHeartbeat() { // 记录心跳日志 if ($this->debug) { Log::debug('发送心跳信号', [ 'timestamp' => time(), 'memory_usage' => memory_get_usage(true) ]); } // 这里可以添加实际的心跳逻辑 // 比如向客户端发送ping消息 } /** * 调度心跳(Windows系统) */ protected function scheduleHeartbeat() { // Windows系统下的心跳调度 if (function_exists('register_tick_function')) { register_tick_function([$this, 'sendHeartbeat']); declare(ticks=1); } } /** * 带重试机制的操作执行 */ protected function executeWithRetry(callable $operation, string $operationName = 'operation') { $attempts = 0; $lastException = null; while ($attempts < $this->retryAttempts) { try { $attempts++; Log::channel('mcp')->info("执行{$operationName},第{$attempts}次尝试"); $result = $operation(); if ($attempts > 1) { Log::channel('mcp')->info("{$operationName}在第{$attempts}次尝试后成功"); } return $result; } catch (\Exception $e) { $lastException = $e; Log::warning("{$operationName}第{$attempts}次尝试失败: " . $e->getMessage()); if ($attempts < $this->retryAttempts) { $delay = $this->retryDelay * pow(1.5, $attempts - 1); // 指数退避 Log::channel('mcp')->info("等待{$delay}ms后重试"); usleep($delay * 1000); } } } Log::channel('mcp')->error("{$operationName}在{$this->retryAttempts}次尝试后仍然失败"); throw $lastException; } /** * 构建MCP服务器 */ protected function buildServer() { if ($this->server !== null) { return $this->server; } // 创建容器并注册服务实例 $container = new BasicContainer(); // 确保 logger 有效后再注册 if ($this->logger) { $container->set(\Psr\Log\LoggerInterface::class, $this->logger); } $container->set(self::class, $this); $builder = Server::make() ->withServerInfo($this->name, $this->version); // 只在 logger 有效时设置 if ($this->logger) { $builder->withLogger($this->logger); } $this->server = $builder->withContainer($container) ->withTool([self::class, 'handleDbQuery'], 'db-query', '执行数据库查询操作(仅支持SELECT语句)') ->withTool([self::class, 'handleSysConfig'], 'sys-config', '获取系统配置信息') ->withTool([self::class, 'handleWriteLog'], 'write-log', '写入系统日志') ->withTool([self::class, 'handleFileOperation'], 'file-operation', '文件读写操作') ->withTool([self::class, 'handleUserManagement'], 'user-management', '用户管理相关操作') ->withTool([self::class, 'handleSystemInfo'], 'system-info', '获取系统运行信息') ->withTool([self::class, 'handleCreateController'], 'controller', '生成webman控制器文件') ->withTool([self::class, 'handleCreateModel'], 'model', '生成webman模型文件') ->withTool([self::class, 'handleCreateView'], 'view', '生成webman视图文件') ->withTool([self::class, 'handleCreateJs'], 'js', '生成webman JS文件') ->withTool([self::class, 'handleCreateApi'], 'api', '生成webman API接口文件') ->withTool([self::class, 'handleCurd'], 'curd', '生成webman CURD模块') ->withTool([self::class, 'handleAddon'], 'addon', '生成webman 插件模块') ->withTool([self::class, 'handleMenu'], 'menu', '生成webman 菜单模块') ->withTool([self::class, 'handleCreateTable'], 'table', '创建数据库表格, 支持字段信息、类型、注释等') ->withTool([self::class, 'handleThinkCommand'], 'think-command', '执行webman框架命令') ->withTool([self::class, 'handleMcpCommand'], 'mcp-command', '执行MCP专用命令') ->withTool([self::class, 'handleWebmanCommand'], 'webman-command', '执行webman框架命令') ->withPrompt([self::class, 'handleWithPrompt'], 'with-prompt', '通过自然语言描述生成数据库表、控制器、模型等') ->withResource([self::class, 'handleConfigResource'], 'config://system', 'config-system', '系统配置信息资源', 'application/json') ->withResource([self::class, 'handleSchemaResource'], 'schema://database', 'schema-database', '数据库表结构信息资源', 'application/json') ->build(); return $this->server; } /** * 处理数据库查询 * @param string $query SQL查询语句 * @param array $params 查询参数 * @return array */ public function handleDbQuery(string $query, array $params = []): array { try { if (empty($query)) { throw new \Exception('SQL查询语句不能为空'); } $trimmedQuery = trim($query); // 安全检查:允许SELECT查询和表结构查看语句 if (!preg_match('/^\s*(select|show|describe|desc)\s+/i', $trimmedQuery)) { throw new \Exception('出于安全考虑,只允许执行SELECT、SHOW、DESCRIBE等查询语句'); } $result = Db::query($query, $params); return [ 'success' => true, 'data' => $result, 'count' => count($result), 'message' => '查询执行成功' ]; } catch (\Exception $e) { Log::channel('mcp')->error('MCP数据库查询错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 处理配置获取 * @param string $key 配置键名(可选) * @return array */ public function handleSysConfig(string $key = ''): array { try { if (empty($key)) { // 返回常用配置的概览 return [ 'app' => [ 'debug' => config('app.debug', false), 'default_timezone' => config('app.default_timezone', 'Asia/Shanghai'), 'default_lang' => config('app.default_lang', 'zh-cn'), ], 'database' => [ 'type' => config('thinkorm.connections.mysql.type', 'mysql'), 'hostname' => config('thinkorm.connections.mysql.hostname', '127.0.0.1'), 'database' => config('thinkorm.connections.mysql.database', ''), ], 'cache' => [ 'default' => config('think-cache.app.default', 'redis'), 'stores' => array_keys(config('cache.stores', [])), ] ]; } else { return ['value' => config($key), 'key' => $key]; } } catch (\Exception $e) { Log::channel('mcp')->error('MCP配置获取错误: ' . $e->getMessage()); throw $e; } } /** * 处理日志写入 * @param string $message 日志消息 * @param string $level 日志级别 * @param array $context 上下文数据 * @return string */ public function handleWriteLog(string $message, string $level = 'info', array $context = []): string { try { if (empty($message)) { throw new \Exception('日志消息不能为空'); } // 支持的日志级别 $allowedLevels = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency']; if (!in_array($level, $allowedLevels)) { $level = 'info'; } Log::channel('mcp')->addRecord($level, $message, $context); return "日志记录成功 [级别: {$level}]"; } catch (\Exception $e) { Log::channel('mcp')->error('MCP日志写入错误: ' . $e->getMessage()); throw $e; } } /** * 处理文件操作 * @param string $operation 操作类型 * @param string $filepath 文件路径 * @return array */ public function handleFileOperation(string $operation, string $filepath): array { try { if (empty($operation) || empty($filepath)) { throw new \Exception('操作类型和文件路径不能为空'); } // 安全检查:限制文件路径范围 $allowedPaths = [ runtime_path(), public_path(), config_path(), ]; $isAllowed = false; $realFilePath = realpath($filepath); if ($realFilePath) { foreach ($allowedPaths as $allowedPath) { $realAllowedPath = realpath($allowedPath); if ($realAllowedPath && strpos($realFilePath, $realAllowedPath) === 0) { $isAllowed = true; break; } } } if (!$isAllowed) { throw new \Exception('文件路径不在允许的范围内'); } switch ($operation) { case 'read': if (!file_exists($filepath)) { throw new \Exception('文件不存在'); } return [ 'content' => file_get_contents($filepath), 'size' => filesize($filepath), 'modified' => date('Y-m-d H:i:s', filemtime($filepath)) ]; case 'exists': return ['exists' => file_exists($filepath)]; case 'info': if (!file_exists($filepath)) { throw new \Exception('文件不存在'); } return [ 'size' => filesize($filepath), 'modified' => date('Y-m-d H:i:s', filemtime($filepath)), 'is_file' => is_file($filepath), 'is_dir' => is_dir($filepath), 'is_readable' => is_readable($filepath), 'is_writable' => is_writable($filepath) ]; default: throw new \Exception('不支持的操作类型: ' . $operation); } } catch (\Exception $e) { Log::channel('mcp')->error('MCP文件操作错误: ' . $e->getMessage()); throw $e; } } /** * 处理用户管理 * @param string $action 操作类型 * @param int $userId 用户ID(可选) * @param int $limit 返回数量限制(可选) * @return array */ public function handleUserManagement(string $action, int $userId = 0, int $limit = 10): array { try { switch ($action) { case 'list': $users = Db::name('admin') ->field('id,username,email,mobile,created_at,status') ->limit($limit) ->select(); return [ 'users' => $users->toArray(), 'count' => count($users) ]; case 'info': if (!$userId) { throw new \Exception('用户ID不能为空'); } $user = Db::name('admin') ->field('id,username,nickname,email,mobile,created_at,status') ->where('id', $userId) ->find(); if (!$user) { throw new \Exception('用户不存在'); } return $user; case 'count': $total = Db::name('admin')->count(); $active = Db::name('admin')->where('status', 1)->count(); return [ 'total' => $total, 'active' => $active, 'inactive' => $total - $active ]; default: throw new \Exception('不支持的操作类型: ' . $action); } } catch (\Exception $e) { Log::channel('mcp')->error('MCP用户管理错误: ' . $e->getMessage()); throw $e; } } /** * 处理系统信息 * @param string $type 信息类型 * @return array */ public function handleSystemInfo(string $type = 'general'): array { try { switch ($type) { case 'general': return [ 'php_version' => PHP_VERSION, 'framework' => 'Webman', 'framework_version' => '1.0.0', 'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown', 'memory_limit' => ini_get('memory_limit'), 'max_execution_time' => ini_get('max_execution_time'), 'upload_max_filesize' => ini_get('upload_max_filesize'), 'post_max_size' => ini_get('post_max_size'), ]; case 'database': $version = 'Unknown'; try { $versionResult = Db::query('SELECT VERSION() as version'); $version = $versionResult[0]['version'] ?? 'Unknown'; } catch (\Exception $e) { // 数据库连接失败时使用默认值 } return [ 'type' => config('thinkorm.connections.mysql.type', 'mysql'), 'version' => $version, 'charset' => config('thinkorm.connections.mysql.charset', 'utf8mb4'), 'collation' => config('thinkorm.connections.mysql.collation', 'utf8mb4_general_ci'), ]; case 'performance': return [ 'memory_usage' => $this->formatBytes(memory_get_usage(true)), 'memory_peak' => $this->formatBytes(memory_get_peak_usage(true)), 'included_files' => count(get_included_files()), ]; case 'cache': return [ 'default_driver' => config('think-cache.app.default', 'redis'), 'opcache_enabled' => function_exists('opcache_get_status') && opcache_get_status() !== false, 'redis_available' => extension_loaded('redis'), 'memcached_available' => extension_loaded('memcached'), ]; default: throw new \Exception('不支持的系统信息类型: ' . $type); } } catch (\Exception $e) { Log::channel('mcp')->error('MCP系统信息错误: ' . $e->getMessage()); throw $e; } } /** * 格式化字节数 * @param int $bytes * @return string */ private function formatBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB']; $index = 0; while ($bytes >= 1024 && $index < count($units) - 1) { $bytes /= 1024; $index++; } return round($bytes, 2) . ' ' . $units[$index]; } /** * 处理配置资源 * @return string */ public function handleConfigResource(): string { $configs = [ 'app' => config('app', []), 'database' => [ 'type' => config('thinkorm.connections.mysql.type', 'mysql'), 'charset' => config('thinkorm.connections.mysql.charset', 'utf8mb4'), 'debug' => config('thinkorm.connections.mysql.trigger_sql', true), ], 'cache' => config('think-cache.app', []), 'session' => config('session', []), 'log' => config('log', []), ]; return json_encode($configs, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); } /** * 处理数据库模式资源 * @return string */ public function handleSchemaResource(): string { try { // 获取所有表名 $tables = Db::query('SHOW TABLES'); $schema = []; foreach ($tables as $table) { $tableName = array_values($table)[0]; // 获取表结构 $columns = Db::query("SHOW COLUMNS FROM `{$tableName}`"); $schema[$tableName] = $columns; } return json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); } catch (\Exception $e) { Log::channel('mcp')->error('MCP数据库模式获取错误: ' . $e->getMessage()); return json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE); } } /** * 设置日志记录器 * @param $logger * @return $this */ public function setLogger($logger) { $this->logger = $logger; return $this; } /** * 获取当前配置信息 * @return array */ public function getConfig() { return [ 'timeout' => $this->timeout, 'connect_timeout' => $this->connectTimeout, 'read_timeout' => $this->readTimeout, 'retry_attempts' => $this->retryAttempts, 'retry_delay' => $this->retryDelay, 'debug' => $this->debug ]; } /** * 启动MCP服务器(STDIO传输) */ public function startWithStdio() { try { // 启动心跳机制 $this->startHeartbeat(); $server = $this->buildServer(); $transport = new StdioServerTransport(); Log::channel('mcp')->info('MCP STDIO服务器启动成功'); $server->listen($transport); } catch (\Exception $e) { Log::channel('mcp')->error('MCP STDIO服务器启动失败: ' . $e->getMessage()); throw $e; } } /** * 使用指定传输启动MCP服务器 */ public function startWithTransport($transport) { try { // 启动心跳机制 $this->startHeartbeat(); $server = $this->buildServer(); $server->listen($transport); Log::channel('mcp')->info('MCP服务器启动成功'); } catch (\Exception $e) { Log::channel('mcp')->error('MCP服务器启动失败: ' . $e->getMessage()); throw $e; } } /** * 使用SSE传输启动MCP服务器 */ public function startWithSse(string $host = '127.0.0.1', int $port = 8080, string $mcpPath = 'mcp') { try { // 启动心跳机制 $this->startHeartbeat(); $server = $this->buildServer(); $transport = new \PhpMcp\Server\Transports\StreamableHttpServerTransport($host, $port, $mcpPath); Log::channel('mcp')->info("MCP SSE服务器启动成功,监听地址: http://{$host}:{$port}/{$mcpPath}"); $server->listen($transport); } catch (\Exception $e) { Log::channel('mcp')->error('MCP SSE服务器启动失败: ' . $e->getMessage()); throw $e; } } /** * 使用HTTP传输启动MCP服务器 */ public function startWithHttp(string $host = '127.0.0.1', int $port = 8080, string $mcpPath = 'mcp') { try { // 启动心跳机制 $this->startHeartbeat(); $server = $this->buildServer(); $transport = new \PhpMcp\Server\Transports\HttpServerTransport($host, $port, $mcpPath); Log::channel('mcp')->info("MCP HTTP服务器启动成功,监听地址: http://{$host}:{$port}/{$mcpPath}"); $server->listen($transport); } catch (\Exception $e) { Log::channel('mcp')->error('MCP HTTP服务器启动失败: ' . $e->getMessage()); throw $e; } } /** * 获取服务器实例 * @return Server|null */ public function getServer() { return $this->buildServer(); } /** * 获取服务信息 * @return array */ public function getServiceInfo(): array { return [ 'name' => $this->name, 'version' => $this->version, 'tools' => 16, // 16个工具(新增ThinkPHP命令工具) 'resources' => 3, // 3个资源 'prompt' => 1, // 1个提示词 'status' => 'ready', 'config' => $this->getConfig() ]; } /** * 生成webman控制器文件 * @param string $module 模块名称 (admin/api/frontend等) * @param string $controller 控制器名称 * @param array $fields 字段信息 (可选) * @param string $description 控制器描述 (可选) * @return array */ public function handleCreateController(string $module, string $controller, array $fields = [], string $description = ''): array { try { // 生成控制器类名 $controllerClass = ucfirst($controller); $controllerPath = "app/{$module}/controller/{$controllerClass}.php"; if ($module == 'admin') { $controllerPath = "plugin/admin/app/controller/{$controllerClass}.php"; } // 检查文件是否已存在 if (file_exists($controllerPath)) { return [ 'success' => false, 'error' => "控制器文件 {$controllerPath} 已存在" ]; } // 生成控制器内容 $controllerContent = $this->CreateControllerContent($module, $controllerClass, $fields, $description); // 确保目录存在 $dir = dirname($controllerPath); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // 写入文件 if (file_put_contents($controllerPath, $controllerContent)) { Log::channel('mcp')->info("webman控制器生成成功: {$controllerPath}"); return [ 'success' => true, 'message' => '控制器生成成功', 'file_path' => $controllerPath, 'content' => $controllerContent ]; } else { throw new \Exception('文件写入失败'); } } catch (\Exception $e) { Log::channel('mcp')->error('webman控制器生成错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 生成模型文件 * @param string $modelName 模型名称 * @param array $fields 字段信息 (可选) * @param string $tableName 表名 (可选,默认使用模型名) * @param string $description 模型描述 (可选) * @return array */ public function handleCreateModel(string $modelName, array $fields = [], string $tableName = '', string $description = ''): array { try { // 生成模型类名 $modelClass = ucfirst($modelName); $modelPath = "app/model/{$modelClass}.php"; // 检查文件是否已存在 if (file_exists($modelPath)) { return [ 'success' => false, 'error' => "模型文件 {$modelPath} 已存在" ]; } // 如果没有指定表名,使用模型名 if (empty($tableName)) { $tableName = strtolower($modelName); } // 生成模型内容 $modelContent = $this->CreateModelContent($modelClass, $tableName, $fields, $description); // 确保目录存在 $dir = dirname($modelPath); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // 写入文件 if (file_put_contents($modelPath, $modelContent)) { Log::channel('mcp')->info("模型生成成功: {$modelPath}"); return [ 'success' => true, 'message' => '模型生成成功', 'file_path' => $modelPath, 'content' => $modelContent ]; } else { throw new \Exception('文件写入失败'); } } catch (\Exception $e) { Log::channel('mcp')->error('模型生成错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 生成控制器内容 * @param string $module 模块名称 * @param string $controllerClass 控制器类名 * @param array $fields 字段信息 * @param string $description 描述 * @return string */ private function CreateControllerContent(string $module, string $controllerClass, array $fields = [], string $description = ''): string { $description = $description ?: $controllerClass; $tpl = 'app/mcp/tpl/controller/' . $module . '.tpl'; if (!file_exists($tpl)) { $tpl = 'app/mcp/tpl/controller/default.tpl'; } $content = view($tpl, [ 'description' => $description, 'controllerClass' => $controllerClass, ]); return $content; } /** * 生成模型内容 * @param string $modelClass 模型类名 * @param string $tableName 表名 * @param array $fields 字段信息 * @param string $description 描述 * @return string */ private function CreateModelContent(string $modelClass, string $tableName, array $fields = [], string $description = ''): string { $description = $description ?: $modelClass; // 生成字段定义 $fieldDefinitions = ''; if (!empty($fields)) { $fieldDefinitions = " // 字段定义\n"; foreach ($fields as $field) { $fieldName = $field['name'] ?? ''; $fieldType = $field['type'] ?? 'string'; $fieldComment = $field['comment'] ?? ''; if ($fieldName) { $fieldDefinitions .= " protected \$" . $fieldName . " = ''; // {$fieldComment}\n"; } } } $content = " false, 'error' => "表 {$tableName} 已存在" ]; } // 生成建表SQL $createSql = $this->CreateCreateTableSql($tableName, $fields, $tableComment, $engine, $charset); // 执行建表SQL $result = Db::execute($createSql); if ($result !== false) { Log::channel('mcp')->info("数据库表创建成功: {$tableName}"); return [ 'success' => true, 'message' => '表创建成功', 'table_name' => $tableName, 'sql' => $createSql, 'fields_count' => count($fields) ]; } else { throw new \Exception('建表SQL执行失败'); } } catch (\Exception $e) { Log::channel('mcp')->error('创建数据库表出现错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 生成建表SQL * @param string $tableName 表名 * @param array $fields 字段信息 * @param string $tableComment 表注释 * @param string $engine 存储引擎 * @param string $charset 字符集 * @return string */ private function CreateCreateTableSql(string $tableName, array $fields, string $tableComment = '', string $engine = 'InnoDB', string $charset = 'utf8mb4'): string { $sql = "CREATE TABLE `{$tableName}` (\n"; $fieldDefinitions = []; $primaryKey = null; foreach ($fields as $field) { $fieldName = $field['name'] ?? ''; $fieldType = $field['type'] ?? 'varchar(255)'; $fieldLength = $field['length'] ?? ''; $fieldDefault = $field['default'] ?? ''; $fieldComment = $field['comment'] ?? ''; $fieldNull = isset($field['null']) && $field['null'] ? 'NULL' : 'NOT NULL'; $fieldAutoIncrement = isset($field['auto_increment']) && $field['auto_increment'] ? 'AUTO_INCREMENT' : ''; $fieldPrimary = isset($field['primary']) && $field['primary'] ? 'PRIMARY KEY' : ''; // 构建字段定义 $fieldDef = " `{$fieldName}` {$fieldType}"; // 添加长度 if (!empty($fieldLength) && !in_array(strtolower($fieldType), ['text', 'longtext', 'mediumtext', 'tinytext', 'blob', 'longblob', 'mediumblob', 'tinyblob'])) { $fieldDef .= "({$fieldLength})"; } // 添加默认值 if ($fieldDefault !== '') { if (is_string($fieldDefault)) { $fieldDef .= " DEFAULT '{$fieldDefault}'"; } else { $fieldDef .= " DEFAULT {$fieldDefault}"; } } // 添加NULL/NOT NULL $fieldDef .= " {$fieldNull}"; // 添加自增 if (!empty($fieldAutoIncrement)) { $fieldDef .= " {$fieldAutoIncrement}"; } // 添加注释 if (!empty($fieldComment)) { $fieldDef .= " COMMENT '{$fieldComment}'"; } // 添加主键 if (!empty($fieldPrimary)) { $fieldDef .= " {$fieldPrimary}"; $primaryKey = $fieldName; } $fieldDefinitions[] = $fieldDef; } // 添加默认字段(如果不存在) $hasId = false; $hasCreateTime = false; $hasUpdateTime = false; $hasDeleteTime = false; foreach ($fields as $field) { if ($field['name'] === 'id') $hasId = true; if ($field['name'] === 'created_at') $hasCreateTime = true; if ($field['name'] === 'updated_at') $hasUpdateTime = true; if ($field['name'] === 'deleted_at') $hasDeleteTime = true; } // 如果没有ID字段,添加默认ID字段 if (!$hasId) { array_unshift($fieldDefinitions, " `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'"); } // 添加默认时间字段 if (!$hasCreateTime) { $fieldDefinitions[] = " `created_at` int(11) DEFAULT NULL COMMENT '创建时间'"; } if (!$hasUpdateTime) { $fieldDefinitions[] = " `updated_at` int(11) DEFAULT NULL COMMENT '更新时间'"; } if (!$hasDeleteTime) { $fieldDefinitions[] = " `deleted_at` int(11) DEFAULT NULL COMMENT '删除时间'"; } $sql .= implode(",\n", $fieldDefinitions); $sql .= "\n)"; // 添加表注释 if (!empty($tableComment)) { $sql .= " COMMENT='{$tableComment}'"; } // 添加存储引擎和字符集 $sql .= " ENGINE={$engine} DEFAULT CHARSET={$charset}"; return $sql; } /** * 获取支持的字段类型 * @return array */ public function getSupportedFieldTypes(): array { return [ '整数类型' => [ 'int(11)' => '整数类型,11位长度', 'bigint(20)' => '大整数类型,20位长度', 'tinyint(1)' => '小整数类型,1位长度', 'smallint(6)' => '小整数类型,6位长度', 'mediumint(9)' => '中等整数类型,9位长度' ], '字符串类型' => [ 'varchar(255)' => '可变长度字符串,最大255字符', 'char(50)' => '固定长度字符串,50字符', 'text' => '长文本类型', 'longtext' => '超长文本类型', 'mediumtext' => '中等长度文本类型', 'tinytext' => '短文本类型' ], '浮点数类型' => [ 'decimal(10,2)' => '定点数类型,10位总长度,2位小数', 'float' => '单精度浮点数', 'double' => '双精度浮点数' ], '日期时间类型' => [ 'datetime' => '日期时间类型', 'timestamp' => '时间戳类型', 'date' => '日期类型', 'time' => '时间类型', 'year' => '年份类型' ], '其他类型' => [ 'json' => 'JSON数据类型', 'blob' => '二进制大对象', 'longblob' => '长二进制大对象', 'mediumblob' => '中等二进制大对象', 'tinyblob' => '小二进制大对象' ] ]; } /** * 处理CRUD生成,基于fun/curd/Curd.php功能 * @param string $tableName 表名 * @param string $module 模块名(admin/frontend/api) * @param array $fields 字段信息 * @param string $description 描述 * @param array $options 其他选项 * @return array */ public function handleCurd(string $tableName, string $module = 'admin', array $fields = [], string $description = '', array $options = []): array { try { // 构建命令行参数 $parameters = [ '--table=' . $tableName, '--app=' . $module, '--controller=' . $this->convertTableNameToControllerName($tableName), '--model=' . $this->convertTableNameToModelName($tableName), '--validate=' . $this->convertTableNameToModelName($tableName), ]; // 添加可选参数 if (!empty($options['force'])) { $parameters[] = '--force=1'; } if (!empty($options['menu'])) { $parameters[] = '--menu=1'; } if (!empty($options['menuname'])) { $parameters[] = '--menuname=' . $options['menuname']; } if (!empty($options['common'])) { $parameters[] = '--common=1'; } // webman 不支持 ThinkPHP Console,使用文件生成方式 $content = $this->generateCurdFiles($tableName, $module, $fields, $description, $options); // 检查执行结果 if (strpos($content, 'success') !== false || strpos($content, 'make success') !== false) { return [ 'success' => true, 'message' => 'CRUD模块生成成功', 'data' => [ 'table' => $tableName, 'module' => $module, 'controller' => $this->convertTableNameToControllerName($tableName), 'model' => $this->convertTableNameToModelName($tableName), 'output' => $content ] ]; } else { return [ 'success' => false, 'error' => 'CRUD模块生成失败', 'output' => $content ]; } } catch (\Exception $e) { Log::channel('mcp')->error('CRUD生成错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 处理插件管理,基于fun/curd/Addon.php功能 * @param string $action 操作类型(create/install/uninstall/enable/disable) * @param string $addonName 插件名称 * @param array $options 其他选项 * @return array */ public function handleAddon(string $action, string $addonName = '', array $options = []): array { try { // 构建命令行参数 $parameters = []; switch ($action) { case 'create': if (empty($addonName)) { throw new \Exception('插件名称不能为空'); } $parameters = [ '--app=' . $addonName, '--title=' . ($options['title'] ?? $addonName), '--description=' . ($options['description'] ?? $addonName), '--author=' . ($options['author'] ?? 'webman'), '--ver=' . ($options['version'] ?? '1.0.0'), '--requires=' . ($options['requires'] ?? '1.0.0'), ]; if (!empty($options['force'])) { $parameters[] = '--force=1'; } break; case 'install': if (empty($addonName)) { throw new \Exception('插件名称不能为空'); } $parameters = [ '--install=1', '--app=' . $addonName ]; break; case 'uninstall': if (empty($addonName)) { throw new \Exception('插件名称不能为空'); } $parameters = [ '--delete=1', '--force=1', '--app=' . $addonName, ]; break; case 'enable': $parameters = [ '--app=' . $addonName, '--enable=1', ]; break; case 'disable': $parameters = [ '--app=' . $addonName, '--disable=1', ]; break; default: throw new \Exception('不支持的操作类型: ' . $action); } // webman 不支持 ThinkPHP Console,使用文件生成方式 $content = $this->generateAddonFiles($addonName, $action, $options); // 检查执行结果 if (strpos($content, 'success') !== false || strpos($content, 'make success') !== false) { return [ 'success' => true, 'message' => "插件{$action}操作成功", 'data' => [ 'action' => $action, 'addon_name' => $addonName, 'output' => $content ] ]; } else { return [ 'success' => false, 'error' => "插件{$action}操作失败", 'output' => $content ]; } } catch (\Exception $e) { Log::channel('mcp')->error('插件管理错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 处理菜单管理,基于fun/curd/Menu.php功能 * @param string $action 操作类型(create/delete) * @param array $menuData 菜单数据 * @param array $options 其他选项 * @return array */ public function handleMenu(string $action, array $menuData = [], array $options = []): array { try { // 构建命令行参数 $parameters = []; switch ($action) { case 'create': if (empty($menuData['controller'])) { throw new \Exception('控制器名称不能为空'); } $parameters = [ '--controller=' . $menuData['controller'], '--app=' . ($menuData['app'] ?? 'admin'), ]; if (!empty($menuData['menuname'])) { $parameters[] = '--menuname=' . $menuData['menuname']; } if (!empty($options['force'])) { $parameters[] = '--force=1'; } break; case 'delete': if (empty($menuData['controller'])) { throw new \Exception('控制器名称不能为空'); } $parameters = [ '--controller=' . $menuData['controller'], '--app=' . ($menuData['app'] ?? 'admin'), '--delete=1', '--force=1' ]; break; default: throw new \Exception('不支持的操作类型: ' . $action); } // webman 不支持 ThinkPHP Console,使用文件生成方式 $content = $this->generateMenuFiles($menuData, $action, $options); // 检查执行结果 if (strpos($content, 'success') !== false || strpos($content, 'make success') !== false) { return [ 'success' => true, 'message' => "菜单{$action}操作成功", 'data' => [ 'action' => $action, 'menu_data' => $menuData, 'output' => $content ] ]; } else { return [ 'success' => false, 'error' => "菜单{$action}操作失败", 'output' => $content ]; } } catch (\Exception $e) { Log::channel('mcp')->error('菜单管理错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 通过自然语言描述生成数据库表、控制器、模型等 * @param string $prompt 自然语言描述 * @param string $type 生成类型 (table/controller/model/js/api/view/all) * @return array */ public function handleWithPrompt(string $prompt, string $type = 'all'): array { try { if (empty($prompt)) { throw new \Exception('描述不能为空'); } // 解析提示词 $parsedData = $this->parsePrompt($prompt); $results = []; // 根据类型生成相应的内容 if (in_array($type, ['table', 'all'])) { if (!empty($parsedData['table'])) { $tableResult = $this->handleCreateTable( $parsedData['table']['name'], $parsedData['table']['fields'], $parsedData['table']['comment'] ?? '', 'InnoDB', 'utf8mb4' ); $results['table'] = $tableResult; } } if (in_array($type, ['controller', 'all'])) { if (!empty($parsedData['controller'])) { $controllerResult = $this->handleCreateController( $parsedData['controller']['module'] ?? 'admin', $parsedData['controller']['name'], $parsedData['controller']['fields'] ?? [], $parsedData['controller']['description'] ?? '' ); $results['controller'] = $controllerResult; } } if (in_array($type, ['model', 'all'])) { if (!empty($parsedData['model'])) { $modelResult = $this->handleCreateModel( $parsedData['model']['name'], $parsedData['model']['fields'] ?? [], $parsedData['model']['table'] ?? '', $parsedData['model']['description'] ?? '' ); $results['model'] = $modelResult; } } if (in_array($type, ['js', 'all'])) { if (!empty($parsedData['js'])) { $jsResult = $this->handleCreateJs( $parsedData['js']['module'] ?? 'admin', $parsedData['js']['name'], $parsedData['js']['fields'] ?? [], $parsedData['js']['description'] ?? '' ); $results['js'] = $jsResult; } } if (in_array($type, ['api', 'all'])) { if (!empty($parsedData['api'])) { $apiResult = $this->handleCreateApi( $parsedData['api']['module'] ?? 'api', $parsedData['api']['name'], $parsedData['api']['fields'] ?? [], $parsedData['api']['description'] ?? '' ); $results['api'] = $apiResult; } } if (in_array($type, ['view', 'all'])) { if (!empty($parsedData['view'])) { $viewResult = $this->handleCreateView( $parsedData['view']['module'] ?? 'admin', $parsedData['view']['name'], $parsedData['view']['fields'] ?? [], $parsedData['view']['description'] ?? '' ); $results['view'] = $viewResult; } } if (in_array($type, ['addon', 'all'])) { if (!empty($parsedData['addon'])) { $addonResult = $this->handleAddon( 'create', $parsedData['addon']['name'], $parsedData['addon']['options'] ?? [] ); $results['addon'] = $addonResult; } } if (in_array($type, ['curd', 'all'])) { if (!empty($parsedData['curd'])) { $curdResult = $this->handleCurd( $parsedData['curd']['name'], $parsedData['curd']['module'] ?? 'admin', $parsedData['curd']['fields'] ?? [], $parsedData['curd']['description'] ?? '', $parsedData['curd']['options'] ?? [] ); $results['curd'] = $curdResult; } } if (in_array($type, ['menu', 'all'])) { if (!empty($parsedData['menu'])) { $menuResult = $this->handleMenu( 'create', $parsedData['menu']['data'] ?? [], $parsedData['menu']['options'] ?? [] ); $results['menu'] = $menuResult; } } //如果这里面都没有 那么执行其他操作 if (empty($results)) { $results = $this->handleOtherOperation($prompt); } return [ 'success' => true, 'message' => '通过提示词生成成功', 'parsed_data' => $parsedData, 'results' => $results ]; } catch (\Exception $e) { Log::channel('mcp')->error('webman withPrompt错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 解析自然语言提示词 * @param string $prompt * @return array */ private function parsePrompt(string $prompt): array { $parsedData = [ 'table' => null, 'controller' => null, 'model' => null, 'js' => null, 'api' => null, 'view' => null, 'addon' => null, 'curd' => null, 'menu' => null ]; // 转换为小写便于匹配 $lowerPrompt = strtolower($prompt); // 提取表名 $tableName = null; if (preg_match('/(?:创建|生成|建立).*?(?:表|table).*?[名为|叫|是]\s*([a-zA-Z_][a-zA-Z0-9_]*)/', $lowerPrompt, $matches)) { $tableName = $matches[1]; } elseif (preg_match('/([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:表|table)/', $lowerPrompt, $matches)) { $tableName = $matches[1]; } elseif (preg_match('/(?:创建|生成|建立).*?([a-zA-Z_][a-zA-Z0-9_]*)/', $lowerPrompt, $matches)) { $tableName = $matches[1]; } // 提取字段信息 $fields = $this->extractFieldsFromPrompt($prompt); // 提取表注释 $tableComment = $this->extractTableComment($prompt); // 构建表数据 if ($tableName) { $parsedData['table'] = [ 'name' => $tableName, 'fields' => $fields, 'comment' => $tableComment ]; // 构建控制器数据 $controllerName = ucfirst($tableName) . 'Controller'; $parsedData['controller'] = [ 'module' => 'admin', 'name' => $controllerName, 'fields' => $fields, 'description' => $tableComment ?: $controllerName ]; // 构建模型数据 $modelName = ucfirst($tableName); $parsedData['model'] = [ 'name' => $modelName, 'fields' => $fields, 'table' => $tableName, 'description' => $tableComment ?: $modelName ]; // 构建JS数据 $parsedData['js'] = [ 'module' => 'admin', 'name' => $controllerName, 'fields' => $fields, 'description' => $tableComment ?: $controllerName ]; // 构建API数据 $parsedData['api'] = [ 'module' => 'api', 'name' => $controllerName, 'fields' => $fields, 'description' => $tableComment ?: $controllerName ]; // 构建视图数据 $parsedData['view'] = [ 'module' => 'admin', 'name' => $controllerName, 'fields' => $fields, 'description' => $tableComment ?: $controllerName ]; // 构建CRUD数据 $parsedData['curd'] = [ 'name' => $tableName, 'module' => 'admin', 'fields' => $fields, 'description' => $tableComment ?: $tableName, 'options' => [] ]; // 构建菜单数据 $parsedData['menu'] = [ 'data' => [ 'controller' => $controllerName, 'app' => 'admin', 'menuname' => $tableComment ?: $tableName ], 'options' => [] ]; } // 解析特殊操作 $this->parseSpecialOperations($lowerPrompt, $parsedData); return $parsedData; } /** * 解析特殊操作 * @param string $lowerPrompt * @param array &$parsedData */ private function parseSpecialOperations(string $lowerPrompt, array &$parsedData): void { // 解析JS相关操作 if (strpos($lowerPrompt, 'js') !== false || strpos($lowerPrompt, 'javascript') !== false || strpos($lowerPrompt, '前端') !== false) { if (!empty($parsedData['js'])) { $parsedData['js']['description'] = '前端JS文件'; } } // 解析API相关操作 if (strpos($lowerPrompt, 'api') !== false || strpos($lowerPrompt, '接口') !== false || strpos($lowerPrompt, '接口文件') !== false) { if (!empty($parsedData['api'])) { $parsedData['api']['description'] = 'API接口文件'; } } // 解析视图相关操作 if (strpos($lowerPrompt, 'view') !== false || strpos($lowerPrompt, '视图') !== false || strpos($lowerPrompt, '页面') !== false) { if (!empty($parsedData['view'])) { $parsedData['view']['description'] = '视图文件'; } } // 解析插件相关操作 if (strpos($lowerPrompt, 'addon') !== false || strpos($lowerPrompt, '插件') !== false) { if (preg_match('/(?:创建|生成|建立).*?(?:插件|addon).*?[名为|叫|是]\s*([a-zA-Z_][a-zA-Z0-9_]*)/', $lowerPrompt, $matches)) { $addonName = $matches[1]; $parsedData['addon'] = [ 'name' => $addonName, 'options' => [ 'title' => $addonName, 'description' => $addonName . '插件', 'author' => 'webman', 'version' => '1.0.0', 'requires' => '1.0.0' ] ]; } } // 解析菜单相关操作 if (strpos($lowerPrompt, 'menu') !== false || strpos($lowerPrompt, '菜单') !== false) { if (!empty($parsedData['menu'])) { $parsedData['menu']['description'] = '菜单权限'; } } // 解析CRUD相关操作 if (strpos($lowerPrompt, 'curd') !== false || strpos($lowerPrompt, 'crud') !== false || strpos($lowerPrompt, '增删改查') !== false) { if (!empty($parsedData['curd'])) { $parsedData['curd']['description'] = 'CRUD模块'; } } // 解析模块类型 if (strpos($lowerPrompt, 'admin') !== false || strpos($lowerPrompt, '后台') !== false) { if (!empty($parsedData['controller'])) { $parsedData['controller']['module'] = 'admin'; } if (!empty($parsedData['js'])) { $parsedData['js']['module'] = 'admin'; } if (!empty($parsedData['view'])) { $parsedData['view']['module'] = 'admin'; } } if (strpos($lowerPrompt, 'frontend') !== false || strpos($lowerPrompt, '前台') !== false) { if (!empty($parsedData['controller'])) { $parsedData['controller']['module'] = 'frontend'; } if (!empty($parsedData['js'])) { $parsedData['js']['module'] = 'frontend'; } if (!empty($parsedData['view'])) { $parsedData['view']['module'] = 'frontend'; } } if (strpos($lowerPrompt, 'api') !== false) { if (!empty($parsedData['controller'])) { $parsedData['controller']['module'] = 'api'; } if (!empty($parsedData['js'])) { $parsedData['js']['module'] = 'api'; } if (!empty($parsedData['view'])) { $parsedData['view']['module'] = 'api'; } } } /** * 从提示词中提取字段信息 * @param string $prompt * @return array */ private function extractFieldsFromPrompt(string $prompt): array { $fields = []; // 常见的字段模式匹配 $fieldPatterns = [ // 用户相关字段 'user_id' => ['用户ID', 'user_id', '用户ID'], 'username' => ['用户名', 'username', '用户名称'], 'email' => ['邮箱', 'email', '邮件'], 'phone' => ['手机', 'phone', '电话', '手机号'], 'password' => ['密码', 'password'], 'nickname' => ['昵称', 'nickname', '昵名'], 'avatar' => ['头像', 'avatar', '照片'], 'gender' => ['性别', 'gender'], 'birthday' => ['生日', 'birthday', '出生日期'], 'address' => ['地址', 'address', '住址'], 'status' => ['状态', 'status'], // 通用字段 'title' => ['标题', 'title', '名称'], 'content' => ['内容', 'content', '描述'], 'description' => ['描述', 'description', '说明'], 'price' => ['价格', 'price', '金额'], 'amount' => ['数量', 'amount', '数量'], 'category_id' => ['分类', 'category', '分类ID'], 'sort' => ['排序', 'sort', '顺序'], 'remark' => ['备注', 'remark', '说明'], 'memo' => ['备注', 'memo', '说明'], // 时间相关字段 'created_at' => ['创建时间', 'created_at'], 'updated_at' => ['更新时间', 'updated_at'], 'deleted_at' => ['删除时间', 'deleted_at'], 'published_at' => ['发布时间', 'published_at'], 'expire_at' => ['过期时间', 'expire_at'] ]; foreach ($fieldPatterns as $fieldName => $patterns) { foreach ($patterns as $pattern) { if (strpos($prompt, $pattern) !== false) { $fields[] = $this->CreateFieldByType($fieldName); break; } } } // 如果没有找到字段,添加默认字段 if (empty($fields)) { $fields = [ [ 'name' => 'title', 'type' => 'varchar(255)', 'comment' => '标题', 'null' => false, 'default' => '' ], [ 'name' => 'content', 'type' => 'text', 'comment' => '内容', 'null' => true ], [ 'name' => 'status', 'type' => 'tinyint(1)', 'comment' => '状态:0=禁用,1=启用', 'null' => false, 'default' => 1 ] ]; } return $fields; } /** * 根据字段名生成字段配置 * @param string $fieldName * @return array */ private function CreateFieldByType(string $fieldName): array { $fieldConfigs = [ 'username' => [ 'name' => 'username', 'type' => 'varchar(50)', 'comment' => '用户名', 'null' => false, 'default' => '' ], 'email' => [ 'name' => 'email', 'type' => 'varchar(100)', 'comment' => '邮箱', 'null' => false, 'default' => '' ], 'phone' => [ 'name' => 'phone', 'type' => 'varchar(20)', 'comment' => '手机号', 'null' => true ], 'password' => [ 'name' => 'password', 'type' => 'varchar(255)', 'comment' => '密码', 'null' => false, 'default' => '' ], 'nickname' => [ 'name' => 'nickname', 'type' => 'varchar(50)', 'comment' => '昵称', 'null' => true ], 'avatar' => [ 'name' => 'avatar', 'type' => 'varchar(255)', 'comment' => '头像', 'null' => true ], 'gender' => [ 'name' => 'gender', 'type' => 'tinyint(1)', 'comment' => '性别:0=未知,1=男,2=女', 'null' => false, 'default' => 0 ], 'birthday' => [ 'name' => 'birthday', 'type' => 'date', 'comment' => '生日', 'null' => true ], 'address' => [ 'name' => 'address', 'type' => 'text', 'comment' => '地址', 'null' => true ], 'status' => [ 'name' => 'status', 'type' => 'tinyint(1)', 'comment' => '状态:0=禁用,1=启用', 'null' => false, 'default' => 1 ], 'title' => [ 'name' => 'title', 'type' => 'varchar(255)', 'comment' => '标题', 'null' => false, 'default' => '' ], 'content' => [ 'name' => 'content', 'type' => 'text', 'comment' => '内容', 'null' => true ], 'description' => [ 'name' => 'description', 'type' => 'text', 'comment' => '描述', 'null' => true ], 'price' => [ 'name' => 'price', 'type' => 'decimal(10,2)', 'comment' => '价格', 'null' => false, 'default' => 0.00 ], 'amount' => [ 'name' => 'amount', 'type' => 'int(11)', 'comment' => '数量', 'null' => false, 'default' => 0 ], 'category_id' => [ 'name' => 'category_id', 'type' => 'int(11)', 'comment' => '分类ID', 'null' => false, 'default' => 0 ], 'sort' => [ 'name' => 'sort', 'type' => 'int(11)', 'comment' => '排序', 'null' => false, 'default' => 0 ], 'remark' => [ 'name' => 'remark', 'type' => 'varchar(255)', 'comment' => '备注', 'null' => true ], 'memo' => [ 'name' => 'memo', 'type' => 'varchar(255)', 'comment' => '备注', 'null' => true ], 'published_at' => [ 'name' => 'published_at', 'type' => 'int(11)', 'comment' => '发布时间', 'null' => true ], 'expire_at' => [ 'name' => 'expire_at', 'type' => 'int(11)', 'comment' => '过期时间', 'null' => true ] ]; return $fieldConfigs[$fieldName] ?? [ 'name' => $fieldName, 'type' => 'varchar(255)', 'comment' => $fieldName, 'null' => true ]; } /** * 从提示词中提取表注释 * @param string $prompt * @return string */ private function extractTableComment(string $prompt): string { // 提取表注释的模式 $patterns = [ '/(?:用于|用来|存储|管理).*?(?:信息|数据|记录)/', '/(?:.*?)(?:表|table)/', '/(?:.*?)(?:管理|系统|模块)/' ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $prompt, $matches)) { return trim($matches[0]) . '表'; } } return ''; } /** * 转换表名为控制器名 * @param string $tableName * @return string */ private function convertTableNameToControllerName(string $tableName): string { return $this->convertTableNameToModelName($tableName) . 'Controller'; } /** * 转换表名为模型名 * @param string $tableName * @return string */ private function convertTableNameToModelName(string $tableName): string { // 移除表前缀 $prefix = config('thinkorm.connections.mysql.prefix'); if (strpos($tableName, $prefix) === 0) { $tableName = substr($tableName, strlen($prefix)); } $tableName = str_replace(['-', '_'], ' ', strtolower($tableName)); $tableName = ucwords($tableName); $tableName = str_replace(' ', '', $tableName); return ucfirst($tableName); } /** * 生成JS文件 * @param string $module 模块名称 (admin/api/frontend等) * @param string $controller 控制器名称 * @param array $fields 字段信息 (可选) * @param string $description 描述 (可选) * @return array */ public function handleCreateJs(string $module, string $controller, array $fields = [], string $description = ''): array { try { // 生成JS文件名 $jsFileName = strtolower($controller); $jsPath = "plugin/admin/public/js/{$jsFileName}.js"; if ($module == 'frontend') { $jsPath = "public/static/js/{$jsFileName}.js"; } // 检查文件是否已存在 if (file_exists($jsPath)) { return [ 'success' => false, 'error' => "JS文件 {$jsPath} 已存在" ]; } // 生成JS内容 $jsContent = $this->generateJsContent($module, $controller, $fields, $description); // 确保目录存在 $dir = dirname($jsPath); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // 写入文件 if (file_put_contents($jsPath, $jsContent)) { Log::channel('mcp')->info("JS文件生成成功: {$jsPath}"); return [ 'success' => true, 'message' => 'JS文件生成成功', 'file_path' => $jsPath, 'content' => $jsContent ]; } else { throw new \Exception('文件写入失败'); } } catch (\Exception $e) { Log::channel('mcp')->error('JS文件生成错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 生成API接口文件 * @param string $controller 控制器名称 * @param string $module 模块名称 (api等) * @param array $fields 字段信息 (可选) * @param string $description 描述 (可选) * @return array */ public function handleCreateApi(string $controller, string $module = 'api', array $fields = [], string $description = ''): array { try { // 生成API控制器类名 $controllerClass = ucfirst($controller); $apiPath = "app/{$module}/controller/{$controllerClass}.php"; // 检查文件是否已存在 if (file_exists($apiPath)) { return [ 'success' => false, 'error' => "API文件 {$apiPath} 已存在" ]; } // 生成API内容 $apiContent = $this->generateApiContent($module, $controllerClass, $fields, $description); // 确保目录存在 $dir = dirname($apiPath); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // 写入文件 if (file_put_contents($apiPath, $apiContent)) { Log::channel('mcp')->info("API文件生成成功: {$apiPath}"); return [ 'success' => true, 'message' => 'API文件生成成功', 'file_path' => $apiPath, 'content' => $apiContent ]; } else { throw new \Exception('文件写入失败'); } } catch (\Exception $e) { Log::channel('mcp')->error('API文件生成错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 生成JS文件内容 * @param string $module 模块名称 * @param string $controller 控制器名称 * @param array $fields 字段信息 * @param string $description 描述 * @return string */ private function generateJsContent(string $module, string $controller, array $fields = [], string $description = ''): string { $controllerLower = strtolower($controller); $description = $description ?: $controller; // 生成表格列配置 $jsCols = $this->generateJsCols($fields); $tpl = 'app/mcp/tpl/js/' . $module . '.tpl'; if (!file_exists($tpl)) { $tpl = 'app/mcp/tpl/js/default.tpl'; } $content = view($tpl, [ 'description' => $description, 'controllerClass' => $controller, 'controllerLower' => $controllerLower, ]); return $content; } /** * 生成API文件内容 * @param string $module 模块名称 * @param string $controllerClass 控制器类名 * @param array $fields 字段信息 * @param string $description 描述 * @return string */ private function generateApiContent(string $module, string $controllerClass, array $fields = [], string $description = ''): string { $description = $description ?: $controllerClass; // 生成字段验证规则 $validationRules = $this->generateApiValidationRules($fields); $tpl = 'app/mcp/tpl/controller/' . $module . '.tpl'; if (!file_exists($tpl)) { $tpl = 'app/mcp/tpl/controller/default.tpl'; } $content = view($tpl, [ 'description' => $description, 'controllerClass' => $controllerClass, ]); return $content; } /** * 生成JS表格列配置 * @param array $fields 字段信息 * @return string */ private function generateJsCols(array $fields): string { if (empty($fields)) { return " {field: 'title', title: '标题', width: 200},"; } $cols = []; foreach ($fields as $field) { $fieldName = $field['name'] ?? ''; $fieldComment = $field['comment'] ?? $fieldName; $fieldType = $field['type'] ?? 'varchar'; // 跳过系统字段 if (in_array($fieldName, ['id', 'created_at', 'updated_at', 'deleted_at'])) { continue; } // 根据字段类型设置不同的显示方式 $width = 200; $templet = ''; if (strpos($fieldType, 'text') !== false) { $width = 300; $templet = ",filter:'string'"; } elseif (strpos($fieldType, 'int') !== false) { $width = 100; $templet = ",filter:'number'"; } elseif (strpos($fieldType, 'datetime') !== false || strpos($fieldType, 'timestamp') !== false) { $width = 180; $templet = ", formatter:Table.api.formatter.datetime,filter:'datetime'"; } elseif (strpos($fieldType, 'tinyint') !== false) { $width = 100; $templet = ", formatter: Table.api.formatter.switch"; } $cols[] = " {field: '{$fieldName}', title: '{$fieldComment}', width: {$width}{$templet},"; } return implode("\n", $cols); } /** * 生成JS请求配置 * @param string $controller 控制器名称 * @return string */ private function generateJsRequests(string $controller): string { $admin_path = admin_path(); return " index_url: '{$admin_path}/{$controller}/index', add_url: '{$admin_path}/{$controller}/add', edit_url: '{$admin_path}/{$controller}/edit', del_url: '{$admin_path}/{$controller}/del', multi_url: '{$admin_path}/{$controller}/multi', table_url: '{$admin_path}/{$controller}/table',"; } /** * 生成API验证规则 * @param array $fields 字段信息 * @return string */ private function generateApiValidationRules(array $fields): string { if (empty($fields)) { return "'title' => 'require|max:255', 'content' => 'require',"; } $rules = []; foreach ($fields as $field) { $fieldName = $field['name'] ?? ''; $fieldType = $field['type'] ?? 'varchar'; if (empty($fieldName) || in_array($fieldName, ['id', 'created_at', 'updated_at', 'deleted_at'])) { continue; } $rule = "'{$fieldName}' => '"; // 根据字段类型设置验证规则 if (strpos($fieldType, 'varchar') !== false) { $maxLength = 255; if (preg_match('/varchar\((\d+)\)/', $fieldType, $matches)) { $maxLength = $matches[1]; } $rule .= "max:{$maxLength}"; } elseif (strpos($fieldType, 'text') !== false) { $rule .= "require"; } elseif (strpos($fieldType, 'int') !== false) { $rule .= "number"; } elseif (strpos($fieldType, 'datetime') !== false || strpos($fieldType, 'timestamp') !== false) { $rule .= "date"; } else { $rule .= "require"; } $rule .= "'"; $rules[] = $rule; } return implode(",\n ", $rules); } /** * 生成视图文件 * @param string $module 模块名称 (admin/api/frontend等) * @param string $controller 控制器名称 * @param array $fields 字段信息 (可选) * @param string $description 描述 (可选) * @return array */ public function handleCreateView(string $module, string $controller, array $fields = [], string $description = ''): array { try { // 生成视图文件名 $viewFileName = strtolower($controller); $viewPath = "app/{$module}/view/{$viewFileName}"; if ($module == 'admin') { $viewPath = "plugin/{$module}/app/view/{$viewFileName}"; } // 检查目录是否已存在 if (is_dir($viewPath)) { return [ 'success' => false, 'error' => "视图目录 {$viewPath} 已存在" ]; } // 生成视图内容 $viewFiles = $this->generateViewFiles($module, $controller, $fields, $description); // 确保目录存在 if (!is_dir($viewPath)) { mkdir($viewPath, 0755, true); } $generatedFiles = []; foreach ($viewFiles as $viewFile) { $filePath = $viewPath . '/' . $viewFile['name']; if (file_put_contents($filePath, $viewFile['content'])) { $generatedFiles[] = $filePath; Log::channel('mcp')->info("视图文件生成成功: {$filePath}"); } } if (!empty($generatedFiles)) { return [ 'success' => true, 'message' => '视图文件生成成功', 'file_paths' => $generatedFiles, 'files_count' => count($generatedFiles) ]; } else { throw new \Exception('视图文件写入失败'); } } catch (\Exception $e) { Log::channel('mcp')->error('视图文件生成错误: ' . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * 生成视图文件 * @param string $module 模块名称 * @param string $controller 控制器名称 * @param array $fields 字段信息 * @param string $description 描述 * @return array */ private function generateViewFiles(string $module, string $controller, array $fields = [], string $description = ''): array { $controllerLower = strtolower($controller); $description = $description ?: $controller; $viewFiles = []; // 生成 index.html $indexContent = $this->generateIndexView($module, $controllerLower, $fields, $description); $viewFiles[] = [ 'name' => 'index.html', 'content' => $indexContent ]; // 生成 add.html $addContent = $this->generateAddView($module, $controllerLower, $fields, $description); $viewFiles[] = [ 'name' => 'update.html', 'content' => $addContent ]; // 生成 edit.html /* $viewFiles[] = [ 'name' => 'edit.html', 'content' => $addContent ]; */ return $viewFiles; } /** * 生成index视图 * @param string $module 模块名称 * @param string $controller 控制器名称 * @param array $fields 字段信息 * @param string $description 描述 * @return string */ private function generateIndexView(string $module, string $controller, array $fields = [], string $description = ''): string { $content = view('app/mcp/tpl/view/index.tpl', [ 'controller' => $controller, 'module' => $module, 'fields' => $fields, 'description' => $description, ]); return $content; } /** * 生成add视图 * @param string $module 模块名称 * @param string $controller 控制器名称 * @param array $fields 字段信息 * @param string $description 描述 * @return string */ private function generateAddView(string $module, string $controller, array $fields = [], string $description = ''): string { foreach ($fields as $k => $field) { if (in_array($field['name'], ['id', 'created_at', 'updated_at', 'deleted_at'])) { continue; } $fieldName = $field['name']; $fieldComment = $field['comment'] ?? $fieldName; $fieldType = $field['type'] ?? 'varchar'; if ($this->shouldUseSelect($fieldName, $fieldComment, $fieldType)) { // 下拉选择框 - 根据字段名称、注释、类型判断 $fields[$k]['selectOptions'] = $this->generateSelectOptions($fieldName, $fieldComment); $fields[$k]['multiple'] = $this->isMultipleSelect($fieldComment); $fields[$k]['type'] = 'select'; } if (strpos($fieldType, 'tinyint') !== false && in_array($fieldName, ['status', 'is_show', 'is_enable'])) { $fields[$k]['type'] = 'radio'; $fields[$k]['selectOptions'] = $this->generateSelectOptions($fieldName, $fieldComment); } } $content = view('app/mcp/tpl/view/update.tpl', [ 'controller' => $controller, 'module' => $module, 'fields' => $fields, 'description' => $description, 'action' => 'update', ]); $content = "