feat: 添加Docker部署模式支持并优化后端服务检测

refactor(backend-manager): 重构后端服务检测逻辑,支持Docker模式
fix(http-client): 兼容不同版本响应格式
chore: 添加@modelcontextprotocol/sdk依赖
This commit is contained in:
YAYOI27
2025-08-25 10:59:03 +08:00
parent b02b27075e
commit 959aa70e41
5 changed files with 1178 additions and 5 deletions

1046
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.4"
}
}

View File

@@ -4,6 +4,10 @@ import path from 'path';
import { HttpClient } from './http-client.js';
import { Config } from './config.js';
import { ActivityMonitor } from './activity-monitor.js';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
/**
* 后端服务管理器
@@ -53,6 +57,60 @@ export class BackendManager {
}
}
/**
* 智能检测Docker容器状态
*/
private async detectDockerContainer(): Promise<boolean> {
try {
// 检查Docker是否可用
await execAsync('docker --version');
// 检查是否有运行中的pansou容器
const { stdout } = await execAsync('docker ps --format "{{.Names}}" --filter "name=pansou"');
const runningContainers = stdout.trim().split('\n').filter(name => name.includes('pansou'));
if (runningContainers.length > 0) {
console.error(`🐳 检测到运行中的Docker容器: ${runningContainers.join(', ')}`);
return true;
}
return false;
} catch (error) {
// Docker不可用或没有容器运行
return false;
}
}
/**
* 智能检测部署模式
* @returns 'docker' | 'source' | 'unknown'
*/
private async detectDeploymentMode(): Promise<'docker' | 'source' | 'unknown'> {
// 1. 首先检查是否有Docker容器运行
const hasDockerContainer = await this.detectDockerContainer();
if (hasDockerContainer) {
return 'docker';
}
// 2. 检查是否有Go可执行文件
const execPath = await this.findGoExecutable();
if (execPath) {
return 'source';
}
// 3. 检查服务是否已经在运行(可能是手动启动的)
this.httpClient.setSilentMode(true);
const isRunning = await this.isBackendRunning();
this.httpClient.setSilentMode(false);
if (isRunning) {
console.error('✅ 检测到后端服务已在运行(可能是手动启动)');
return 'source'; // 假设是源码模式
}
return 'unknown';
}
/**
* 查找Go可执行文件路径
*/
@@ -110,7 +168,65 @@ export class BackendManager {
return true;
}
// 首先检查是否已有服务在运行
// 智能检测部署模式如果未明确配置Docker模式
let effectiveDockerMode = this.config.dockerMode;
if (!effectiveDockerMode) {
console.error('🔍 正在智能检测部署模式...');
const detectedMode = await this.detectDeploymentMode();
switch (detectedMode) {
case 'docker':
console.error('🐳 智能检测使用Docker部署模式');
effectiveDockerMode = true;
break;
case 'source':
console.error('📦 智能检测:使用源码部署模式');
effectiveDockerMode = false;
break;
case 'unknown':
console.error('❓ 无法检测部署模式,使用默认源码模式');
effectiveDockerMode = false;
break;
}
} else {
console.error(`⚙️ 使用配置指定的模式: ${effectiveDockerMode ? 'Docker' : '源码'}`);
}
// Docker模式处理
if (effectiveDockerMode) {
console.error('🐳 Docker模式已启用正在检查后端服务连接...');
// Docker模式下进行重试检查因为容器可能需要时间启动
const maxRetries = 3;
const retryDelay = 2000; // 2秒
this.httpClient.setSilentMode(true);
for (let i = 0; i < maxRetries; i++) {
const isRunning = await this.isBackendRunning();
if (isRunning) {
this.httpClient.setSilentMode(false);
console.error('✅ Docker模式下后端服务连接成功');
return true;
}
if (i < maxRetries - 1) {
console.error(`🔄 连接尝试 ${i + 1}/${maxRetries} 失败,${retryDelay/1000}秒后重试...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
this.httpClient.setSilentMode(false);
console.error('❌ Docker模式下后端服务连接失败');
console.error('请确保Docker容器正在运行');
console.error(' docker-compose up -d');
console.error('或检查Docker容器状态');
console.error(' docker ps');
return false;
}
// 源码模式:首先检查是否已有服务在运行
this.httpClient.setSilentMode(true);
const isRunning = await this.isBackendRunning();
this.httpClient.setSilentMode(false);
@@ -124,7 +240,8 @@ export class BackendManager {
const execPath = await this.findGoExecutable();
if (!execPath) {
console.error('❌ 未找到PanSou后端可执行文件');
console.error('请确保在项目根目录下存在以下文件之一:');
console.error('如果您使用Docker部署请在MCP配置中设置 DOCKER_MODE=true');
console.error('如果您使用源码部署,请确保在项目根目录下存在以下文件之一:');
console.error(' - pansou.exe / pansou');
console.error(' - main.exe / main');
return false;

View File

@@ -19,7 +19,9 @@ const ConfigSchema = z.object({
idleTimeout: z.number().positive().default(300000), // 默认5分钟
enableIdleShutdown: z.boolean().default(true),
// 项目根目录路径
projectRootPath: z.string().optional()
projectRootPath: z.string().optional(),
// Docker部署模式当设置为true时不会尝试启动本地进程
dockerMode: z.boolean().default(false)
});
export type Config = z.infer<typeof ConfigSchema>;
@@ -56,7 +58,9 @@ export function loadConfig(): Config {
idleTimeout: process.env.IDLE_TIMEOUT ? parseInt(process.env.IDLE_TIMEOUT) : undefined,
enableIdleShutdown: process.env.ENABLE_IDLE_SHUTDOWN !== 'false', // 默认为true除非明确设置为false
// 项目根目录路径
projectRootPath: process.env.PROJECT_ROOT_PATH
projectRootPath: process.env.PROJECT_ROOT_PATH,
// Docker部署模式
dockerMode: process.env.DOCKER_MODE === 'true'
};
// 移除undefined值让zod使用默认值

View File

@@ -186,7 +186,8 @@ export class HttpClient {
const response: AxiosResponse<ApiResponse<SearchResponseData>> = await this.client.post('/api/search', requestData);
if (response.data.code !== 200) {
// 兼容不同版本的响应格式源码版本使用200Docker版本使用0
if (response.data.code !== 200 && response.data.code !== 0) {
throw new Error(response.data.message || '搜索请求失败');
}