2026-01-28 06:16:04 +00:00

380 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Kneron Toolchain Web GUI Task Scheduler
* Express.js服務器代理所有API調用
*/
const express = require('express');
const cors = require('cors');
const multer = require('multer');
const axios = require('axios');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
const compression = require('compression');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 4000;
// 配置API服務
const API_SERVICES = {
onnx: {
url: process.env.ONNX_SERVICE_URL || 'http://localhost:5001',
apiKey: process.env.ONNX_API_KEY || 'onnx-secret-key'
},
bie: {
url: process.env.BIE_SERVICE_URL || 'http://localhost:5002',
apiKey: process.env.BIE_API_KEY || 'bie-secret-key'
},
nef: {
url: process.env.NEF_SERVICE_URL || 'http://localhost:5003',
apiKey: process.env.NEF_API_KEY || 'nef-secret-key'
}
};
// 中間件配置
app.use(helmet());
app.use(compression());
app.use(morgan('combined'));
// CORS配置 - 只允許前端訪問
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true
}));
// 請求限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分鐘
max: 100, // 限制每個IP每15分鐘最多100個請求
message: 'Too many requests from this IP, please try again later.'
});
app.use('/api', limiter);
// 解析JSON和URL編碼
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
// 文件上傳配置
const storage = multer.memoryStorage();
const upload = multer({
storage: storage,
limits: {
fileSize: 500 * 1024 * 1024 // 500MB
}
});
// 健康檢查
app.get('/health', (req, res) => {
res.json({
service: 'task-scheduler',
status: 'healthy',
timestamp: new Date().toISOString(),
services: Object.keys(API_SERVICES)
});
});
// API代理函數
async function proxyRequest(service, endpoint, method = 'GET', data = null, files = null) {
try {
const serviceConfig = API_SERVICES[service];
if (!serviceConfig) {
throw new Error(`Service ${service} not found`);
}
const url = `${serviceConfig.url}${endpoint}`;
const headers = {
'X-API-Key': serviceConfig.apiKey,
'Content-Type': 'application/json'
};
let response;
if (method === 'GET') {
response = await axios.get(url, { headers });
} else if (method === 'POST') {
if (files) {
// 處理文件上傳
const formData = new FormData();
Object.keys(files).forEach(key => {
formData.append(key, files[key].buffer, files[key].originalname);
});
response = await axios.post(url, formData, {
headers: {
'X-API-Key': serviceConfig.apiKey,
'Content-Type': 'multipart/form-data'
}
});
} else {
response = await axios.post(url, data, { headers });
}
} else if (method === 'DELETE') {
response = await axios.delete(url, { headers });
}
return response.data;
} catch (error) {
console.error(`Proxy request failed for ${service}:`, error.message);
throw error;
}
}
// ONNX API代理
app.post('/api/onnx/upload', upload.single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file provided' });
}
const result = await proxyRequest('onnx', '/api/onnx/upload', 'POST', null, { file: req.file });
res.json(result);
} catch (error) {
console.error('ONNX upload error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.post('/api/onnx/process', async (req, res) => {
try {
const result = await proxyRequest('onnx', '/api/onnx/process', 'POST', req.body);
res.json(result);
} catch (error) {
console.error('ONNX process error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/onnx/tasks/:taskId/status', async (req, res) => {
try {
const result = await proxyRequest('onnx', `/api/onnx/tasks/${req.params.taskId}/status`);
res.json(result);
} catch (error) {
console.error('ONNX status error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/onnx/tasks/:taskId/result', async (req, res) => {
try {
const result = await proxyRequest('onnx', `/api/onnx/tasks/${req.params.taskId}/result`);
// 如果是文件下載,設置適當的響應頭
if (result.headers && result.headers['content-type']) {
res.set('Content-Type', result.headers['content-type']);
res.set('Content-Disposition', result.headers['content-disposition']);
res.send(result.data);
} else {
res.json(result);
}
} catch (error) {
console.error('ONNX result error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/onnx/tasks', async (req, res) => {
try {
const result = await proxyRequest('onnx', '/api/onnx/tasks');
res.json(result);
} catch (error) {
console.error('ONNX tasks list error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.delete('/api/onnx/tasks/:taskId', async (req, res) => {
try {
const result = await proxyRequest('onnx', `/api/onnx/tasks/${req.params.taskId}`, 'DELETE');
res.json(result);
} catch (error) {
console.error('ONNX cancel error:', error.message);
res.status(500).json({ error: error.message });
}
});
// BIE API代理
app.post('/api/bie/process', async (req, res) => {
try {
const result = await proxyRequest('bie', '/api/bie/process', 'POST', req.body);
res.json(result);
} catch (error) {
console.error('BIE process error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/bie/tasks/:taskId/status', async (req, res) => {
try {
const result = await proxyRequest('bie', `/api/bie/tasks/${req.params.taskId}/status`);
res.json(result);
} catch (error) {
console.error('BIE status error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/bie/tasks/:taskId/result', async (req, res) => {
try {
const result = await proxyRequest('bie', `/api/bie/tasks/${req.params.taskId}/result`);
if (result.headers && result.headers['content-type']) {
res.set('Content-Type', result.headers['content-type']);
res.set('Content-Disposition', result.headers['content-disposition']);
res.send(result.data);
} else {
res.json(result);
}
} catch (error) {
console.error('BIE result error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/bie/tasks', async (req, res) => {
try {
const result = await proxyRequest('bie', '/api/bie/tasks');
res.json(result);
} catch (error) {
console.error('BIE tasks list error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.delete('/api/bie/tasks/:taskId', async (req, res) => {
try {
const result = await proxyRequest('bie', `/api/bie/tasks/${req.params.taskId}`, 'DELETE');
res.json(result);
} catch (error) {
console.error('BIE cancel error:', error.message);
res.status(500).json({ error: error.message });
}
});
// NEF API代理
app.post('/api/nef/process', async (req, res) => {
try {
const result = await proxyRequest('nef', '/api/nef/process', 'POST', req.body);
res.json(result);
} catch (error) {
console.error('NEF process error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/nef/tasks/:taskId/status', async (req, res) => {
try {
const result = await proxyRequest('nef', `/api/nef/tasks/${req.params.taskId}/status`);
res.json(result);
} catch (error) {
console.error('NEF status error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/nef/tasks/:taskId/result', async (req, res) => {
try {
const result = await proxyRequest('nef', `/api/nef/tasks/${req.params.taskId}/result`);
if (result.headers && result.headers['content-type']) {
res.set('Content-Type', result.headers['content-type']);
res.set('Content-Disposition', result.headers['content-disposition']);
res.send(result.data);
} else {
res.json(result);
}
} catch (error) {
console.error('NEF result error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.get('/api/nef/tasks', async (req, res) => {
try {
const result = await proxyRequest('nef', '/api/nef/tasks');
res.json(result);
} catch (error) {
console.error('NEF tasks list error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.delete('/api/nef/tasks/:taskId', async (req, res) => {
try {
const result = await proxyRequest('nef', `/api/nef/tasks/${req.params.taskId}`, 'DELETE');
res.json(result);
} catch (error) {
console.error('NEF cancel error:', error.message);
res.status(500).json({ error: error.message });
}
});
// 完整工作流程
app.post('/api/workflow/complete', async (req, res) => {
try {
const { onnx_file_id, model_id, version, platform, data_dir } = req.body;
// 1. 處理ONNX優化
const onnxResult = await proxyRequest('onnx', '/api/onnx/process', 'POST', {
file_id: onnx_file_id,
model_id,
version,
platform
});
// 2. 處理BIE分析
const bieResult = await proxyRequest('bie', '/api/bie/process', 'POST', {
onnx_file_id,
model_id,
version,
platform,
data_dir
});
// 3. 處理NEF編譯
const nefResult = await proxyRequest('nef', '/api/nef/process', 'POST', {
bie_file_id: bieResult.task_id, // 假設BIE結果包含文件ID
model_id,
version,
platform
});
res.json({
success: true,
workflow_id: `workflow_${Date.now()}`,
steps: {
onnx: onnxResult,
bie: bieResult,
nef: nefResult
},
message: 'Complete workflow submitted successfully'
});
} catch (error) {
console.error('Workflow error:', error.message);
res.status(500).json({ error: error.message });
}
});
// 錯誤處理中間件
app.use((error, req, res, next) => {
console.error('Server error:', error);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
});
});
// 404處理
app.use('*', (req, res) => {
res.status(404).json({ error: 'Endpoint not found' });
});
// 啟動服務器
app.listen(PORT, () => {
console.log(`🚀 Task scheduler running on port ${PORT}`);
console.log(`📡 Proxying to services:`);
Object.keys(API_SERVICES).forEach(service => {
console.log(` ${service}: ${API_SERVICES[service].url}`);
});
});
module.exports = app;