Sfoglia il codice sorgente

feat: 添加文件上传重试机制

HeWang 8 mesi fa
parent
commit
a9d691a8a0

+ 66 - 6
electron/main.js

@@ -4,11 +4,13 @@ import path from 'path';
 import {fileURLToPath} from 'url';
 import fs from 'fs';
 import axios from 'axios';
+import ProcessManager from './processManager.js'
 
-
+const processManager = new ProcessManager(); // 创建进程管理器实例
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
 
+
 function createWindow() {
     const win = new BrowserWindow({
         width: 800,
@@ -202,10 +204,33 @@ async function uploadFile(filePath, uploadUrl) {
         return response.data
     } catch (err) {
         console.log("Error uploading file:", err);
-        return { status: false, code: '', message: '上传stl文件失败', details: '' };
+        return { status: false, code: '', message: '上传件失败', details: '' };
     }
 }
 
+async function uploadFileWithRetry(filePath, uploadUrl, maxRetries = 3) {
+    let attempt = 1;
+
+    while (attempt <= maxRetries) {
+        console.log("Uploading file - attempt:", attempt);
+        try {
+            const response = await uploadFile(filePath, uploadUrl);
+            console.log("response", response);
+            if (response.status) { // 上传成功
+                return response;
+            } else { // 上传失败
+                console.log("Fail to upload file - attempt:", attempt);
+                attempt++
+            }
+        } catch (error) {
+            console.log("Error uploading file - attempt:", attempt);
+            console.log("Error:", error);
+            attempt++
+        }
+    }
+    return { status: false, code: '', message: '上传文件失败', details: '' };
+}
+
 // 导入算法镜像
 ipcMain.on('docker-import', (event, filePath, tag) => {
     const command = 'bash /home/cicv/work/pji_desktop/docker_import/run_docker_import.sh ' + filePath + ' pji_nav ' + tag
@@ -289,10 +314,26 @@ ipcMain.on('start-container', (event, {zip_file_path, image_name, container_name
 
 // 执行仿真
 ipcMain.on('run-simulation', (event, {random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point}) => {
+
     const command = 'bash /home/cicv/work/pji_desktop/simulation/run_simulation.sh ' + random_flag + ' ' + count + ' ' + obstacle_flag + ' ' +
         default_start_flag + ' ' + default_end_flag + ' ' + start_point + ' ' + end_point;
     console.log('command:', command);
-    exec(command, (error, stdout, stderr) => {
+    // exec(command, (error, stdout, stderr) => {
+    //     if (error) {
+    //         console.error(`exec error: ${error}`);
+    //         event.reply('run-simulation-response', { success: false, message: error.message });
+    //     } else {
+    //         console.log(`stdout: ${stdout}`);
+    //         console.error(`stderr: ${stderr}`);
+    //         if (stdout.toString().includes('Evaluation finished')) { // 判断结束标志
+    //             event.reply('run-simulation-response', { success: true, message: 'Run simulation successfully' });
+    //         } else {
+    //             event.reply('run-simulation-response', { success: false, message: 'Error running simulation' });
+    //         }
+    //     }
+    // });
+
+    processManager.startProcess('run-simulation', command, (error, stdout, stderr) => {
         if (error) {
             console.error(`exec error: ${error}`);
             event.reply('run-simulation-response', { success: false, message: error.message });
@@ -306,6 +347,12 @@ ipcMain.on('run-simulation', (event, {random_flag, count, obstacle_flag, default
             }
         }
     });
+
+    // 设置超时机制(
+    setTimeout(() => {
+        processManager.killProcess('run-simulation');
+        event.reply('run-simulation-response', { success: false, message: 'Script running timeout' });
+    }, 3*60*1000); // 3 分钟
 });
 
 // 读取并上传算法评价结果
@@ -344,12 +391,25 @@ ipcMain.handle('process-evaluation-files', async (event, {N, equipmentNo, sceneN
             const parsedData = JSON.parse(jsonData);
             console.log("parsedData", parsedData);
             // 上传pdf文件
-            const uploadPdfResult = await uploadFile(pdfFilePath, uploadPdfUrl);
+            // 查找pdf文件是否存在
+            let pdfFileExists = fs.existsSync(pdfFilePath)
+            // pdf文件不存在
+            if (!pdfFileExists) break
+            // pdf文件存在
+            const uploadPdfResult = await uploadFileWithRetry(pdfFilePath, uploadPdfUrl);
             console.log("uploadPdfResult", uploadPdfResult);
-            if (!uploadPdfResult.status) { break}
+            if (!uploadPdfResult.status) {
+                break
+            }
+
             // 上传bag文件
+            // 查找bag文件是否存在
+            let bagFileExists = fs.existsSync(bagFilePath)
+            // bag文件不存在
+            if (!bagFileExists) break
+            // bag文件存在
             const uploadBagResult = await uploadFile(bagFilePath, uploadBagUrl);
-            console.log("uploadBagUrl", uploadBagUrl);
+            console.log("uploadBagResult", uploadBagResult);
             if (!uploadBagResult.status) { break}
             // 整合结果
             result.push({

+ 1 - 11
electron/preload.js

@@ -5,8 +5,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
 
     openFileManager: async () => {
         try {
-            const result = await ipcRenderer.invoke('dialog:open');
-            return result;
+            return await ipcRenderer.invoke('dialog:open');
         } catch (error) {
             console.error('打开文件管理器时出错:', error);
             throw error;
@@ -34,15 +33,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
     runSimulation: async (random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point) => {
         // 异步
         ipcRenderer.send('run-simulation', {random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point});
-
-        // 同步
-        // try {
-        //     const result = ipcRenderer.invoke('run-simulation', {obstacle_flag, default_start_flag, default_end_flag, start_point, end_point});
-        //     return result;
-        // } catch (error) {
-        //     console.error('执行仿真测试出错:', error);
-        //     throw error;
-        // }
     },
     onRunSimulationResponse: (callback) => ipcRenderer.once('run-simulation-response', callback),
     generateWorld: (rosbag_path) => {

+ 45 - 0
electron/processManager.js

@@ -0,0 +1,45 @@
+import {exec, spawn} from 'child_process';
+
+class ProcessManager {
+  constructor() {
+    this.processes = new Map(); // 存储所有子进程的引用
+  }
+
+  // 启动新的子进程
+  startProcess(key, command, callback) {
+    // 如果相同 key 的进程已经在运行,先终止它
+    if (this.processes.has(key)) {
+      this.killProcess(key);
+    }
+
+    // 启动新进程
+    const child = exec(command, (error, stdout, stderr) => {
+      if (callback) callback(error, stdout, stderr); // 执行回调
+
+      // 执行完成后,清理存储的进程
+      this.processes.delete(key);
+    });
+
+    // 存储进程引用
+    this.processes.set(key, child);
+  }
+
+  // 终止指定 key 的子进程
+  killProcess(key) {
+    const child = this.processes.get(key);
+    if (child) {
+      child.kill();
+      this.processes.delete(key);
+    }
+  }
+
+  // 终止所有子进程
+  killAll() {
+    for (const [key, child] of this.processes.entries()) {
+      child.kill();
+      this.processes.delete(key);
+    }
+  }
+}
+
+export default ProcessManager;

+ 9 - 0
package-lock.json

@@ -10,6 +10,7 @@
       "dependencies": {
         "axios": "^1.7.2",
         "element-plus": "^2.7.5",
+        "moment": "^2.30.1",
         "path-browserify": "^1.0.1",
         "pinia": "^2.1.7",
         "vue": "^3.4.21",
@@ -4227,6 +4228,14 @@
         "ufo": "^1.5.3"
       }
     },
+    "node_modules/moment": {
+      "version": "2.30.1",
+      "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
+      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
   "dependencies": {
     "axios": "^1.7.2",
     "element-plus": "^2.7.5",
+    "moment": "^2.30.1",
     "path-browserify": "^1.0.1",
     "pinia": "^2.1.7",
     "vue": "^3.4.21",

+ 0 - 15
src/utils/axios.js

@@ -1,15 +0,0 @@
-import axios from 'axios';
-
-// 创建一个默认实例
-const localInstance = axios.create({
-    baseURL: 'https://api.example.com', // 默认的API地址
-    timeout: 60 * 60 * 1000, // 请求超时时间 (ms)
-});
-
-// 创建另一个实例用于不同的API地址或端口
-const pjiInstance = axios.create({
-    baseURL: 'https://api.otherdomain.com:8080', // 另一个API地址和端口
-    timeout: 10000,
-});
-
-export { localInstance, pjiInstance };

+ 15 - 8
src/views/AboutView.vue

@@ -34,7 +34,7 @@
                 @selection-change="handleSelectionChange"
                 fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
         <el-table-column type="selection" width="55"/>
-        <el-table-column prop="test_time" label="测试时间"/>
+        <el-table-column prop="test_time" :formatter="formatDate" label="测试时间"/>
         <el-table-column prop="round" label="轮次"/>
         <el-table-column prop="test_bag_path" :formatter="(row) => formatPath(row, 'test_bag_path')"  label="测试bag"/>
         <el-table-column prop="test_duration" label="测试时长"/>
@@ -153,8 +153,7 @@
       </el-dialog>
 
 <!--      <el-button style="margin-left: 10px;" type="primary" @click="goToMain">更换设置并执行</el-button>-->
-<!--      :disabled="evalResult.length === 0"-->
-      <el-button style="margin-left: 10px;" type="primary" @click="showEvalResult" >算法评价结果</el-button>
+      <el-button style="margin-left: 10px;" type="primary" @click="showEvalResult" :disabled="evalResult.length === 0">算法评价结果</el-button>
     </el-form-item>
 <!--    <el-form-item label="            ">-->
 <!--      <el-button type="primary" @click="goToAlgorithmEval">算法评价结果</el-button>-->
@@ -170,12 +169,11 @@
 import {useRoute, useRouter} from 'vue-router'; // 导入 Vue Router 的 useRouter 钩子
 import {nextTick, reactive} from 'vue'
 import {ref} from 'vue'
-import {ElMessageBox} from 'element-plus'
 import {ElTable, ElLoading, ElMessage} from "element-plus";
 import {getCurrentInstance} from "vue";
 import axios from "axios";
 import path from 'path-browserify';
-import * as net from "node:net";
+import moment from "moment";
 
 const startDialogVisible = ref(false)
 const endDialogVisible = ref(false)
@@ -209,7 +207,10 @@ console.log("deviceName", deviceName)
 console.log("algoImageName", algoImageName)
 console.log("worldPath", worldPath)
 
-const testTime = ref("1726033307148")
+// const testTime = ref("1726113464184")
+const testTime = ref("")
+
+const resetEvalTableData = ref([])
 
 const handleClose = (done: () => void) => {
   done()
@@ -225,7 +226,7 @@ const form = reactive({
   type: [],
   resource: true,
   desc: '',
-  isRandom: true,
+  isRandom: false,
   origin: true,
   destination: true,
   randomCount: ref(1),
@@ -263,7 +264,7 @@ const destinationChange = (value: string) => {
 
 const handleSelectionChange = (rows: []) => {
   multipleSelection.value = rows
-  console.log("multipleSelection", multipleSelection.value)
+  console.log("multipleSelection", +multipleSelection.value)
 }
 
 const downloadFile = async (typeName) => {
@@ -300,6 +301,10 @@ const formatPath = (row, name) => {
   return path.basename(row[name])
 }
 
+const formatDate =  (row) => {
+  return moment(+row.test_time).format("YYYY-MM-DD HH:mm:ss")
+}
+
 // 弹窗显示算法评价记录
 const showEvalResult = async (value: string) => {
   console.log("testTime", testTime.value)
@@ -314,6 +319,7 @@ const showEvalResult = async (value: string) => {
     }
     // 解析结果
     evalTableData.value = JSON.parse(response.data.details)
+    resetEvalTableData.value = evalTableData.value // for reset purpose
     await nextTick() // 避免vue warning: onMounted is called when there is no active component instance...
     // 开启弹窗
     evalTableVisible.value = true
@@ -352,6 +358,7 @@ const runSimulation = () => {
       const response = await window.electronAPI.processEvaluationFiles(N, deviceNo, sceneId)
       if (!response.success) { // 算法评价结果收集失败
         console.log("Error collecting evaluation results")
+        proxy.$message.error("仿真测试发生错误!");
         return
       }
       console.log("response.data", response.data)

+ 0 - 4
src/views/ReportView.vue

@@ -413,8 +413,6 @@ const algorithmImport = async () => {
   }
 };
 
-
-
 // 数据容量单位从B转成MB
 const dataSizeFormatter = (row, column, cellValue, index) => {
   // 假设 1MB = 1024 * 1024 字节
@@ -441,7 +439,6 @@ const isPropertySame= (array, propertyName) => {
   return true; // 所有值都是相同的
 }
 
-
 // 地图更新
 const updateMap = async () => {
   // 检查选择的记录数量
@@ -509,7 +506,6 @@ const handleSelectionChange = (rows: []) => {
   }
 }
 
-
 // world文件生成
 const generateWorld = async (row) => {
   const id = row.id