Преглед на файлове

feat: 上传优化以及退出优化

zhangjie преди 10 месеца
родител
ревизия
a522c87025
променени са 11 файла, в които са добавени 142 реда и са изтрити 178 реда
  1. 4 1
      jsconfig.json
  2. 2 1
      package.json
  3. 23 29
      src/mixins/uploadTaskMixin.js
  4. 5 14
      src/modules/client/views/Scan.vue
  5. 4 1
      src/modules/client/views/ScanOther.vue
  6. 4 0
      src/modules/client/views/ScanPaper.vue
  7. 18 120
      src/plugins/imageUpload.js
  8. 37 0
      src/plugins/pQueue.js
  9. 26 11
      src/views/Home.vue
  10. 1 1
      vue.config.js
  11. 18 0
      yarn.lock

+ 4 - 1
jsconfig.json

@@ -15,5 +15,8 @@
       "dom.iterable",
       "scripthost"
     ]
+  },
+  "vueCompilerOptions": {
+    "target": 2.7
   }
-}
+}

+ 2 - 1
package.json

@@ -25,6 +25,7 @@
     "imagemagick": "^0.1.3",
     "js-md5": "^0.8.3",
     "log4js": "^6.3.0",
+    "p-queue": "^8.0.1",
     "sqlite3": "^5.0.0",
     "vue": "^2.6.11",
     "vue-ls": "^3.2.2",
@@ -60,4 +61,4 @@
       "git add"
     ]
   }
-}
+}

+ 23 - 29
src/mixins/uploadTaskMixin.js

@@ -1,19 +1,19 @@
 import db from "../plugins/db";
-import UploadTask from "../plugins/imageUpload";
-// import { getLocalDate } from "../plugins/utils";
+import { toUploadImg } from "../plugins/imageUpload";
+import usePQueue from "../plugins/pQueue";
 
 /**
  * 上传流程:
  * 1、先取数据库中所有未上传的记录,生成上传任务列表,开启当前上传周期。
- * 2、当前上传周期内新采集的图片追加进当前上传周期。
- * 3、当前上传周期内上传失败的图片进入下次上传周期。
- * 4、当前上传周期结束之后,定时开启下次上传周期。
+ * 2、当前上传周期内上传失败的图片进入下次上传周期。
+ * 3、当前上传周期结束之后,定时开启下次上传周期。
  */
 export default {
   data() {
     return {
       uploadTask: null,
       taskSetTs: [],
+      stopQueue: null,
     };
   },
   methods: {
@@ -22,9 +22,6 @@ export default {
         console.log(err);
       });
     },
