新增启动命令识别

This commit is contained in:
yxsj245
2025-10-29 16:43:03 +08:00
parent c3e418bf16
commit 8a2927026c
5 changed files with 250 additions and 12 deletions

View File

@@ -167,6 +167,7 @@ const GameDeploymentPage: React.FC = () => {
const [instanceName, setInstanceName] = useState('')
const [instanceDescription, setInstanceDescription] = useState('')
const [instanceStartCommand, setInstanceStartCommand] = useState('')
const [startCommandEdited, setStartCommandEdited] = useState(false)
const [creatingInstance, setCreatingInstance] = useState(false)
// 更多游戏部署相关状态
@@ -1683,14 +1684,97 @@ const GameDeploymentPage: React.FC = () => {
try {
setCreatingInstance(true)
// 生成启动命令考虑选中的Java版本
let finalStartCommand = instanceStartCommand || generateStartCommand(selectedServer, selectedMinecraftJava)
// 首先扫描Minecraft目录以智能检测启动文件
console.log('[Minecraft实例创建] 开始扫描目录:', downloadResult.targetDirectory)
console.log('[Minecraft实例创建] 用户输入的启动命令:', instanceStartCommand)
console.log('[Minecraft实例创建] 选择的Java版本:', selectedMinecraftJava)
let finalStartCommand = ''
let scanSuccess = false
try {
const scanResult = await apiClient.scanMinecraftDirectory(downloadResult.targetDirectory)
console.log('[Minecraft实例创建] 扫描API响应:', scanResult)
if (scanResult.success && scanResult.data) {
console.log('[Minecraft实例创建] 扫描数据:', {
jarFiles: scanResult.data.jarFiles,
batFiles: scanResult.data.batFiles,
shFiles: scanResult.data.shFiles,
recommendedStartCommand: scanResult.data.recommendedStartCommand,
startMethod: scanResult.data.startMethod,
platform: scanResult.data.platform
})
if (scanResult.data.recommendedStartCommand) {
const recommendedCommand = scanResult.data.recommendedStartCommand
scanSuccess = true
console.log('[Minecraft实例创建] 扫描成功,推荐命令:', recommendedCommand)
// 优先使用推荐命令,除非用户手动修改过启动命令
const useRecommended = !startCommandEdited || !instanceStartCommand.trim()
if (useRecommended) {
console.log('[Minecraft实例创建] 启动命令未被手动修改,使用智能检测结果')
// 根据扫描结果的启动方式决定如何生成命令
if (scanResult.data.startMethod === 'jar_file' && selectedMinecraftJava !== 'default') {
const javaExecutable = getSelectedJavaExecutable(selectedMinecraftJava)
finalStartCommand = recommendedCommand.replace(/^java\b/, javaExecutable)
console.log('[Minecraft实例创建] 替换Java路径:', javaExecutable)
} else {
finalStartCommand = recommendedCommand
console.log('[Minecraft实例创建] 直接使用推荐命令')
}
console.log('[Minecraft实例创建] 最终启动命令:', finalStartCommand)
// 同步到输入框,便于用户确认
setInstanceStartCommand(finalStartCommand)
// 如果用户手动输入了启动命令且选择了特定Java版本替换其中的java
if (instanceStartCommand && selectedMinecraftJava !== 'default') {
finalStartCommand = replaceJavaInCommand(instanceStartCommand, selectedMinecraftJava)
const startMethodText = scanResult.data.startMethod === 'bat_script' ? 'BAT脚本' :
scanResult.data.startMethod === 'sh_script' ? 'SH脚本' :
'JAR文件'
} else {
console.log('[Minecraft实例创建] 检测到用户已修改启动命令,保留用户输入')
finalStartCommand = selectedMinecraftJava !== 'default'
? replaceJavaInCommand(instanceStartCommand, selectedMinecraftJava)
: instanceStartCommand
}
} else {
console.log('[Minecraft实例创建] 未检测到推荐的启动命令')
}
} else {
console.log('[Minecraft实例创建] 扫描API返回失败或无数据')
}
} catch (scanError: any) {
console.error('[Minecraft实例创建] 目录扫描异常:', scanError)
console.error('[Minecraft实例创建] 错误详情:', scanError.message, scanError.stack)
scanSuccess = false
}
// 如果扫描失败或没有检测到启动文件,使用默认生成的启动命令
if (!scanSuccess || !finalStartCommand) {
console.log('[Minecraft实例创建] 使用默认启动命令生成逻辑')
finalStartCommand = instanceStartCommand || generateStartCommand(selectedServer, selectedMinecraftJava)
// 如果用户手动输入了启动命令且选择了特定Java版本替换其中的java
if (instanceStartCommand && selectedMinecraftJava !== 'default') {
finalStartCommand = replaceJavaInCommand(instanceStartCommand, selectedMinecraftJava)
}
console.log('[Minecraft实例创建] 默认启动命令:', finalStartCommand)
addNotification({
type: 'info',
title: '使用默认启动命令',
message: `启动命令: ${finalStartCommand}`,
duration: 3000
})
}
console.log('[Minecraft实例创建] 准备创建实例,启动命令:', finalStartCommand)
const response = await apiClient.createInstance({
name: instanceName.trim(),
description: instanceDescription.trim() || `Minecraft ${selectedServer} ${selectedVersion}`,
@@ -1720,6 +1804,7 @@ const GameDeploymentPage: React.FC = () => {
setInstanceName('')
setInstanceDescription('')
setInstanceStartCommand('')
setStartCommandEdited(false)
// 跳转到实例管理页面
navigate('/instances')
@@ -1828,6 +1913,7 @@ const GameDeploymentPage: React.FC = () => {
// 更新Minecraft实例启动命令
if (selectedServer && selectedVersion && selectedMinecraftJava) {
setInstanceStartCommand(generateStartCommand(selectedServer, selectedMinecraftJava))
setStartCommandEdited(false)
}
}, [selectedMinecraftJava, selectedServer])
@@ -3697,6 +3783,7 @@ const GameDeploymentPage: React.FC = () => {
// 自动生成启动命令
if (version && selectedServer) {
setInstanceStartCommand(generateStartCommand(selectedServer, selectedMinecraftJava))
setStartCommandEdited(false)
// 自动更新安装路径
setMinecraftInstallPath(generateMinecraftPath(selectedServer, version))
}
@@ -3862,9 +3949,33 @@ const GameDeploymentPage: React.FC = () => {
{selectedServer} {selectedVersion} {downloadResult.targetDirectory}
</p>
<button
onClick={() => {
onClick={async () => {
setInstanceName(`${selectedServer}-${selectedVersion}`)
setInstanceDescription(`Minecraft ${selectedServer} ${selectedVersion} 服务器`)
// 打开弹窗前预扫描并填充启动命令
try {
console.log('[Minecraft实例创建] 打开弹窗前进行目录扫描:', downloadResult.targetDirectory)
const scanResult = await apiClient.scanMinecraftDirectory(downloadResult.targetDirectory)
if (scanResult.success && scanResult.data?.recommendedStartCommand) {
let cmd = scanResult.data.recommendedStartCommand as string
if (scanResult.data.startMethod === 'jar_file' && selectedMinecraftJava !== 'default') {
const javaExecutable = getSelectedJavaExecutable(selectedMinecraftJava)
cmd = cmd.replace(/^java\b/, javaExecutable)
}
setInstanceStartCommand(cmd)
setStartCommandEdited(false)
} else {
const fallback = generateStartCommand(selectedServer, selectedMinecraftJava)
setInstanceStartCommand(fallback)
setStartCommandEdited(false)
}
} catch (e) {
const fallback = generateStartCommand(selectedServer, selectedMinecraftJava)
setInstanceStartCommand(fallback)
setStartCommandEdited(false)
}
setShowCreateInstanceModal(true)
setTimeout(() => setCreateInstanceModalAnimating(true), 10)
}}
@@ -5015,7 +5126,7 @@ const GameDeploymentPage: React.FC = () => {
<input
type="text"
value={instanceStartCommand}
onChange={(e) => setInstanceStartCommand(e.target.value)}
onChange={(e) => { setInstanceStartCommand(e.target.value); setStartCommandEdited(true) }}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="启动命令(自动生成,可手动修改)"
/>

View File

@@ -760,6 +760,11 @@ class ApiClient {
return this.post('/game-deployment/update-game-list')
}
// 扫描Minecraft目录中的启动文件
async scanMinecraftDirectory(directory: string) {
return this.post('/game-deployment/scan-minecraft-directory', { directory })
}
// RCON API
async getRconConfig(instanceId: string) {
return this.get(`/rcon/${instanceId}/config`)

View File

@@ -236,7 +236,8 @@
"appid": "1948160",
"tip": "游戏端口27015 UDP 100-200 UDP(查询端口可选开通)配置文件位置通用游戏存档路径1存档位置通用游戏存档路径1温馨提示此服务端需要自行从客户端生成配置文件上传才可以开服请将保存位置映射到容器外部防止存档丢失",
"image": "https://shared.cdn.queniuqe.com/store_item_assets/steam/apps/227300/header.jpg",
"url": "https://store.steampowered.com/app/227300/Euro_Truck_Simulator_2/"
"url": "https://store.steampowered.com/app/227300/Euro_Truck_Simulator_2/",
"docs": "https://docs.gsm.xiaozhuhouses.asia/%E6%B8%B8%E6%88%8F%E7%99%BE%E7%A7%91/Steam/%E6%AC%A7%E6%B4%B2%E5%8D%A1%E8%BD%A62%E9%A3%9F%E7%94%A8%E8%AF%B4%E6%98%8E.html"
},
"American_Truck_Simulator": {
"game_nameCN": "美国卡车模拟",

View File

@@ -384,11 +384,28 @@ export class GameConfigManager {
const sectionData = section.key === '' ? jsonData : (jsonData[section.key] || {})
for (const field of section.fields) {
const value = sectionData[field.name]
if (value !== undefined) {
result[section.key][field.name] = value
if (field.type === 'nested' && field.nested_fields) {
// 处理嵌套字段
const nestedValue = sectionData[field.name]
if (nestedValue !== undefined && typeof nestedValue === 'object') {
// JSON格式的嵌套字段本身就是对象直接使用
result[section.key][field.name] = nestedValue
} else {
// 使用默认值填充嵌套字段
const nestedDefaults: { [key: string]: any } = {}
for (const nestedField of field.nested_fields) {
nestedDefaults[nestedField.name] = nestedField.default
}
result[section.key][field.name] = nestedDefaults
}
} else {
result[section.key][field.name] = field.default
// 处理普通字段
const value = sectionData[field.name]
if (value !== undefined) {
result[section.key][field.name] = value
} else {
result[section.key][field.name] = field.default
}
}
}
}

View File

@@ -779,4 +779,108 @@ router.post('/update-game-list', authenticateToken, async (req: Request, res: Re
}
})
// 扫描Minecraft目录中的启动文件
router.post('/scan-minecraft-directory', authenticateToken, async (req: Request, res: Response) => {
try {
const { directory } = req.body
if (!directory) {
return res.status(400).json({
success: false,
error: '缺少必填参数',
message: '目录路径为必填项'
})
}
logger.info(`扫描Minecraft目录: ${directory}`)
try {
// 检查目录是否存在
await fs.access(directory)
} catch {
return res.status(400).json({
success: false,
error: '目录不存在',
message: `指定的目录不存在: ${directory}`
})
}
try {
const files = await fs.readdir(directory)
const platform = os.platform()
const isWindows = platform === 'win32'
// 查找.jar文件
const jarFiles = files.filter(file => file.toLowerCase().endsWith('.jar'))
// 查找启动脚本
const batFiles = files.filter(file => file.toLowerCase().endsWith('.bat'))
const shFiles = files.filter(file => file.toLowerCase().endsWith('.sh'))
logger.info(`找到文件: jar=${jarFiles.length}, bat=${batFiles.length}, sh=${shFiles.length}`)
// 确定推荐的启动方式
let recommendedStartCommand = ''
let startMethod = 'none'
// 优先使用对应平台的启动脚本
if (isWindows && batFiles.length > 0) {
// Windows平台优先使用.bat脚本
// 优先选择run.bat否则使用第一个找到的.bat文件
const runBat = batFiles.find(f => f.toLowerCase() === 'run.bat')
const scriptFile = runBat || batFiles[0]
recommendedStartCommand = scriptFile
startMethod = 'bat_script'
logger.info(`[智能检测] 推荐使用BAT脚本: ${scriptFile}`)
} else if (!isWindows && shFiles.length > 0) {
// Linux/Mac平台优先使用.sh脚本
// 优先选择run.sh否则使用第一个找到的.sh文件
const runSh = shFiles.find(f => f.toLowerCase() === 'run.sh')
const scriptFile = runSh || shFiles[0]
recommendedStartCommand = `./${scriptFile}`
startMethod = 'sh_script'
logger.info(`[智能检测] 推荐使用SH脚本: ${scriptFile}`)
} else if (jarFiles.length > 0) {
// 如果没有对应平台的脚本使用jar文件
// 优先选择server.jar否则使用第一个找到的jar文件
const serverJar = jarFiles.find(f => f.toLowerCase() === 'server.jar')
const jarFile = serverJar || jarFiles[0]
recommendedStartCommand = `java -jar ${jarFile}`
startMethod = 'jar_file'
logger.info(`[智能检测] 推荐使用JAR文件: ${jarFile}, 完整命令: ${recommendedStartCommand}`)
} else {
logger.warn(`[智能检测] 未找到任何启动文件 (jar/bat/sh)`)
}
res.json({
success: true,
data: {
jarFiles,
batFiles,
shFiles,
recommendedStartCommand,
startMethod,
platform: isWindows ? 'Windows' : (platform === 'darwin' ? 'MacOS' : 'Linux')
}
})
} catch (error: any) {
logger.error('读取目录文件失败:', error)
res.status(500).json({
success: false,
error: '读取目录失败',
message: error.message
})
}
} catch (error: any) {
logger.error('扫描Minecraft目录失败:', error)
res.status(500).json({
success: false,
error: '扫描目录失败',
message: error.message
})
}
})
export default router