mirror of
https://github.com/GSManagerXZ/GameServerManager.git
synced 2026-05-31 18:09:38 +08:00
新增启动命令识别
This commit is contained in:
@@ -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="启动命令(自动生成,可手动修改)"
|
||||
/>
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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": "美国卡车模拟",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user