Files
im/app/mcp/McpService.php
T

4020 lines
138 KiB
PHP
Raw Normal View History

2025-11-07 09:56:20 +08:00
<?php
namespace app\mcp;
use PhpMcp\Server\Server;
use PhpMcp\Server\ServerBuilder;
use PhpMcp\Server\Transports\StdioServerTransport;
use PhpMcp\Server\Defaults\BasicContainer;
use PhpMcp\Schema\Tool;
use PhpMcp\Schema\Resource;
use PhpMcp\Schema\ToolAnnotations;
use PhpMcp\Schema\Annotations;
use think\facade\Db;
use support\Log;
/**
* MCP(Model Context Protocol)服务类
* 提供与AI模型交互的上下文协议服务
*/
class McpService
{
/**
* MCP服务器实例
* @var Server|null
*/
protected $server = null;
/**
* 日志记录器
*/
protected $logger;
/**
* 超时配置(毫秒)
* @var int
*/
protected $timeout = 600000;
/**
* 连接超时配置(毫秒)
* @var int
*/
protected $connectTimeout = 30000;
/**
* 读取超时配置(毫秒)
* @var int
*/
protected $readTimeout = 30000;
/**
* 重试次数
* @var int
*/
protected $retryAttempts = 3;
/**
* 重试延迟(毫秒)
* @var int
*/
protected $retryDelay = 1000;
/**
* 调试模式
* @var bool
*/
protected $debug = false;
/**
* 服务名称
* @var string
*/
protected $name = 'mcp';
/**
* 服务版本
* @var string
*/
protected $version = '1.0.0';
/**
* 内存限制
* @var string
*/
protected $memoryLimit;
/**
* 缓冲区大小
* @var int
*/
protected $bufferSize;
/**
* 心跳机制启用
* @var bool
*/
protected $heartbeatEnabled;
/**
* 心跳间隔(秒)
* @var int
*/
protected $heartbeatInterval;
/**
* 构造函数
*/
public function __construct()
{
$this->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('plugin.bilulanlv.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('plugin.bilulanlv.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('plugin.bilulanlv.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 = "<?php
namespace app\\model;
// SoftDelete 为 ThinkPHP 专属,这里注释以避免耦合
// use traits\\model\\SoftDelete;
/**
* {$description}模型
* Class {$modelClass}
* @package app\\model
*/
class {$modelClass} extends Base
{
// use SoftDelete; // 如需软删除,请实现项目内等价方案
/**
* 数据表名
* @var string
*/
protected \$table = '{$tableName}';
/**
* 软删除字段
* @var string
*/
protected \$deleteTime = 'deleted_at';
{$fieldDefinitions}
}";
return $content;
}
/**
* 创建数据库表格
* @param string $tableName 表名
* @param array $fields 字段信息数组
* @param string $tableComment 表注释
* @param string $engine 存储引擎 (默认 InnoDB)
* @param string $charset 字符集 (默认 utf8mb4)
* @return array
*/
public function handleCreateTable(string $tableName, array $fields, string $tableComment = '', string $engine = 'InnoDB', string $charset = 'utf8mb4'): array
{
try {
// 验证表名
if (empty($tableName)) {
throw new \Exception('表名不能为空');
}
// 验证字段信息
if (empty($fields) || !is_array($fields)) {
throw new \Exception('字段信息不能为空且必须是数组');
}
// 检查表是否已存在
$existingTables = Db::query("SHOW TABLES LIKE '{$tableName}'");
if (!empty($existingTables)) {
return [
'success' => 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
{
return " index_url: '/app/admin/{$controller}/index',
add_url: '/app/admin/{$controller}/add',
edit_url: '/app/admin/{$controller}/edit',
del_url: '/app/admin/{$controller}/del',
multi_url: '/app/admin/{$controller}/multi',
table_url: '/app/admin/{$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 = "<form class=\"layui-form\" lay-filter=\"form\">\n";
return $content;
}
/**
* 判断是否应该使用下拉选择框
* @param string $fieldName 字段名称
* @param string $fieldComment 字段注释
* @param string $fieldType 字段类型
* @return bool
*/
private function shouldUseSelect(string $fieldName, string $fieldComment, string $fieldType): bool
{
// 1. 根据字段注释判断
$commentKeywords = [
'选择',
'类型',
'分类',
'等级',
'级别',
'状态',
'方式',
'模式',
'种类',
'下拉',
'列表',
'枚举',
'选项',
'菜单',
'角色',
'权限',
'部门',
'省份',
'城市',
'地区',
'国家',
'行业',
'职业',
'学历',
'婚姻',
'1:',
'2:',
'3:',
'|',
'',
',',
'/',
'\\', // 包含选项分隔符的注释
];
foreach ($commentKeywords as $keyword) {
if (strpos($fieldComment, $keyword) !== false) {
return true;
}
}
// 2. 根据字段名称判断
$nameKeywords = [
'_id',
'type',
'category',
'level',
'grade',
'status',
'state',
'kind',
'class',
'group',
'dept',
'role',
'auth',
'mode',
'method',
'way',
'style',
'format',
'gender',
'sex',
'province',
'city',
'area',
'region',
'country',
'nation'
];
foreach ($nameKeywords as $keyword) {
if (strpos($fieldName, $keyword) !== false) {
return true;
}
}
// 3. 根据字段类型判断
if (strpos($fieldType, 'enum') !== false) {
return true;
}
// 4. 小范围的整数字段可能是选择字段
if (strpos($fieldType, 'tinyint') !== false || strpos($fieldType, 'smallint') !== false) {
// 排除明确的布尔字段(is_开头的字段)
if (strpos($fieldName, 'is_') !== 0 && !in_array($fieldName, ['status', 'sort', 'weigh', 'createtime', 'updatetime'])) {
return true;
}
}
return false;
}
/**
* 判断是否为多选字段
* @param string $fieldComment 字段注释
* @return bool
*/
private function isMultipleSelect(string $fieldComment): bool
{
$multipleKeywords = [
'多选',
'复选',
'多个',
'批量',
'多种',
'可选多个',
'可多选',
'标签',
'爱好',
'技能',
'特长',
'兴趣',
'权限',
'角色组'
];
foreach ($multipleKeywords as $keyword) {
if (strpos($fieldComment, $keyword) !== false) {
return true;
}
}
return false;
}
/**
* 生成下拉选择框的选项
* @param string $fieldName 字段名称
* @param string $fieldComment 字段注释
* @return string 选项数组的字符串表示
*/
private function generateSelectOptions(string $fieldName, string $fieldComment): string
{
// 1. 首先尝试从字段注释中解析选项
$parsedOptions = $this->parseOptionsFromComment($fieldComment);
if (!empty($parsedOptions)) {
return $parsedOptions;
}
// 2. 根据字段名称智能生成选项
if (strpos($fieldName, 'status') !== false) {
// 状态字段
return "['0'=>'禁用', '1'=>'启用']";
} elseif (strpos($fieldName, 'is_') === 0) {
// 布尔类型字段 (is_show, is_enable等)
return "['0'=>'否', '1'=>'是']";
} elseif (strpos($fieldName, 'gender') !== false || strpos($fieldName, 'sex') !== false) {
// 性别字段
return "['0'=>'保密', '1'=>'男', '2'=>'女']";
} elseif (strpos($fieldName, 'type') !== false) {
// 类型字段 - 根据具体用途生成
if (strpos($fieldName, 'user') !== false) {
return "['1'=>'普通用户', '2'=>'VIP用户', '3'=>'超级用户']";
} elseif (strpos($fieldName, 'content') !== false || strpos($fieldName, 'article') !== false) {
return "['1'=>'文章', '2'=>'图片', '3'=>'视频']";
} elseif (strpos($fieldName, 'pay') !== false) {
return "['1'=>'微信支付', '2'=>'支付宝', '3'=>'银行卡']";
} else {
return "['1'=>'类型一', '2'=>'类型二', '3'=>'类型三']";
}
} elseif (strpos($fieldName, 'level') !== false) {
// 等级字段
return "['1'=>'初级', '2'=>'中级', '3'=>'高级', '4'=>'专家级']";
} elseif (strpos($fieldName, 'grade') !== false) {
// 等级字段
return "['A'=>'A级', 'B'=>'B级', 'C'=>'C级', 'D'=>'D级']";
} elseif (strpos($fieldName, 'priority') !== false) {
// 优先级字段
return "['1'=>'低', '2'=>'中', '3'=>'高', '4'=>'紧急']";
} elseif (strpos($fieldName, 'category') !== false) {
// 分类字段 - 提供更通用的选项
return "[''=>'请选择分类', '1'=>'默认分类', '2'=>'热门分类', '3'=>'推荐分类']";
} elseif (strpos($fieldName, 'admin_id') !== false) {
// 管理员字段 - 系统表
return "\\think\\facade\\Db::name('admin')->column('username', 'id') ?: [''=>'请选择管理员']";
} elseif (strpos($fieldName, 'member_id') !== false) {
// 会员字段 - 系统表
return "\\think\\facade\\Db::name('member')->column('username', 'id') ?: [''=>'请选择会员']";
} elseif (strpos($fieldName, 'user_id') !== false) {
// 用户字段 - 通用处理
return "\\think\\facade\\Db::name('user')->column('username', 'id') ?: [''=>'请选择用户']";
} elseif (strpos($fieldName, 'role_id') !== false) {
// 角色字段 - 权限表
return "\\think\\facade\\Db::name('auth_role')->column('name', 'id') ?: [''=>'请选择角色']";
} elseif (strpos($fieldName, 'dept_id') !== false || strpos($fieldName, 'department_id') !== false) {
// 部门字段
return "\\think\\facade\\Db::name('dept')->column('name', 'id') ?: [''=>'请选择部门']";
} elseif (strpos($fieldName, 'parent_id') !== false || strpos($fieldName, 'pid') !== false) {
// 父级字段 - 提供静态选项避免表不存在的问题
return "['0'=>'顶级分类', ''=>'请选择上级分类']";
} elseif (strpos($fieldName, 'province') !== false || strpos($fieldName, 'city') !== false || strpos($fieldName, 'area') !== false) {
// 地区字段 - 提供静态选项
if (strpos($fieldName, 'province') !== false) {
return "['11'=>'北京市', '12'=>'天津市', '13'=>'河北省', '21'=>'辽宁省', '31'=>'上海市', '32'=>'江苏省', '44'=>'广东省']";
} elseif (strpos($fieldName, 'city') !== false) {
return "['1101'=>'东城区', '1102'=>'西城区', '1103'=>'朝阳区', '1104'=>'丰台区', '1105'=>'石景山区']";
} else {
return "['110101'=>'东华门街道', '110102'=>'景山街道', '110103'=>'交道口街道']";
}
} elseif (strpos($fieldName, 'sort') !== false || strpos($fieldName, 'order') !== false) {
// 排序字段
return "['1'=>'1', '10'=>'10', '50'=>'50', '100'=>'100', '999'=>'999']";
} elseif (strpos($fieldName, 'state') !== false) {
// 状态字段
return "['0'=>'待处理', '1'=>'处理中', '2'=>'已完成', '3'=>'已取消']";
} else {
// 默认选项
return "[''=>'请选择{$fieldComment}', '1'=>'选项一', '2'=>'选项二', '3'=>'选项三']";
}
}
/**
* 从字段注释中解析选项
* @param string $comment 字段注释
* @return string 解析出的选项数组字符串,如果解析失败返回空字符串
*/
private function parseOptionsFromComment(string $comment): string
{
if (empty($comment)) {
return '';
}
// 解析模式1: "sex=1:男,2:女,3:未知" 或 "sex=1:男|2:女|3:未知" 或 "status=[1:可用,0:不可用]" 或 "status=(1:可用,0:不可用)"
// 先提取等号后面的部分,如果没有等号则使用整个注释
$commentPart = $comment;
if (preg_match('/^[^=]*=(.+)$/', $comment, $eqMatch)) {
$commentPart = $eqMatch[1];
}
// 匹配 数字:值 的格式
if (preg_match_all('/(\d+)\s*[:]\s*([^,|)\]()\s]+)/', $commentPart, $matches, PREG_SET_ORDER)) {
$options = [];
foreach ($matches as $match) {
$key = trim($match[1]);
$value = trim($match[2]);
// 清理值中的特殊字符
$value = preg_replace('/[()()\[\]【】]/', '', $value);
$options[] = "'{$key}'=>'{$value}'";
}
if (!empty($options)) {
return '[' . implode(', ', $options) . ']';
}
}
// 解析模式2: "性别=男,女,未知" 或 "类型=[选项1,选项2]" 或 "选项:(男|女|未知)"
if (preg_match('/[,|]/', $comment)) {
// 先提取选项部分(支持等号、冒号、括号等分隔符)
$optionsPart = $comment;
// 处理等号分隔的格式: "性别=男,女"
if (preg_match('/^[^=]*=\s*[(\[(【]?([^)\])】]+)[)\])】]?$/', $comment, $eqMatch)) {
$optionsPart = $eqMatch[1];
}
// 处理冒号分隔的格式: "选项:[男,女]" 或 "选项:(男,女)"
elseif (preg_match('/[:]\s*[(\[(【]?([^)\])】]+)[)\])】]?$/', $comment, $colonMatch)) {
$optionsPart = $colonMatch[1];
}
// 分割选项
$items = preg_split('/[,|,、]/', $optionsPart);
$options = [];
foreach ($items as $index => $item) {
$item = trim($item);
// 清理特殊字符
$item = preg_replace('/[()()\[\]【】]/', '', $item);
if (!empty($item) && !preg_match('/^\d+$/', $item)) { // 排除纯数字和空值
$key = $index + 1;
$options[] = "'{$key}'=>'{$item}'";
}
}
if (count($options) > 1) {
return '[' . implode(', ', $options) . ']';
}
}
// 解析模式3: "选择类型:1-普通 2-VIP 3-超级" 或 "状态=1-启用 0-禁用"
if (preg_match_all('/(\d+)\s*[-—=]\s*([^\s,|]+)/', $comment, $matches, PREG_SET_ORDER)) {
$options = [];
foreach ($matches as $match) {
$key = trim($match[1]);
$value = trim($match[2]);
// 清理值中的特殊字符
$value = preg_replace('/[()()\[\]【】]/', '', $value);
$options[] = "'{$key}'=>'{$value}'";
}
if (!empty($options)) {
return '[' . implode(', ', $options) . ']';
}
}
// 解析模式4: 包含"或"的表达式 "男或女" "是或否" "启用或禁用"
if (preg_match('/(.+?)或(.+)/', $comment, $matches)) {
$option1 = trim($matches[1]);
$option2 = trim($matches[2]);
// 清理特殊字符
$option1 = preg_replace('/[()()\[\]【】=:]/', '', $option1);
$option2 = preg_replace('/[()()\[\]【】]/', '', $option2);
// 如果是状态类的,使用0/1作为键值
if (preg_match('/(启用|开启|打开|显示|是)/', $option1) || preg_match('/(禁用|关闭|隐藏|否)/', $option2)) {
return "['1'=>'{$option1}', '0'=>'{$option2}']";
} else {
return "['1'=>'{$option1}', '2'=>'{$option2}']";
}
}
// 解析模式5: 枚举值格式 "enum('male','female','unknown')" 或 "ENUM(1,2,3)"
if (preg_match('/enum\s*\(\s*([^)]+)\s*\)/i', $comment, $matches)) {
$enumValues = $matches[1];
// 移除引号并分割
$items = preg_split('/[,]/', $enumValues);
$options = [];
foreach ($items as $index => $item) {
$item = trim($item, " '\"");
if (!empty($item)) {
$key = is_numeric($item) ? $item : ($index + 1);
$options[] = "'{$key}'=>'{$item}'";
}
}
if (!empty($options)) {
return '[' . implode(', ', $options) . ']';
}
}
return '';
}
/**
* 处理其他操作
* @param string $prompt 自然语言描述
* @return array
*/
private function handleOtherOperation(string $prompt): array
{
$lowerPrompt = strtolower($prompt);
$results = [];
// 处理数据库查询操作
if (strpos($lowerPrompt, '查询') !== false || strpos($lowerPrompt, 'select') !== false) {
$results['db_query'] = [
'success' => true,
'message' => '检测到数据库查询操作',
'suggestion' => '请使用 db-query 工具执行数据库查询'
];
}
// 处理系统配置操作
if (strpos($lowerPrompt, '配置') !== false || strpos($lowerPrompt, 'config') !== false) {
$results['sys_config'] = [
'success' => true,
'message' => '检测到系统配置操作',
'suggestion' => '请使用 sys-config 工具获取系统配置'
];
}
// 处理日志操作
if (strpos($lowerPrompt, '日志') !== false || strpos($lowerPrompt, 'log') !== false) {
$results['write_log'] = [
'success' => true,
'message' => '检测到日志操作',
'suggestion' => '请使用 write-log 工具写入系统日志'
];
}
// 处理文件操作
if (strpos($lowerPrompt, '文件') !== false || strpos($lowerPrompt, 'file') !== false) {
$results['file_operation'] = [
'success' => true,
'message' => '检测到文件操作',
'suggestion' => '请使用 file-operation 工具进行文件读写操作'
];
}
// 处理用户管理操作
if (strpos($lowerPrompt, '用户') !== false || strpos($lowerPrompt, 'user') !== false) {
$results['user_management'] = [
'success' => true,
'message' => '检测到用户管理操作',
'suggestion' => '请使用 user-management 工具进行用户管理'
];
}
// 处理系统信息操作
if (strpos($lowerPrompt, '系统信息') !== false || strpos($lowerPrompt, 'system') !== false) {
$results['system_info'] = [
'success' => true,
'message' => '检测到系统信息操作',
'suggestion' => '请使用 system-info 工具获取系统运行信息'
];
}
// 处理控制器生成操作
if (strpos($lowerPrompt, '控制器') !== false || strpos($lowerPrompt, 'controller') !== false) {
$results['controller'] = [
'success' => true,
'message' => '检测到控制器生成操作',
'suggestion' => '请使用 controller 工具生成控制器文件'
];
}
// 处理模型生成操作
if (strpos($lowerPrompt, '模型') !== false || strpos($lowerPrompt, 'model') !== false) {
$results['model'] = [
'success' => true,
'message' => '检测到模型生成操作',
'suggestion' => '请使用 model 工具生成模型文件'
];
}
// 处理数据库表创建操作
if (strpos($lowerPrompt, '数据库表') !== false || strpos($lowerPrompt, 'table') !== false) {
$results['table'] = [
'success' => true,
'message' => '检测到数据库表创建操作',
'suggestion' => '请使用 table 工具创建数据库表'
];
}
// 处理插件操作
if (strpos($lowerPrompt, '插件') !== false || strpos($lowerPrompt, 'addon') !== false) {
$results['addon'] = [
'success' => true,
'message' => '检测到插件操作',
'suggestion' => '请使用 addon 工具进行插件管理'
];
}
// 处理菜单操作
if (strpos($lowerPrompt, '菜单') !== false || strpos($lowerPrompt, 'menu') !== false) {
$results['menu'] = [
'success' => true,
'message' => '检测到菜单操作',
'suggestion' => '请使用 menu 工具进行菜单管理'
];
}
// 处理CRUD操作
if (strpos($lowerPrompt, 'crud') !== false || strpos($lowerPrompt, '增删改查') !== false) {
$results['curd'] = [
'success' => true,
'message' => '检测到CRUD操作',
'suggestion' => '请使用 curd 工具生成CRUD模块'
];
}
// 处理JS文件生成操作
if (strpos($lowerPrompt, 'js') !== false || strpos($lowerPrompt, 'javascript') !== false || strpos($lowerPrompt, '前端') !== false) {
$results['js'] = [
'success' => true,
'message' => '检测到JS文件生成操作',
'suggestion' => '请使用 js 工具生成前端JS文件'
];
}
// 处理API接口生成操作
if (strpos($lowerPrompt, 'api') !== false || strpos($lowerPrompt, '接口') !== false) {
$results['api'] = [
'success' => true,
'message' => '检测到API接口生成操作',
'suggestion' => '请使用 api 工具生成API接口文件'
];
}
// 处理视图文件生成操作
if (strpos($lowerPrompt, '视图') !== false || strpos($lowerPrompt, 'view') !== false || strpos($lowerPrompt, '页面') !== false) {
$results['view'] = [
'success' => true,
'message' => '检测到视图文件生成操作',
'suggestion' => '请使用 view 工具生成视图文件'
];
}
// 处理webman命令执行操作
if (
strpos($lowerPrompt, 'webman') !== false || strpos($lowerPrompt, '命令') !== false ||
strpos($lowerPrompt, 'command') !== false || strpos($lowerPrompt, '执行') !== false ||
strpos($lowerPrompt, 'start') !== false || strpos($lowerPrompt, 'stop') !== false ||
strpos($lowerPrompt, 'build') !== false || strpos($lowerPrompt, 'plugin') !== false
) {
$results['think-command'] = [
'success' => true,
'message' => '检测到webman命令执行操作',
'suggestion' => '请使用 think-command 工具执行webman框架命令,支持的命令包括:start、stop、build、plugin:install、queue:work等'
];
}
// 如果没有匹配到任何操作,返回通用建议
if (empty($results)) {
$results['general'] = [
'success' => false,
'message' => '未能识别具体的操作类型',
'suggestion' => '请尝试以下操作:' . "\n" .
'- 创建数据库表:包含表名和字段信息' . "\n" .
'- 生成控制器:指定模块和控制器名称' . "\n" .
'- 生成模型:指定模型名称和字段信息' . "\n" .
'- 生成JS文件:指定模块和控制器名称' . "\n" .
'- 生成API接口:指定模块和控制器名称' . "\n" .
'- 生成视图文件:指定模块和控制器名称' . "\n" .
'- 创建插件:指定插件名称' . "\n" .
'- 创建菜单:指定菜单信息' . "\n" .
'- 生成CRUD模块:指定表名和字段信息' . "\n" .
'- 数据库查询:使用SELECT语句' . "\n" .
'- 系统配置:获取系统配置信息' . "\n" .
'- 文件操作:进行文件读写操作' . "\n" .
'- 用户管理:进行用户相关操作' . "\n" .
'- 系统信息:获取系统运行信息' . "\n" .
'- webman命令:执行框架内置命令(start、stop、build、plugin等)'
];
}
return $results;
}
/**
* 执行webman框架命令
* @param string $command 命令名称
* @param array $params 命令参数 (可选)
* @param array $options 命令选项 (可选)
* @return array
*/
public function handleThinkCommand(string $command, array $params = [], array $options = []): array
{
try {
// 验证命令是否为安全的内置命令
$allowedCommands = [
// webman 基础命令
'start',
'stop',
'restart',
'reload',
'status',
'help',
'version',
// webman 构建命令
'build',
'build:phar',
'build:bin',
// webman 插件命令
'plugin:install',
'plugin:uninstall',
'plugin:list',
'plugin:enable',
'plugin:disable',
// webman 队列命令
'queue:work',
'queue:failed',
'queue:retry',
'queue:flush',
// webman 日志命令
'log:clear',
'log:tail',
// webman 缓存命令
'cache:clear',
'cache:clear:redis',
// webman 路由命令
'route:list',
'route:cache',
// webman 配置命令
'config:cache',
'config:clear',
// webman 数据库命令
'db:seed',
'db:migrate',
'db:rollback',
// 自定义 MCP 命令
'mcp:start',
'mcp:stop',
'mcp:status',
'mcp:test'
];
if (!in_array($command, $allowedCommands)) {
return [
'success' => false,
'message' => '不支持的命令或命令不安全',
'allowed_commands' => $allowedCommands
];
}
// 构建完整的命令(只支持 webman 命令)
$fullCommand = 'php webman ' . $command;
// 添加参数
if (!empty($params)) {
foreach ($params as $param) {
$fullCommand .= ' ' . escapeshellarg($param);
}
}
// 添加选项
if (!empty($options)) {
foreach ($options as $option => $value) {
if (is_numeric($option)) {
// 简单选项,如 --verbose
$fullCommand .= ' --' . $value;
} else {
// 带值选项,如 --name=value
$fullCommand .= ' --' . $option . '=' . escapeshellarg($value);
}
}
}
// 切换到项目根目录执行命令
$rootPath = base_path();
$originalDir = getcwd();
if ($originalDir !== $rootPath) {
chdir($rootPath);
}
// 执行命令并捕获输出
$output = [];
$returnCode = 0;
exec($fullCommand . ' 2>&1', $output, $returnCode);
// 恢复原始目录
if ($originalDir !== $rootPath) {
chdir($originalDir);
}
// 记录命令执行日志
$this->handleWriteLog("执行 webman 命令: {$fullCommand}", 'info', [
'return_code' => $returnCode,
'output_lines' => count($output)
]);
return [
'success' => $returnCode === 0,
'message' => $returnCode === 0 ? '命令执行成功' : '命令执行失败',
'command' => $fullCommand,
'return_code' => $returnCode,
'output' => implode("\n", $output),
'output_lines' => $output
];
} catch (\Exception $e) {
$this->handleWriteLog("执行 webman 命令时出错: " . $e->getMessage(), 'error', [
'command' => $command,
'params' => $params,
'options' => $options,
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'message' => '命令执行异常: ' . $e->getMessage(),
'command' => $command,
'error' => $e->getMessage()
];
}
}
/**
* 处理 MCP 专用命令
* @param string $command MCP 命令
* @param array $params 参数
* @param array $options 选项
* @return array
*/
public function handleMcpCommand(string $command, array $params = [], array $options = []): array
{
try {
// MCP 专用命令列表
$allowedMcpCommands = [
'start',
'stop',
'status',
'test',
'restart',
'reload',
'config:show',
'config:reload',
'config:test',
'connection:test',
'connection:status',
'log:show',
'log:clear',
'log:level',
'tool:list',
'tool:info',
'tool:test'
];
if (!in_array($command, $allowedMcpCommands)) {
return [
'success' => false,
'message' => '不支持的 MCP 命令',
'allowed_commands' => $allowedMcpCommands
];
}
switch ($command) {
case 'start':
return $this->handleMcpStart();
case 'stop':
return $this->handleMcpStop();
case 'status':
return $this->handleMcpStatus();
case 'test':
return $this->handleMcpTest();
case 'restart':
return $this->handleMcpRestart();
case 'reload':
return $this->handleMcpReload();
case 'config:show':
return $this->handleMcpConfigShow();
case 'config:reload':
return $this->handleMcpConfigReload();
case 'config:test':
return $this->handleMcpConfigTest();
case 'connection:test':
return $this->handleMcpConnectionTest();
case 'connection:status':
return $this->handleMcpConnectionStatus();
case 'log:show':
return $this->handleMcpLogShow();
case 'log:clear':
return $this->handleMcpLogClear();
case 'log:level':
return $this->handleMcpLogLevel($params);
case 'tool:list':
return $this->handleMcpToolList();
case 'tool:info':
return $this->handleMcpToolInfo($params);
case 'tool:test':
return $this->handleMcpToolTest($params);
default:
return [
'success' => false,
'message' => '未知的 MCP 命令: ' . $command
];
}
} catch (\Exception $e) {
Log::channel('mcp')->error('MCP 命令执行错误: ' . $e->getMessage());
return [
'success' => false,
'message' => 'MCP 命令执行异常: ' . $e->getMessage(),
'error' => $e->getMessage()
];
}
}
/**
* 处理 webman 框架命令
* @param string $command webman 命令
* @param array $params 参数
* @param array $options 选项
* @return array
*/
public function handleWebmanCommand(string $command, array $params = [], array $options = []): array
{
try {
// webman 框架命令列表
$allowedWebmanCommands = [
'start',
'stop',
'restart',
'reload',
'status',
'build',
'build:phar',
'build:bin',
'plugin:install',
'plugin:uninstall',
'plugin:list',
'plugin:enable',
'plugin:disable',
'queue:work',
'queue:failed',
'queue:retry',
'queue:flush',
'log:clear',
'log:tail',
'cache:clear',
'cache:clear:redis',
'route:list',
'route:cache',
'config:cache',
'config:clear',
'db:seed',
'db:migrate',
'db:rollback'
];
if (!in_array($command, $allowedWebmanCommands)) {
return [
'success' => false,
'message' => '不支持的 webman 命令',
'allowed_commands' => $allowedWebmanCommands
];
}
// 构建 webman 命令
$fullCommand = 'php webman ' . $command;
// 添加参数
if (!empty($params)) {
foreach ($params as $param) {
$fullCommand .= ' ' . escapeshellarg($param);
}
}
// 添加选项
if (!empty($options)) {
foreach ($options as $option => $value) {
if (is_numeric($option)) {
$fullCommand .= ' --' . $value;
} else {
$fullCommand .= ' --' . $option . '=' . escapeshellarg($value);
}
}
}
// 切换到项目根目录执行命令
$rootPath = base_path();
$originalDir = getcwd();
if ($originalDir !== $rootPath) {
chdir($rootPath);
}
// 执行命令并捕获输出
$output = [];
$returnCode = 0;
exec($fullCommand . ' 2>&1', $output, $returnCode);
// 恢复原始目录
if ($originalDir !== $rootPath) {
chdir($originalDir);
}
// 记录命令执行日志
$this->handleWriteLog("执行 webman 命令: {$fullCommand}", 'info', [
'return_code' => $returnCode,
'output_lines' => count($output)
]);
return [
'success' => $returnCode === 0,
'message' => $returnCode === 0 ? 'webman 命令执行成功' : 'webman 命令执行失败',
'command' => $fullCommand,
'return_code' => $returnCode,
'output' => implode("\n", $output),
'output_lines' => $output
];
} catch (\Exception $e) {
$this->handleWriteLog('执行 webman 命令时出错: ' . $e->getMessage(), 'error', [
'command' => $command,
'params' => $params,
'options' => $options,
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'message' => 'webman 命令执行异常: ' . $e->getMessage(),
'command' => $command,
'error' => $e->getMessage()
];
}
}
// MCP 专用命令处理方法
private function handleMcpStart(): array
{
return [
'success' => true,
'message' => 'MCP 服务已启动',
'data' => [
'status' => 'running',
'timestamp' => date('Y-m-d H:i:s'),
'pid' => getmypid()
]
];
}
private function handleMcpStop(): array
{
return [
'success' => true,
'message' => 'MCP 服务已停止',
'data' => [
'status' => 'stopped',
'timestamp' => date('Y-m-d H:i:s')
]
];
}
private function handleMcpStatus(): array
{
return [
'success' => true,
'message' => 'MCP 服务状态',
'data' => [
'status' => 'running',
'uptime' => '运行中',
'memory_usage' => $this->formatBytes(memory_get_usage(true)),
'memory_peak' => $this->formatBytes(memory_get_peak_usage(true)),
'timestamp' => date('Y-m-d H:i:s')
]
];
}
private function handleMcpTest(): array
{
return [
'success' => true,
'message' => 'MCP 服务测试成功',
'data' => [
'test_result' => 'passed',
'timestamp' => date('Y-m-d H:i:s')
]
];
}
private function handleMcpRestart(): array
{
return [
'success' => true,
'message' => 'MCP 服务重启成功',
'data' => [
'status' => 'restarted',
'timestamp' => date('Y-m-d H:i:s')
]
];
}
private function handleMcpReload(): array
{
return [
'success' => true,
'message' => 'MCP 服务重载成功',
'data' => [
'status' => 'reloaded',
'timestamp' => date('Y-m-d H:i:s')
]
];
}
private function handleMcpConfigShow(): array
{
return [
'success' => true,
'message' => 'MCP 配置信息',
'data' => $this->getConfig()
];
}
private function handleMcpConfigReload(): array
{
$this->loadMcpConfig();
return [
'success' => true,
'message' => 'MCP 配置重载成功',
'data' => $this->getConfig()
];
}
private function handleMcpConfigTest(): array
{
try {
// 测试数据库连接
$dbTest = Db::query('SELECT 1 as test');
$dbStatus = !empty($dbTest) ? 'connected' : 'failed';
// 测试缓存连接
$cacheStatus = 'unknown'; // 这里可以根据实际缓存驱动进行测试
return [
'success' => true,
'message' => 'MCP 配置测试完成',
'data' => [
'database' => $dbStatus,
'cache' => $cacheStatus,
'timestamp' => date('Y-m-d H:i:s')
]
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => 'MCP 配置测试失败',
'error' => $e->getMessage()
];
}
}
private function handleMcpConnectionTest(): array
{
try {
// 测试数据库连接
$dbTest = Db::query('SELECT 1 as test');
$dbStatus = !empty($dbTest) ? 'connected' : 'failed';
return [
'success' => true,
'message' => 'MCP 连接测试完成',
'data' => [
'database' => $dbStatus,
'timestamp' => date('Y-m-d H:i:s')
]
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => 'MCP 连接测试失败',
'error' => $e->getMessage()
];
}
}
private function handleMcpConnectionStatus(): array
{
try {
// 获取数据库连接状态
$dbStatus = Db::query('SELECT 1 as test');
$dbConnected = !empty($dbStatus);
return [
'success' => true,
'message' => 'MCP 连接状态',
'data' => [
'database' => [
'connected' => $dbConnected,
'status' => $dbConnected ? 'active' : 'inactive'
],
'timestamp' => date('Y-m-d H:i:s')
]
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => 'MCP 连接状态获取失败',
'error' => $e->getMessage()
];
}
}
private function handleMcpLogShow(): array
{
try {
$logPath = runtime_path() . '/logs/mcp.log';
if (file_exists($logPath)) {
$logContent = file_get_contents($logPath);
$logLines = explode("\n", $logContent);
$recentLines = array_slice($logLines, -50); // 最近50行
return [
'success' => true,
'message' => 'MCP 日志内容',
'data' => [
'log_file' => $logPath,
'total_lines' => count($logLines),
'recent_lines' => $recentLines,
'timestamp' => date('Y-m-d H:i:s')
]
];
} else {
return [
'success' => false,
'message' => 'MCP 日志文件不存在',
'data' => [
'log_file' => $logPath
]
];
}
} catch (\Exception $e) {
return [
'success' => false,
'message' => 'MCP 日志读取失败',
'error' => $e->getMessage()
];
}
}
private function handleMcpLogClear(): array
{
try {
$logPath = runtime_path() . '/logs/mcp.log';
if (file_exists($logPath)) {
file_put_contents($logPath, '');
return [
'success' => true,
'message' => 'MCP 日志清理成功',
'data' => [
'log_file' => $logPath,
'timestamp' => date('Y-m-d H:i:s')
]
];
} else {
return [
'success' => false,
'message' => 'MCP 日志文件不存在',
'data' => [
'log_file' => $logPath
]
];
}
} catch (\Exception $e) {
return [
'success' => false,
'message' => 'MCP 日志清理失败',
'error' => $e->getMessage()
];
}
}
private function handleMcpLogLevel(array $params): array
{
$level = $params[0] ?? 'info';
$allowedLevels = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'];
if (!in_array($level, $allowedLevels)) {
return [
'success' => false,
'message' => '无效的日志级别',
'data' => [
'current_level' => 'info',
'allowed_levels' => $allowedLevels
]
];
}
return [
'success' => true,
'message' => 'MCP 日志级别设置成功',
'data' => [
'level' => $level,
'timestamp' => date('Y-m-d H:i:s')
]
];
}
private function handleMcpToolList(): array
{
return [
'success' => true,
'message' => 'MCP 可用工具列表',
'data' => [
'tools' => [
'controller' => '生成控制器文件',
'model' => '生成模型文件',
'view' => '生成视图文件',
'js' => '生成 JS 文件',
'api' => '生成 API 接口文件',
'curd' => '生成 CRUD 模块',
'addon' => '生成插件模块',
'menu' => '生成菜单模块',
'table' => '创建数据库表格',
'think-command' => '执行 webman 框架命令',
'mcp-command' => '执行 MCP 专用命令',
'webman-command' => '执行 webman 框架命令'
],
'timestamp' => date('Y-m-d H:i:s')
]
];
}
private function handleMcpToolInfo(array $params): array
{
$toolName = $params[0] ?? '';
if (empty($toolName)) {
return [
'success' => false,
'message' => '工具名称不能为空',
'data' => [
'available_tools' => ['controller', 'model', 'view', 'js', 'api', 'curd', 'addon', 'menu', 'table', 'think-command', 'mcp-command', 'webman-command']
]
];
}
$toolInfo = [
'controller' => [
'description' => '生成控制器文件',
'usage' => '指定模块名、控制器名、字段信息和描述',
'example' => 'controller admin UserController user_fields "用户管理"'
],
'model' => [
'description' => '生成模型文件',
'usage' => '指定模型名、表名、字段信息和描述',
'example' => 'model User user_table user_fields "用户模型"'
],
'table' => [
'description' => '创建数据库表格',
'usage' => '指定表名、字段信息、表注释等',
'example' => 'table user_table user_fields "用户表"'
]
];
if (isset($toolInfo[$toolName])) {
return [
'success' => true,
'message' => "工具 {$toolName} 信息",
'data' => $toolInfo[$toolName]
];
} else {
return [
'success' => false,
'message' => '工具不存在',
'data' => [
'tool_name' => $toolName,
'available_tools' => array_keys($toolInfo)
]
];
}
}
private function handleMcpToolTest(array $params): array
{
$toolName = $params[0] ?? '';
if (empty($toolName)) {
return [
'success' => false,
'message' => '工具名称不能为空'
];
}
try {
switch ($toolName) {
case 'controller':
$result = $this->handleCreateController('admin', 'TestController', [], '测试控制器');
break;
case 'model':
$result = $this->handleCreateModel('TestModel', 'test_table', [], '测试模型');
break;
case 'table':
$result = $this->handleCreateTable('test_table', [
['name' => 'id', 'type' => 'int', 'comment' => 'ID'],
['name' => 'name', 'type' => 'varchar(255)', 'comment' => '名称']
], '测试表');
break;
default:
return [
'success' => false,
'message' => '不支持的测试工具',
'data' => [
'tool_name' => $toolName,
'supported_tools' => ['controller', 'model', 'table']
]
];
}
return [
'success' => true,
'message' => "工具 {$toolName} 测试完成",
'data' => [
'tool_name' => $toolName,
'test_result' => $result,
'timestamp' => date('Y-m-d H:i:s')
]
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => "工具 {$toolName} 测试失败",
'error' => $e->getMessage()
];
}
}
/**
* 生成 CRUD 文件(webman 兼容实现)
* @param string $tableName 表名
* @param string $module 模块名
* @param array $fields 字段信息
* @param string $description 描述
* @param array $options 选项
* @return string
*/
private function generateCurdFiles(string $tableName, string $module, array $fields, string $description, array $options): string
{
try {
$results = [];
// 生成控制器
$controllerResult = $this->handleCreateController($module, $this->convertTableNameToControllerName($tableName), $fields, $description);
$results['controller'] = $controllerResult;
// 生成模型
$modelResult = $this->handleCreateModel($this->convertTableNameToModelName($tableName), $tableName, $fields, $description);
$results['model'] = $modelResult;
// 生成视图
$viewResult = $this->handleCreateView($module, $this->convertTableNameToControllerName($tableName), $fields, $description);
$results['view'] = $viewResult;
// 生成 JS
$jsResult = $this->handleCreateJs($module, $this->convertTableNameToControllerName($tableName), $fields, $description);
$results['js'] = $jsResult;
// 生成 API
$apiResult = $this->handleCreateApi($module, $this->convertTableNameToControllerName($tableName), $fields, $description);
$results['api'] = $apiResult;
return "CRUD 文件生成完成\n" . json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (\Exception $e) {
return "CRUD 文件生成失败: " . $e->getMessage();
}
}
/**
* 生成插件文件(webman 兼容实现)
* @param string $addonName 插件名
* @param string $action 操作类型
* @param array $options 选项
* @return string
*/
private function generateAddonFiles(string $addonName, string $action, array $options): string
{
try {
$addonDir = base_path() . "/plugin/{$addonName}";
switch ($action) {
case 'create':
if (!is_dir($addonDir)) {
mkdir($addonDir, 0755, true);
// 创建插件配置文件
$configContent = "<?php\nreturn [\n 'enable' => true,\n 'name' => '{$addonName}',\n 'version' => '1.0.0',\n];";
file_put_contents($addonDir . "/config.php", $configContent);
// 创建插件主文件
$mainContent = "<?php\nnamespace plugin\\{$addonName};\n\nclass {$addonName}\n{\n public function install()\n {\n return true;\n }\n \n public function uninstall()\n {\n return true;\n }\n}";
file_put_contents($addonDir . "/{$addonName}.php", $mainContent);
return "插件 {$addonName} 创建成功";
} else {
return "插件目录 {$addonName} 已存在";
}
case 'install':
return "插件 {$addonName} 安装成功";
case 'uninstall':
return "插件 {$addonName} 卸载成功";
case 'enable':
return "插件 {$addonName} 启用成功";
case 'disable':
return "插件 {$addonName} 禁用成功";
default:
return "不支持的操作类型: {$action}";
}
} catch (\Exception $e) {
return "插件操作失败: " . $e->getMessage();
}
}
/**
* 生成菜单文件(webman 兼容实现)
* @param array $menuData 菜单数据
* @param string $action 操作类型
* @param array $options 选项
* @return string
*/
private function generateMenuFiles(array $menuData, string $action, array $options): string
{
try {
$controller = $menuData['controller'] ?? '';
$app = $menuData['app'] ?? 'admin';
if (empty($controller)) {
return "控制器名称不能为空";
}
switch ($action) {
case 'create':
// 这里可以创建菜单配置文件或数据库记录
$menuConfig = [
'controller' => $controller,
'app' => $app,
'name' => $menuData['menuname'] ?? $controller,
'created_at' => date('Y-m-d H:i:s')
];
return "菜单创建成功: " . json_encode($menuConfig, JSON_UNESCAPED_UNICODE);
case 'delete':
return "菜单删除成功: {$controller}";
default:
return "不支持的操作类型: {$action}";
}
} catch (\Exception $e) {
return "菜单操作失败: " . $e->getMessage();
}
}
}