新增新的构建平台

This commit is contained in:
yxsj245
2025-10-26 17:08:27 +08:00
parent 344497b37d
commit 8e560a06fd
4 changed files with 506 additions and 82 deletions

View File

@@ -284,6 +284,12 @@ const GameDeploymentPage: React.FC = () => {
const [cloudInstanceStartCommand, setCloudInstanceStartCommand] = useState('')
const [creatingCloudInstance, setCreatingCloudInstance] = useState(false)
// Modrinth 整合包相关状态
const [modrinthPackId, setModrinthPackId] = useState('')
const [modrinthVersion, setModrinthVersion] = useState('')
const [modrinthCacheList, setModrinthCacheList] = useState<any[]>([])
const [modrinthCacheLoading, setModrinthCacheLoading] = useState(false)
// SteamCMD高级选项
const [showAdvanced, setShowAdvanced] = useState(false)
const [steamcmdCommand, setSteamcmdCommand] = useState('')
@@ -446,8 +452,95 @@ const GameDeploymentPage: React.FC = () => {
}
}
// 获取 Modrinth 缓存列表
const fetchModrinthCache = async () => {
try {
setModrinthCacheLoading(true)
const response = await apiClient.getModrinthCache()
if (response.success && response.data) {
setModrinthCacheList(response.data)
} else {
addNotification({
type: 'error',
title: '获取缓存列表失败',
message: response.message || '无法获取 Modrinth 缓存列表'
})
}
} catch (error: any) {
console.error('获取 Modrinth 缓存列表失败:', error)
addNotification({
type: 'error',
title: '获取缓存列表失败',
message: error.message || '网络请求失败'
})
} finally {
setModrinthCacheLoading(false)
}
}
// 开始云构建
const handleCloudBuild = async () => {
// Modrinth 类型的验证
if (cloudBuildType === 'modrinth') {
if (!modrinthPackId || !modrinthVersion) {
addNotification({
type: 'warning',
title: '请填写整合包信息',
message: '请填写整合包 ID/URL 和版本号'
})
return
}
if (!cloudBuildPath) {
addNotification({
type: 'warning',
title: '请选择部署路径',
message: '请先选择整合包部署路径'
})
return
}
try {
setBuildingCloud(true)
setCloudBuildLogs(['开始构建 Modrinth 整合包任务...'])
setCloudBuildComplete(false)
setCloudBuildResult(null)
const response = await apiClient.createCloudBuildTask({
coreName: modrinthPackId,
version: modrinthVersion,
type: 'modrinth'
})
if (response.success && response.data) {
if (response.data.fileName) {
// 缓存命中,使用 fileName 让后端生成下载链接
setCloudBuildLogs(prev => [...prev, '从缓存获取整合包文件...'])
await handleCloudDownload(response.data.fileName, true)
} else if (response.data.taskId) {
// 需要构建,开始监控进度
setCloudBuildTask(response.data)
setCloudBuildLogs(prev => [...prev, '构建任务已创建,正在查询状态...'])
monitorCloudBuildProgress(response.data.taskId)
} else {
throw new Error('无效的响应数据')
}
} else {
throw new Error(response.message || '创建构建任务失败')
}
} catch (error: any) {
console.error('Modrinth 整合包构建失败:', error)
addNotification({
type: 'error',
title: '构建失败',
message: error.message || '创建构建任务失败'
})
setBuildingCloud(false)
}
return
}
// msl_Official 类型的验证
if (!selectedCloudCore || !selectedCloudVersion) {
addNotification({
type: 'warning',
@@ -479,15 +572,17 @@ const GameDeploymentPage: React.FC = () => {
})
if (response.success && response.data) {
if (response.data.cached) {
// 缓存命中,直接下载
if (response.data.fileName) {
// 缓存命中,使用 fileName 让后端生成下载链接
setCloudBuildLogs(prev => [...prev, '从缓存获取服务端文件...'])
await handleCloudDownload(response.data.fileName, null)
await handleCloudDownload(response.data.fileName, true)
} else if (response.data.taskId) {
// 需要构建,监控进度
// 需要构建,开始监控进度
setCloudBuildTask(response.data)
setCloudBuildLogs(prev => [...prev, '构建任务已创建,正在处理...'])
setCloudBuildLogs(prev => [...prev, '构建任务已创建,正在查询状态...'])
monitorCloudBuildProgress(response.data.taskId)
} else {
throw new Error('无效的响应数据')
}
} else {
throw new Error(response.message || '创建构建任务失败')
@@ -533,8 +628,13 @@ const GameDeploymentPage: React.FC = () => {
if (status === 'COMPLETED') {
// 停止监控
isMonitoring = false
setCloudBuildLogs(prev => [...prev, '构建完成,开始下载...'])
await handleCloudDownload(null, taskId)
if (downloadUrl) {
setCloudBuildLogs(prev => [...prev, '构建完成,开始下载...'])
// 使用返回的 downloadUrl 直接下载
await handleCloudDownload(downloadUrl, false)
} else {
throw new Error('任务完成但未返回下载链接')
}
} else if (status === 'FAILED') {
// 停止监控
isMonitoring = false
@@ -542,10 +642,6 @@ const GameDeploymentPage: React.FC = () => {
} else if (status === 'PROCESSING' || status === 'QUEUED') {
// 继续监控
setTimeout(() => checkProgress(), 2000)
} else {
// 未知状态,停止监控
isMonitoring = false
console.warn('未知的构建状态:', status)
}
}
} catch (error: any) {
@@ -564,15 +660,16 @@ const GameDeploymentPage: React.FC = () => {
}
// 下载并解压云构建文件
const handleCloudDownload = async (fileName: string | null, taskId: string | null) => {
const handleCloudDownload = async (downloadUrlOrFileName: string, isFileName: boolean = false) => {
try {
setCloudBuildLogs(prev => [...prev, '开始下载服务端文件...'])
const isModrinth = cloudBuildType === 'modrinth'
setCloudBuildLogs(prev => [...prev, `开始下载${isModrinth ? '整合包' : '服务端'}文件...`])
const response = await apiClient.downloadAndExtractCloudBuild({
fileName: fileName || undefined,
taskId: taskId || undefined,
coreName: selectedCloudCore.name,
version: selectedCloudVersion,
downloadUrl: isFileName ? undefined : downloadUrlOrFileName,
fileName: isFileName ? downloadUrlOrFileName : undefined,
coreName: isModrinth ? modrinthPackId : selectedCloudCore.name,
version: isModrinth ? modrinthVersion : selectedCloudVersion,
targetPath: cloudBuildPath
})
@@ -588,17 +685,18 @@ const GameDeploymentPage: React.FC = () => {
setCloudBuildComplete(true)
setCloudBuildResult({
coreName: selectedCloudCore.name,
version: selectedCloudVersion,
coreName: isModrinth ? modrinthPackId : selectedCloudCore.name,
version: isModrinth ? modrinthVersion : selectedCloudVersion,
path: cloudBuildPath,
startCommand
startCommand,
type: cloudBuildType
})
setBuildingCloud(false)
addNotification({
type: 'success',
title: '部署完成',
message: '服务端文件已成功下载并解压到部署目录'
message: `${isModrinth ? '整合包' : '服务端'}文件已成功下载并解压到部署目录`
})
} else {
throw new Error(response.message || '下载并解压失败')
@@ -608,7 +706,7 @@ const GameDeploymentPage: React.FC = () => {
addNotification({
type: 'error',
title: '下载失败',
message: error.message || '下载服务端文件失败'
message: error.message || `下载${cloudBuildType === 'modrinth' ? '整合包' : '服务端'}文件失败`
})
setBuildingCloud(false)
}
@@ -1760,6 +1858,49 @@ const GameDeploymentPage: React.FC = () => {
}
}, [])
// 当云构建平台类型或标签页变化时,加载对应数据
useEffect(() => {
if (activeTab === 'cloud-build') {
if (cloudBuildType === 'modrinth') {
fetchModrinthCache()
} else if (cloudBuildType === 'msl_Official') {
fetchCloudBuildCores()
fetchCloudBuildStats()
}
}
}, [cloudBuildType, activeTab])
// 当 Modrinth 整合包 ID 和版本变化时,自动更新路径
useEffect(() => {
if (cloudBuildType === 'modrinth' && modrinthPackId && modrinthVersion && defaultGamePath) {
// 清理整合包 ID移除特殊字符和 URL 前缀
let cleanPackId = modrinthPackId
// 如果是 URL提取最后的 ID 部分
if (cleanPackId.includes('/')) {
const parts = cleanPackId.split('/')
cleanPackId = parts[parts.length - 1]
}
// 移除特殊字符
cleanPackId = cleanPackId.replace(/[<>:"|?*]/g, '').trim()
const cleanVersion = modrinthVersion.replace(/[<>:"|?*]/g, '').trim()
if (cleanPackId && cleanVersion) {
// 组合文件夹名称
const folderName = `modrinth-${cleanPackId}-${cleanVersion}`
// 根据平台使用正确的路径分隔符
const isWindows = systemInfo?.platform === 'win32'
const separator = isWindows ? '\\' : '/'
// 确保基础路径以分隔符结尾
const normalizedBasePath = defaultGamePath.endsWith(separator) || defaultGamePath.endsWith('/') || defaultGamePath.endsWith('\\')
? defaultGamePath
: defaultGamePath + separator
setCloudBuildPath(normalizedBasePath + folderName)
}
}
}, [modrinthPackId, modrinthVersion, cloudBuildType, defaultGamePath, systemInfo])
// 自动生成和更新SteamCMD命令
useEffect(() => {
@@ -3056,47 +3197,98 @@ const GameDeploymentPage: React.FC = () => {
</label>
<select
value={cloudBuildType}
onChange={(e) => setCloudBuildType(e.target.value)}
onChange={(e) => {
setCloudBuildType(e.target.value)
// 重置选择状态
setSelectedCloudCore(null)
setSelectedCloudVersion('')
setModrinthPackId('')
setModrinthVersion('')
setCloudBuildComplete(false)
setCloudBuildResult(null)
setCloudBuildLogs([])
}}
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"
disabled={buildingCloud}
>
<option value="msl_Official">Java部署</option>
<option value="modrinth">Modrinth </option>
</select>
</div>
{/* 核心选择 */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
</label>
{cloudBuildCoresLoading ? (
<div className="flex items-center justify-center py-4">
<Loader className="w-5 h-5 animate-spin text-blue-500" />
<span className="ml-2 text-sm text-gray-600 dark:text-gray-400">...</span>
{/* Modrinth 整合包 ID 输入 */}
{cloudBuildType === 'modrinth' ? (
<>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ID URL
</label>
<input
type="text"
value={modrinthPackId}
onChange={(e) => setModrinthPackId(e.target.value)}
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="例如: AANobbMI 或 https://modrinth.com/modpack/xxx"
disabled={buildingCloud}
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
ID Modrinth URL
</p>
</div>
) : (
<select
value={selectedCloudCore?.name || ''}
onChange={(e) => {
const core = cloudBuildCores.find(c => c.name === e.target.value)
setSelectedCloudCore(core || null)
setSelectedCloudVersion('')
}}
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"
disabled={buildingCloud}
>
<option value=""></option>
{cloudBuildCores.map((core) => (
<option key={core.name} value={core.name}>
{core.displayName} - {core.description}
</option>
))}
</select>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
</label>
<input
type="text"
value={modrinthVersion}
onChange={(e) => setModrinthVersion(e.target.value)}
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="例如: 5.2.5 或 latest"
disabled={buildingCloud}
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
</p>
</div>
</>
) : (
<>
{/* 核心选择 */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
</label>
{cloudBuildCoresLoading ? (
<div className="flex items-center justify-center py-4">
<Loader className="w-5 h-5 animate-spin text-blue-500" />
<span className="ml-2 text-sm text-gray-600 dark:text-gray-400">...</span>
</div>
) : (
<select
value={selectedCloudCore?.name || ''}
onChange={(e) => {
const core = cloudBuildCores.find(c => c.name === e.target.value)
setSelectedCloudCore(core || null)
setSelectedCloudVersion('')
}}
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"
disabled={buildingCloud}
>
<option value=""></option>
{cloudBuildCores.map((core) => (
<option key={core.name} value={core.name}>
{core.displayName} - {core.description}
</option>
))}
</select>
)}
</div>
</>
)}
{/* 版本选择 */}
{selectedCloudCore && (
{/* 版本选择 - 仅在 msl_Official 模式下显示 */}
{cloudBuildType === 'msl_Official' && selectedCloudCore && (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Minecraft版本
@@ -3123,8 +3315,8 @@ const GameDeploymentPage: React.FC = () => {
</div>
)}
{/* 下载统计 */}
{cloudBuildStats && selectedCloudCore && (
{/* 下载统计 - 仅在 msl_Official 模式下显示 */}
{cloudBuildType === 'msl_Official' && cloudBuildStats && selectedCloudCore && (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
<div className="text-xs text-gray-600 dark:text-gray-400">
{(() => {
@@ -3150,6 +3342,102 @@ const GameDeploymentPage: React.FC = () => {
</div>
</div>
)}
{/* Modrinth 缓存列表 - 仅在 modrinth 模式下显示 */}
{cloudBuildType === 'modrinth' && (
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
</label>
<button
onClick={fetchModrinthCache}
disabled={modrinthCacheLoading}
className="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 flex items-center space-x-1"
>
<RefreshCw className={`w-3 h-3 ${modrinthCacheLoading ? 'animate-spin' : ''}`} />
<span></span>
</button>
</div>
{modrinthCacheLoading ? (
<div className="flex items-center justify-center py-4">
<Loader className="w-5 h-5 animate-spin text-blue-500" />
<span className="ml-2 text-sm text-gray-600 dark:text-gray-400">...</span>
</div>
) : modrinthCacheList.length > 0 ? (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3 max-h-48 overflow-y-auto">
<div className="space-y-2">
{modrinthCacheList.map((cache, index) => (
<button
key={index}
onClick={() => {
setModrinthPackId(cache.packUrl)
setModrinthVersion(cache.version)
// 自动生成包含整合包名称的路径
const basePath = cloudBuildPath || defaultGamePath || ''
if (basePath) {
// 清理整合包 ID移除特殊字符和 URL 前缀
let cleanPackId = cache.packUrl
// 如果是 URL提取最后的 ID 部分
if (cleanPackId.includes('/')) {
const parts = cleanPackId.split('/')
cleanPackId = parts[parts.length - 1]
}
// 移除特殊字符
cleanPackId = cleanPackId.replace(/[<>:"|?*]/g, '').trim()
const cleanVersion = cache.version.replace(/[<>:"|?*]/g, '').trim()
// 组合文件夹名称
const folderName = `modrinth-${cleanPackId}-${cleanVersion}`
// 根据平台使用正确的路径分隔符
const isWindows = systemInfo?.platform === 'win32'
const separator = isWindows ? '\\' : '/'
// 确保基础路径以分隔符结尾
const normalizedBasePath = basePath.endsWith(separator) || basePath.endsWith('/') || basePath.endsWith('\\')
? basePath
: basePath + separator
setCloudBuildPath(normalizedBasePath + folderName)
}
addNotification({
type: 'info',
title: '已选择缓存',
message: `已自动填充:${cache.packUrl} v${cache.version}`
})
}}
disabled={buildingCloud}
className="w-full text-left text-xs text-gray-600 dark:text-gray-400 p-2 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="flex justify-between items-start mb-1">
<span className="font-medium text-gray-800 dark:text-gray-200">{cache.packUrl}</span>
<span className="text-green-600 dark:text-green-400 font-semibold">{cache.version}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-500 dark:text-gray-400">: {cache.downloadCount}</span>
<span className="text-gray-500 dark:text-gray-400">{(cache.fileSize / 1024 / 1024).toFixed(2)} MB</span>
</div>
<div className="mt-1 text-blue-600 dark:text-blue-400 text-[10px] flex items-center space-x-1">
<Download className="w-3 h-3" />
<span></span>
</div>
</button>
))}
</div>
</div>
) : (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3 text-center">
<p className="text-xs text-gray-500 dark:text-gray-400"></p>
</div>
)}
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
💡 缓存时间: 48小时 |
</p>
</div>
)}
</div>
</div>
@@ -3179,10 +3467,9 @@ const GameDeploymentPage: React.FC = () => {
<button
onClick={handleCloudBuild}
disabled={
!selectedCloudCore ||
!selectedCloudVersion ||
!cloudBuildPath.trim() ||
buildingCloud
cloudBuildType === 'modrinth'
? (!modrinthPackId.trim() || !modrinthVersion.trim() || !cloudBuildPath.trim() || buildingCloud)
: (!selectedCloudCore || !selectedCloudVersion || !cloudBuildPath.trim() || buildingCloud)
}
className="w-full bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white py-3 px-4 rounded-lg transition-colors flex items-center justify-center space-x-2"
>

View File

@@ -930,8 +930,8 @@ class ApiClient {
}
async downloadAndExtractCloudBuild(data: {
downloadUrl?: string
fileName?: string
taskId?: string
coreName: string
version: string
targetPath: string
@@ -942,6 +942,10 @@ class ApiClient {
async getCloudBuildStats() {
return this.get('/stats')
}
async getModrinthCache() {
return this.get('/cache/modrinth')
}
}
// 创建单例实例

View File

@@ -11,6 +11,7 @@ const router = Router()
// MSL云构建服务器地址
const MSL_BUILD_SERVER = 'https://download.mc.xiaozhuhouses.asia:4433'
// const MSL_BUILD_SERVER = 'http://127.0.0.1:3000'
// 获取核心列表
router.get('/:type/cores', authenticateToken, async (req, res) => {
@@ -92,7 +93,7 @@ router.get('/build/:taskId', authenticateToken, async (req, res) => {
// 下载并解压到目标目录
router.post('/download', authenticateToken, async (req, res) => {
try {
const { fileName, taskId, coreName, version, targetPath } = req.body
const { downloadUrl, fileName, coreName, version, targetPath } = req.body
if (!targetPath) {
return res.status(400).json({
@@ -101,22 +102,34 @@ router.post('/download', authenticateToken, async (req, res) => {
})
}
// 1. 生成下载链接
const linkResponse = await axios.post(`${MSL_BUILD_SERVER}/api/download`, {
fileName,
taskId,
coreName,
version
}, {
timeout: 30000
})
let finalDownloadUrl = downloadUrl
if (!linkResponse.data.success || !linkResponse.data.data.downloadUrl) {
throw new Error('生成下载链接失败')
// 如果提供了 fileName 而不是 downloadUrl需要先生成下载链接
if (!downloadUrl && fileName) {
const linkResponse = await axios.post(`${MSL_BUILD_SERVER}/api/download`, {
fileName,
coreName,
version
}, {
timeout: 30000
})
if (!linkResponse.data.success || !linkResponse.data.data.downloadUrl) {
throw new Error('生成下载链接失败')
}
finalDownloadUrl = linkResponse.data.data.downloadUrl
}
const downloadUrl = linkResponse.data.data.downloadUrl
const downloadFileName = linkResponse.data.data.fileName || `${coreName}-${version}.zip`
if (!finalDownloadUrl) {
return res.status(400).json({
success: false,
message: '缺少必要参数downloadUrl 或 fileName'
})
}
// 1. 使用 downloadUrl
const downloadFileName = fileName || `${coreName}-${version}.zip`
// 2. 确保目标目录存在
await fs.ensureDir(targetPath)
@@ -126,11 +139,9 @@ router.post('/download', authenticateToken, async (req, res) => {
await fs.ensureDir(tempDir)
const tempFilePath = path.join(tempDir, downloadFileName)
console.log(`正在下载文件到: ${tempFilePath}`)
const downloadResponse = await axios({
method: 'get',
url: `${MSL_BUILD_SERVER}${downloadUrl}`,
url: `${MSL_BUILD_SERVER}${finalDownloadUrl}`,
responseType: 'stream',
timeout: 300000 // 5分钟
})
@@ -141,14 +152,10 @@ router.post('/download', authenticateToken, async (req, res) => {
createWriteStream(tempFilePath)
)
console.log(`文件下载完成,开始解压到: ${targetPath}`)
// 4. 解压文件
const zip = new AdmZip(tempFilePath)
zip.extractAllTo(targetPath, true)
console.log('文件解压完成')
// 5. 删除临时文件
await fs.remove(tempFilePath)
@@ -255,5 +262,23 @@ router.get('/stats', authenticateToken, async (req, res) => {
}
})
// 获取 Modrinth 缓存列表
router.get('/cache/modrinth', authenticateToken, async (req, res) => {
try {
const response = await axios.get(`${MSL_BUILD_SERVER}/api/cache/modrinth`, {
timeout: 30000
})
res.json(response.data)
} catch (error: any) {
console.error('获取 Modrinth 缓存列表失败:', error.message)
res.status(error.response?.status || 500).json({
success: false,
message: error.response?.data?.message || '获取 Modrinth 缓存列表失败',
error: error.message
})
}
})
export default router

108
开发.md
View File

@@ -0,0 +1,108 @@
# API 对接使用指南
## 🚀 快速开始
### 1⃣ **创建构建任务**
```bash
POST http://localhost:3000/api/build
```
**请求头:**
```json
{
"Content-Type": "application/json",
"X-API-Key": "dev-api-key-change-in-production"
}
```
**请求体Modrinth 整合包):**
```json
{
"coreName": "AANobbMI",
"version": "5.2.5",
"type": "modrinth"
}
```
或者使用完整 URL
```json
{
"coreName": "https://modrinth.com/modpack/fabulously-optimized",
"version": "latest",
"type": "modrinth"
}
```
**响应示例:**
```json
{
"success": true,
"message": "构建任务已创建",
"data": {
"taskId": "e2a4d9f2-2637-4e02-af21-980a44910e44",
"status": "PROCESSING",
"message": "任务已创建,正在处理"
}
}
```
### 2⃣ **轮询查询任务状态**
```bash
GET http://localhost:3000/api/build/{taskId}
```
**请求头:**
```json
{
"X-API-Key": "dev-api-key-change-in-production"
}
```
**建议:** 每 3-5 秒查询一次
**响应示例(处理中):**
```json
{
"success": true,
"data": {
"taskId": "e2a4d9f2-2637-4e02-af21-980a44910e44",
"status": "PROCESSING",
"progress": 50,
"message": "正在下载文件...",
"activeBuilds": 1,
"queueSize": 0
}
}
```
**响应示例(完成):**
```json
{
"success": true,
"data": {
"taskId": "e2a4d9f2-2637-4e02-af21-980a44910e44",
"status": "COMPLETED",
"progress": 100,
"downloadUrl": "/api/download/47d0y7u45dq",
"expireTime": "2025-10-27 10:30:55",
"message": "构建完成"
}
}
```
### 3⃣ **下载文件**
**直接使用返回的 downloadUrl**
```bash
GET http://localhost:3000/api/download/47d0y7u45dq
```
**重要:**
-**自动缓存**:文件已自动下载并缓存到本地
-**直接下载**:无需额外 API 调用
- ⏱️ **有效期**:下载链接有效期 24 小时
- 📦 **缓存时间**Modrinth 整合包缓存 48 小时
---