-    async uploadSuccessCallback(curUploadTask) {
-      await db.updateUploadState(curUploadTask.id);
-    },
     async initUploadTask() {
       this.clearTaskSetTs();
       const unuploadList = await db.searchUploadList({
@@ -39,31 +36,28 @@ export default {
         );
         return;
       }
-      // 创建上传任务
-      this.uploadTask = new UploadTask({
-        taskList: unuploadList,
-        uploadSuccessCallback: (curUploadTask) => {
-          this.uploadSuccessCallback(curUploadTask);
-        },
-        uploadTaskOverCallback: () => {
-          if (!this.uploadTask) return;
-          this.taskSetTs.push(
-            setTimeout(() => {
-              this.initUploadTask();
-            }, 1 * 1000)
-          );
-        },
-        uploadErrorCallBack: (curUploadTask) => {
-          const content = `任务:${curUploadTask.taskId},采集Id:${curUploadTask.id},图片上传失败!`;
-          console.log(content);
-          return;
-        },
+
+      const uploadTasks = unuploadList.map((item) => {
+        return async () => {
+          await toUploadImg(item);
+          await db.updateUploadState(item.id);
+        };
       });
+
+      const { stopQueue, buildQueue } = usePQueue();
+      this.stopQueue = stopQueue;
+      await buildQueue(uploadTasks);
+
+      this.taskSetTs.push(
+        setTimeout(() => {
+          this.initUploadTask();
+        }, 1 * 1000)
+      );
     },
     stopUpload() {
       this.clearTaskSetTs();
-      this.uploadTask && this.uploadTask.stopUploadTask();
-      this.uploadTask = null;
+      this.stopQueue && this.stopQueue();
+      this.stopQueue = null;
     },
     clearTaskSetTs() {
       if (!this.taskSetTs.length) return;

+ 5 - 14
src/modules/client/views/Scan.vue

@@ -100,12 +100,6 @@
     <!-- OcrAreaSetDialog -->
     <ocr-area-set-dialog ref="OcrAreaSetDialog" @modified="getOcrArea">
     </ocr-area-set-dialog>
-    <!-- ScanTaskProcessDialog -->
-    <scan-task-process-dialog
-      ref="ScanTaskProcessDialog"
-      :task="scanInfo"
-      @on-close="search"
-    ></scan-task-process-dialog>
   </div>
 </template>
 
@@ -114,12 +108,11 @@ import db from "../../../plugins/db";
 import { taskInfos } from "../api";
 import SecSelect from "@/components/SecSelect.vue";
 import OcrAreaSetDialog from "../components/OcrAreaSetDialog.vue";
-import ScanTaskProcessDialog from "../components/ScanTaskProcessDialog.vue";
 import { setScanner } from "../../../plugins/scanner";
 
 export default {
   name: "scan",
-  components: { OcrAreaSetDialog, SecSelect, ScanTaskProcessDialog },
+  components: { OcrAreaSetDialog, SecSelect },
   data() {
     return {
       filter: {
@@ -129,7 +122,6 @@ export default {
       },
       searchFilter: {},
       task: {},
-      scanInfo: {},
       ocrArea: null,
       setDisabled: false,
     };
@@ -187,11 +179,10 @@ export default {
         this.$message.error("请先设置条形码识别区!");
         return;
       }
-      this.scanInfo = {
-        ...this.task,
-        isFormal,
-      };
-      this.$refs.ScanTaskProcessDialog.open();
+
+      const scanName = isFormal ? "ScanPaper" : "ScanOther";
+      this.$ls.set("task", this.task);
+      this.$router.push({ name: scanName });
     },
     toSetOrcArea() {
       this.$refs.OcrAreaSetDialog.open();

+ 4 - 1
src/modules/client/views/ScanOther.vue

@@ -161,6 +161,9 @@ export default {
       return this.GLOBAL.delayMode === 1;
     },
   },
+  created() {
+    this.$on("go-back", this.goBackHandle);
+  },
   beforeDestroy() {
     this.stopLoopScaningFile();
   },
@@ -184,7 +187,7 @@ export default {
         ).catch(() => {});
         if (res !== "confirm") return;
       }
-
+      this.$router.go(-1);
       logger.info(`99退出扫描`);
     },
     // scan

+ 4 - 0
src/modules/client/views/ScanPaper.vue

@@ -196,6 +196,9 @@ export default {
       return this.isNormalTab ? this.normalCount : this.errorCount;
     },
   },
+  created() {
+    this.$on("go-back", this.goBackHandle);
+  },
   beforeDestroy() {
     this.stopLoopScaningFile();
   },
@@ -225,6 +228,7 @@ export default {
         if (res !== "confirm") return;
       }
 
+      this.$router.go(-1);
       logger.info(`99退出扫描`);
     },
     switchTab(tab) {

+ 18 - 120
src/plugins/imageUpload.js

@@ -1,151 +1,49 @@
 const fs = window.nodeRequire("fs");
 const path = window.nodeRequire("path");
 import { uploadImage } from "../modules/client/api";
-import { Message } from "element-ui";
 import { MD5 } from "./md5";
 
 /**
  * 文件上传
  * @param {Object} options 上传信息
  */
-function toUploadImg(options) {
+export async function toUploadImg(options) {
   const formData = new FormData();
   // frontFile,frontMd5,versoFile,vers5,taskId
 
-  const { file: frontFile, md5: frontMd5 } = getFileAndMd5(
+  const { file: frontFile, md5: frontMd5 } = await getFileAndMd5(
     options.frontOriginImgPath
   );
   formData.append("frontMd5", frontMd5);
   formData.append("frontFile", frontFile);
-  const { file: versoFile, md5: versoMd5 } = getFileAndMd5(
+  const { file: versoFile, md5: versoMd5 } = await getFileAndMd5(
     options.versoOriginImgPath
   );
   formData.append("versoMd5", versoMd5);
   formData.append("versoFile", versoFile);
 
   formData.append("paperScanTaskId", options.taskId);
-  formData.append("studentCode", options.studentCode);
-
-  if (options.isFormal && options.ocrArea) {
-    const ocrArea = JSON.parse(options.ocrArea);
-    Object.keys(ocrArea).forEach((k) => {
-      formData.append(k, ocrArea[k]);
-    });
+  formData.append("batchNo", options.batchNo);
+
+  if (options.isFormal) {
+    formData.append("studentCode", options.studentCode);
+    if (options.ocrArea) {
+      const ocrArea = JSON.parse(options.ocrArea);
+      Object.keys(ocrArea).forEach((k) => {
+        formData.append(k, ocrArea[k]);
+      });
+    }
+  } else {
+    formData.append("fileType", options.fileType);
+    formData.append("roomOrClass", options.roomOrClass);
   }
 
   return uploadImage(options.isFormal, formData);
 }
 
-function getFileAndMd5(filepath) {
-  const buffer = fs.readFileSync(filepath);
+export async function getFileAndMd5(filepath) {
+  const buffer = await fs.promises.readFile(filepath);
   const md5 = MD5(buffer);
   const file = new File([buffer], path.basename(filepath));
   return { file, md5 };
 }
-
-/**
- * 获取文件的MD5
- * @param {String} source 文件路径
- */
-// function getMD5(source) {
-//   const buffer = fs.readFileSync(source);
-//   let fsHash = crypto.createHash("md5");
-//   fsHash.update(buffer);
-//   return fsHash.digest("hex");
-// }
-
-let messageInst = null;
-
-class UploadTask {
-  constructor({
-    taskList,
-    uploadSuccessCallback,
-    uploadTaskOverCallback,
-    uploadErrorCallBack,
-  }) {
-    this.taskList = taskList;
-    this.setTs = [];
-    this.taskRunning = false;
-    this.uploadSuccessCallback = uploadSuccessCallback;
-    this.uploadTaskOverCallback = uploadTaskOverCallback;
-    this.uploadErrorCallBack = uploadErrorCallBack;
-
-    this.startUploadTask();
-  }
-
-  addSetTime(action, time = 1 * 1000) {
-    this.setTs.push(setTimeout(action, time));
-  }
-
-  clearSetTs() {
-    if (!this.setTs.length) return;
-    this.setTs.forEach((t) => clearTimeout(t));
-    this.setTs = [];
-  }
-
-  addUploadTask(data) {
-    this.taskList.push(data);
-  }
-
-  getCurTask() {
-    return this.taskList.shift();
-  }
-
-  startUploadTask() {
-    this.taskRunning = true;
-
-    this.addSetTime(() => {
-      this.runUploadTask();
-    }, 10);
-  }
-
-  async runUploadTask() {
-    this.clearSetTs();
-
-    if (!this.taskList.length || !this.taskRunning) {
-      this.overUploadTask();
-      return;
-    }
-    const curTask = this.getCurTask();
-
-    let uploadResult = true;
-    await toUploadImg(curTask).catch(() => {
-      if (messageInst) {
-        messageInst.close();
-        messageInst = null;
-      }
-      messageInst = Message.error({
-        message: "上传异常",
-        offset: 1,
-      });
-      uploadResult = false;
-    });
-    if (uploadResult) {
-      if (messageInst) {
-        messageInst.close();
-        messageInst = null;
-      }
-      this.uploadSuccessCallback(curTask);
-    } else {
-      this.uploadErrorCallBack(curTask);
-    }
-
-    this.addSetTime(() => {
-      this.runUploadTask();
-    }, 10);
-  }
-
-  overUploadTask() {
-    this.taskRunning = false;
-    this.clearSetTs();
-    this.uploadTaskOverCallback && this.uploadTaskOverCallback();
-  }
-  stopUploadTask() {
-    this.taskRunning = false;
-    this.taskList = [];
-  }
-}
-
-export default UploadTask;
-
-export { toUploadImg };

+ 37 - 0
src/plugins/pQueue.js

@@ -0,0 +1,37 @@
+import PQueue from "p-queue";
+import { objAssign } from "./utils";
+
+export function usePQueue(customOption = {}) {
+  const defaultOption = {
+    concurrency: 6,
+    autoStart: true,
+  };
+  const option = objAssign(defaultOption, customOption);
+  const queue = new PQueue(option);
+
+  async function buildQueue(tasks) {
+    try {
+      const results = await queue.addAll(tasks);
+      return results;
+    } catch (error) {
+      console.error(error);
+      stopQueue();
+      throw error;
+    }
+  }
+
+  function startQueue() {
+    queue.start();
+  }
+
+  function stopQueue() {
+    queue.pause();
+    queue.clear();
+  }
+
+  return {
+    buildQueue,
+    startQueue,
+    stopQueue,
+  };
+}

+ 26 - 11
src/views/Home.vue

@@ -23,8 +23,14 @@
             <span>{{ userName }}</span>
           </li>
           <li class="hover" @click.stop="toBack">
-            <i class="icon icon-logout"></i>
-            <span>退出</span>
+            <template v-if="canLogout">
+              <i class="icon icon-logout"></i>
+              <span>退出</span>
+            </template>
+            <template v-else>
+              <i class="icon icon-narrow-right-act"></i>
+              <span>后退</span>
+            </template>
           </li>
         </ul>
       </div>
@@ -34,13 +40,15 @@
 </template>
 
 <script>
+import emitter from "element-ui/lib/mixins/emitter";
+
 import uploadTaskMixin from "../mixins/uploadTaskMixin";
 import setTimeMixins from "../mixins/setTimeMixins";
 import db from "../plugins/db";
 
 export default {
   name: "home",
-  mixins: [uploadTaskMixin, setTimeMixins],
+  mixins: [uploadTaskMixin, setTimeMixins, emitter],
   data() {
     return {
       userName: this.$ls.get("user", { realName: "" }).realName,
@@ -58,6 +66,9 @@ export default {
     unuploadNo() {
       return this.$store.state.client.unuploadNo;
     },
+    canLogout() {
+      return this.$route.name === "Scan";
+    },
   },
   methods: {
     // unupload count
@@ -70,16 +81,20 @@ export default {
       }, 2 * 1000);
     },
     toBack() {
-      if (this.$route.name === "Scan" && this.unuploadNo) {
-        this.$confirm("当前还有数据未上传,确定要退出采集吗?", "提示", {
-          type: "warning",
-        })
-          .then(() => {
-            this.$router.go(-1);
+      if (this.$route.name === "Scan") {
+        if (this.unuploadNo) {
+          this.$confirm("当前还有数据未上传,确定要退出采集吗?", "提示", {
+            type: "warning",
           })
-          .catch(() => {});
+            .then(() => {
+              this.$router.go(-1);
+            })
+            .catch(() => {});
+        } else {
+          this.$router.go(-1);
+        }
       } else {
-        this.$router.go(-1);
+        this.broadcast(this.$route.name, "go-back");
       }
     },
   },

+ 1 - 1
vue.config.js

@@ -12,7 +12,7 @@ module.exports = defineConfig({
   },
   pluginOptions: {
     electronBuilder: {
-      externals: ["crypto-js", "log4js", "gm", "cropperjs"],
+      externals: ["crypto-js", "log4js", "gm", "cropperjs", "p-queue"],
       builderOptions: {
         extraFiles: [
           "extra/scan/**",

+ 18 - 0
yarn.lock

@@ -4640,6 +4640,11 @@ eventemitter3@^4.0.0:
   resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
   integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
 
+eventemitter3@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
+  integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
+
 events@^3.0.0, events@^3.2.0:
   version "3.3.0"
   resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -7422,6 +7427,14 @@ p-map@^4.0.0:
   dependencies:
     aggregate-error "^3.0.0"
 
+p-queue@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.npmmirror.com/p-queue/-/p-queue-8.0.1.tgz#718b7f83836922ef213ddec263ff4223ce70bef8"
+  integrity sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==
+  dependencies:
+    eventemitter3 "^5.0.1"
+    p-timeout "^6.1.2"
+
 p-retry@^4.5.0:
   version "4.6.2"
   resolved "https://registry.npmmirror.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16"
@@ -7430,6 +7443,11 @@ p-retry@^4.5.0:
     "@types/retry" "0.12.0"
     retry "^0.13.1"
 
+p-timeout@^6.1.2:
+  version "6.1.2"
+  resolved "https://registry.npmmirror.com/p-timeout/-/p-timeout-6.1.2.tgz#22b8d8a78abf5e103030211c5fc6dee1166a6aa5"
+  integrity sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==
+
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"