<template>
  <div style='height: 100%;'>
    <div id='import-header'>
      <img src='../assets/images/nav_images.png' alt=''>照片批量上传工具
      <button id='exit-btn' @click='logout' title="退出"></button>
    </div>
    <div id='import-body'>
      <div id='import-div'>
        <a href='javascript:;' class='file'>
          <!-- webkitdirectory-->
          <button id='import-btn' @click="importPhotos">
            上传照片
          </button>
        </a>
        <div id='progressDiv'>
          <div>

            <span>
              成功:
              <span style="color:green;">{{successNum}}</span>/{{allNum}}
            </span>
            <span>
              失败:
              <span style="color:red;">{{errorNum}}</span>/{{allNum}}
            </span>
            <span>
              并发请求:
              <span style="color:black;">{{reqNum}}</span>
            </span>
          </div>
          <div>
            <span>开始时间：{{startProcessTimeFormat}} </span>
            <span>结束时间：{{endProcessTimeFormat}} </span>
            <span>处理速度：{{(1000*processSpeed).toFixed(2)}}个/秒</span>
          </div>
          <div>
            <span v-show="completeShow" style="color: green;font-weight: bold;">全部处理完成</span>
            <span v-show="!completeShow&&(successNum>0||errorNum>0)" style="color: red;font-weight: bold;">处理中...</span>

          </div>
        </div>
      </div>
      <div id='progress-div'>
        <div id='console-panel'>
          <div v-for='msgInfo in returnMsgList200' :key="msgInfo.fileName">
            <p class='console-line' v-if='msgInfo.success'>
              <span>
                <img src='../assets/images/icon_sucess.png'>
              </span>
              <span>{{msgInfo.fileName}}:{{msgInfo.msg}}</span>
            </p>
            <p class='console-line' v-if='!msgInfo.success'>
              <span>
                <img src='../assets/images/icon_failed.png'>
              </span>
              <span>{{msgInfo.fileName}}:{{msgInfo.msg}}</span>
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
const CryptoJS = require("crypto-js");
const Base64 = require("js-base64").Base64;
const fs = window.nodeRequire("fs");
const path = window.nodeRequire("path");
const async = require("async");
const moment = require("moment");

function isImageFile(fileName) {
  const lowercaseName = fileName.toLowerCase();
  return (
    lowercaseName.endsWith(".jpg") ||
    lowercaseName.endsWith(".jpeg") ||
    lowercaseName.endsWith(".png")
  );
}

// (async () => window.DB.updateFaceSet("fbd41e3e320f21c9f35467c268f86b4c", 22))();

//读取文件，返回网站路径. 只扫描当前文件夹，不递归扫描
function readImageFiles(folderPath) {
  const files = fs.readdirSync(folderPath);
  return files
    .filter(isImageFile)
    .sort()
    .map(fileName => folderPath + "/" + fileName);
}

let CONCURRENCY = 2; //同时处理的照片数量; 深夜增加并发

