380 lines
11 KiB
JavaScript
380 lines
11 KiB
JavaScript
/**
|
||
* 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;
|