// server/index.js import express from 'express' import cors from 'cors' import grpc from '@grpc/grpc-js' import protoLoader from '@grpc/proto-loader' import { resolve } from 'path'; import { fileURLToPath } from 'url'; import 'dotenv/config'; const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const app = express() app.use(express.static(path.join(__dirname, '../dist'))); app.use(cors()) app.use(express.json()) // ========== 加载 Proto ========== const PROTO_ROOT = path.resolve(__dirname, './proto') // 指向 server/proto/ const PROTO_ENTRY = path.join(PROTO_ROOT, 'cmvr/api/agv_service.proto') const packageDefinition = protoLoader.loadSync( PROTO_ENTRY, { includeDirs: [PROTO_ROOT , path.join(PROTO_ROOT, 'cmvr/api')], // 关键:让加载器从 PROTO_ROOT 查找 import keepCase: true, longs: String, enums: String, defaults: true, oneofs: true } ) const cmvrProto = grpc.loadPackageDefinition(packageDefinition).cmvr.api const createGrpcClient = (address) => { const grpcClient = new cmvrProto.AgvService( address, grpc.credentials.createInsecure() ) return grpcClient } // 获取机器人当前地图名称 app.post('/api/agv/getCurrentMapName', async (req, res) => { const request = { header: { device_id: req.body.device_id || 'agv_src1100' } }; const grpcClient = createGrpcClient(req.body.ip) const resp = await new Promise((resolve, reject) => { grpcClient.getMapStatus(request, (err, res) => { if (err) { resolve({ code: 500, data: `grpc服务端错误` }); } else { resolve({ code: 200, data: res.status }); } }); }) if (resp.code === 200) { res.json(resp) } else { res.status(500).json({ error: 'grpc服务端错误' }) } }) // 获取机器人当前地图名称 app.post('/api/agv/getCurrentMap', async (req, res) => { const grpcClient = createGrpcClient(req.body.ip) const request = { header: { device_id: req.body.device_id || 'agv_src1100' }, data: { map_name: req.body.mapName } }; const resp = await new Promise((resolve, reject) => { grpcClient.RobotConfigDownloadMap(request, (err, res) => { if (err) { resolve({ code: 500, data: `grpc服务端错误` }); } else { resolve({ code: 200, data: res.status }); } }); }) if (resp.code === 200) { res.json(resp) } else { res.status(500).json({ error: 'grpc服务端错误' }) } }) app.post('/api/agv/getRobotState', async (req, res) => { const request = { header: { device_id: req.body.device_id || 'agv_src1100' } }; const grpcClient = createGrpcClient(req.body.ip) const response = await new Promise((resolve, reject) => { grpcClient.GetRobotLocation(request, (err, res) => { if (err) { resolve({ code: 500, data: `grpc服务端错误` }); } else { resolve({ code: 200, data: res.status }); } }) }) const resp = await new Promise((resolve, reject) => { grpcClient.GetBatteryStatus(request, (err, res) => { if (err) { resolve({ code: 500, data: `grpc服务端错误` }); } else { resolve({ code: 200, data: res.status }); } }); }) if (response.code === 200 && resp.code ===200) { res.json({ code: 200, data: { x: response.data.x, y: response.data.y, angle: response.data.angle, confidence: response.data.confidence, batteryLevel: resp.data.battery_level, batteryTemp: resp.data.battery_temp, charging: resp.data.charging, voltage: resp.data.voltage } }) } else { res.status(500).json({ error: 'grpc服务端错误' }) } }) app.post('/api/agv/moveRobot', async (req, res) => { const grpcClient = createGrpcClient(req.body.ip) const request = { header: { device_id: req.body.device_id || 'agv_src1100' }, data: { vx: req.body.vx, vy: req.body.vy, w: req.body.w, duration: req.body.duration } }; const resp = await new Promise((resolve, reject) => { grpcClient.RobotMotionControl(request, (err, res) => { if (err) { resolve({ code: 500, data: `grpc服务端错误` }); } else { resolve({ code: 200, data: res.status }); } }); }) if (resp.code === 200) { res.json({ code: 200, data: resp }) } else { res.status(500).json({ error: 'grpc服务端错误' }) } }) app.post('/api/agv/stopRobot', async (req, res) => { const grpcClient = createGrpcClient(req.body.ip) const request = { header: { device_id: req.body.device_id || 'agv_src1100' } }; const resp = await new Promise((resolve, reject) => { grpcClient.RobotControlStop(request, (err, res) => { if (err) { resolve({ code: 500, data: `grpc服务端错误` }); } else { resolve({ code: 200, data: res.status }); } }); }) if (resp.code === 200) { res.json({ code: 200, data: resp }) } else { res.status(500).json({ error: 'grpc服务端错误' }) } }) const isProd = process.env.NODE_ENV === 'production'; const PORT = process.env.VITE_SERVICE_PORT || 3000; // ============ 生产环境:托管 Vite 构建产物 ============ if (isProd) { const distPath = resolve(__dirname, '../dist'); // 托管静态文件 app.use(express.static(distPath)); // SPA 回退:所有非 API 请求返回 index.html app.get('*', (req, res) => { res.sendFile(resolve(distPath, 'index.html')); }); app.post('*', (req, res) => { res.sendFile(resolve(distPath, 'index.html')); }); } // ========== 启动 ========= app.listen(PORT, () => { console.log(`gRPC 桥接服务已启动: http://localhost:${PORT}`) })