export default {
  data() {
    return {
      photoList: [], //总的照片队列
      returnMsgList: [], //返回信息
      successNum: 0, //成功数量
      errorNum: 0, //失败数量
      allNum: 0, //总数
      completeShow: false,
      reqNum: 0,
      startProcessTime: null,
      endProcessTime: null,
      processSpeed: 0
    };
  },
  methods: {
    init() {
      this.photoList = [];
      this.returnMsgList = [];
      this.successNum = 0;
      this.errorNum = 0;
      this.allNum = 0;
      this.completeShow = false;
      this.reqNum = 0;
      this.startProcessTime = null;
      this.endProcessTime = null;
      this.processSpeed = 0;
    },
    importPhotos() {
      this.init();
      var electron = window.nodeRequire("electron");
      let dialog = electron.remote.dialog;
      dialog.showOpenDialog(
        {
          title: "选择照片所在目录",
          properties: ["openDirectory"]
        },
        folderPaths => {
          if (folderPaths) {
            this.photoList = readImageFiles(folderPaths[0]);
            this.allNum = this.photoList.length;
            this.startProcessTime = Date.now();
            this.processQueue();
          }
        }
      );
    },
    processQueue() {
      let taskQueue = [];
      let total = 0;
      for (
        let i = this.successNum + this.errorNum;
        i < this.photoList.length && total < 1000;
        i++, total++
      ) {
        taskQueue.push(this.processStudentPhoto.bind(this, this.photoList[i]));
      }
      this.processQueueSingle(taskQueue);
    },
    processQueueSingle(taskQueue1000) {
      // 并发处理请求，可在控制台查看请求峰值
      CONCURRENCY = new Date().getHours() < 6 ? 8 : 5;
      async.parallelLimit(taskQueue1000, CONCURRENCY, (err, results) => {
        if (err) {
          alert(err);
          console.log(err);
        } else {
          console.log(results);
          if (this.successNum + this.errorNum < this.allNum) {
            console.log("处理完1000张图片了，3秒后继续...");
            setTimeout(this.processQueue, 3000);
          } else {
            console.log("photoList处理完毕");
            this.endProcessTime = Date.now();
            this.completeShow = true;
          }
        }
      });
    },
    //处理单个照片
    async processStudentPhoto(studentPhotoPath) {
      const fileSuffix = path.extname(studentPhotoPath); //文件后缀
      const identityNumber = path
        .basename(studentPhotoPath)
        .replace(fileSuffix, ""); //文件名就是身份证号码
      const photoFile = fs.readFileSync(studentPhotoPath);
      const rootOrgId = localStorage.getItem("rootOrgId");
      this.reqNum = window.requestInProcessingTotal;

      this.processSpeed =
        (this.successNum + this.errorNum) /
        (Date.now() - this.startProcessTime);

      //生成新名称
      const upyunPhotoPath = (() => {
        const md5Hash = CryptoJS.MD5(
          Base64.encode(identityNumber + new Date().getTime())
        ).toString();
        return rootOrgId + "/" + identityNumber + "/" + md5Hash + fileSuffix;
      })();

      try {
        let studentFaceInfo = await this.getStudentInfo(
          rootOrgId,
          identityNumber
        );
        let faceToken = await this.detectFace(photoFile);
        if (this.faceSetToken == undefined) {
          this.faceSetToken = await this.getFaceSetToken();
        }
        await this.addFaceToSet(this.faceSetToken, faceToken);

        const photoInfo = {
          studentId: studentFaceInfo.student.id,
          faceSetToken: this.faceSetToken,
          faceToken: faceToken,
          studentPhotoPath: studentPhotoPath,
          rootOrgId: rootOrgId,
          upyunPhotoPath
        };
        await this.saveImageToUpyun({ upyunPhotoPath, photoFile });
        await this.saveStudentFaceInfoByPut(photoInfo);
        this.finishOnePhotoSuccess("处理成功", studentPhotoPath);
      } catch (err) {
        console.log(err);
        this.finishOnePhotoFail(err, studentPhotoPath);
      }
    },
    async getStudentInfo(rootOrgId, identityNumber) {
      return new Promise((resolve, reject) => {
        this.$http
          .get(
            "/api/ecs_core/studentFaceInfo/identityNumber?orgId=" +
              rootOrgId +
              "&identityNumber=" +
              identityNumber
          )
          .then(res => {
            var studentFaceInfo = res.data;
            if (studentFaceInfo.student && studentFaceInfo.student.id) {
              resolve(studentFaceInfo);
            } else {
              reject("查询身份证不存在");
            }
          })
          .catch(err => {
            console.log(err);
            reject("根据身份证号码查询失败");
          });
      });
    },
    //保存文件至又拍云
    async saveImageToUpyun({ upyunPhotoPath, photoFile }) {
      const url = process.env.VUE_APP_UPYUN_BUCKETURL + upyunPhotoPath;
      const authorization =
        "Basic " +
        Base64.encode(
          process.env.VUE_APP_UPYUN_OPERATOR +
            ":" +
            process.env.VUE_APP_UPYUN_PASSWORD
        );
      const headers = {
        headers: {
          Authorization: authorization,
          "Content-Type": "image/jpeg"
        }
      };
      return this.$http.put(url, photoFile, headers).catch(err => {
        console.log(err);
        throw "saveImageToUpyun失败";
      });
    },
    //获取faceSetToken
    async getFaceSetToken() {
      return window.DB.queryFaceSet();
    },
    //faceToken加入faceSetToken
    async addFaceToSet(faceset_token, face_token) {
      let formData_addface = new FormData();
      formData_addface.append("api_key", process.env.VUE_APP_FACEPP_API_KEY);
      formData_addface.append(
        "api_secret",
        process.env.VUE_APP_FACEPP_API_SECRET
      );
      formData_addface.append("faceset_token", faceset_token);
      formData_addface.append("face_tokens", face_token);
      return new Promise((resolve, reject) => {
        this.$http
          .post("/facepp/v3/faceset/addface", formData_addface)
          .then(res => {
            // console.log(
            //   `res.data.face_added: ${
            //     res.data.face_added
            //   }, res.data.face_count: ${res.data.face_count}`
            // );
            if (res.data.face_count > 8000) {
              window.DB.updateFaceSet(faceset_token, res.data.face_count).then(
                () => {
                  this.faceSetToken = undefined;
                  resolve();
                },
                err => {
                  reject(err);
                }
              );
            } else {
              resolve();
            }
          })
          .catch(err => {
            console.log(err);
            reject("faceToken加入faceSetToken失败: addface catch error");
          });
      });
    },
    //face++分析人脸
    async detectFace(file) {
      let fileBlob = new Blob([file]);
      let formData_face_token = new FormData();
      formData_face_token.append("api_key", process.env.VUE_APP_FACEPP_API_KEY);
      formData_face_token.append(
        "api_secret",
        process.env.VUE_APP_FACEPP_API_SECRET
      );
      formData_face_token.append("image_file", fileBlob);
      return new Promise((resolve, reject) => {
        this.$http
          .post("/facepp/v3/detect", formData_face_token)
          .then(res => {
            if (res.data.faces.length >= 1) {
              resolve(res.data.faces[0].face_token);
            } else {
              reject("face++没有检测到人脸;");
            }
          })
          .catch(err => {
            console.log(err);
            reject("调用face++检测人脸失败");
          });
      });
    },
    async saveStudentFaceInfoByPut({
      rootOrgId,
      studentId,
      faceSetToken,
      faceToken,
      upyunPhotoPath
    }) {
      return this.$http
        .put("/api/ecs_core/studentFaceInfo", {
          rootOrgId,
          studentId,
          faceSetToken,
          faceToken,
          photoPath: upyunPhotoPath
        })
        .catch(err => {
          console.log(err);
          throw "saveStudentFaceInfoByPut失败";
        });
    },
    //成功或失败处理
    finishOnePhotoFail(msg, studentPhotoPath) {
      try {
        const fileName = path.basename(studentPhotoPath);
        this.returnMsgList.push({
          success: false,
          fileName,
          msg
        });
        this.errorNum++;

        //移动照片到errorfiles文件夹
        const errorfilePath = path.join(
          path.dirname(studentPhotoPath),
          "errorfiles"
        );
        if (!fs.existsSync(errorfilePath)) {
          fs.mkdirSync(errorfilePath);
        }
        fs.copyFileSync(studentPhotoPath, path.join(errorfilePath, fileName));

        fs.appendFileSync(
          path.join(errorfilePath, "errorPhotos.txt"),
          fileName + ":" + msg + "\n"
        );
      } catch (error) {
        console.log(error);
      }
    },
    //成功处理一张照片
    finishOnePhotoSuccess(msg, studentPhotoPath) {
      this.returnMsgList.push({
        success: true,
        fileName: path.basename(studentPhotoPath),
        msg: msg
      });
      this.successNum++;
    },
    logout() {
      localStorage.removeItem("rootOrgId");
      localStorage.removeItem("userName");
      localStorage.removeItem("user_token");
      this.$router.push({
        path: "/login"
      });
    }
  },
  computed: {
    returnMsgList200() {
      return this.returnMsgList.slice(this.returnMsgList.length - 200);
    },
    startProcessTimeFormat() {
      return this.startProcessTime
        ? moment(this.startProcessTime).format("YYYY-MM-DD HH:mm:ss")
        : "-";
    },
    endProcessTimeFormat() {
      return this.endProcessTime
        ? moment(this.endProcessTime).format("YYYY-MM-DD HH:mm:ss")
        : "-";
    }
  }
};
</script>

<style scoped>
</style>
