Browse Source

登录页-极验

Michael Wang 3 years ago
parent
commit
de8c0a45b6
5 changed files with 188 additions and 19 deletions
  1. 1 0
      index.html
  2. 3 1
      src/api/login.ts
  3. 104 0
      src/features/UserLogin/GeeTest.vue
  4. 79 17
      src/features/UserLogin/UserLogin.vue
  5. 1 1
      src/utils/logger.ts

+ 1 - 0
index.html

@@ -6,6 +6,7 @@
   <link rel="icon" href="/favicon.ico" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>网考学生端</title>
+  <script src="https://static.geetest.com/static/tools/gt.js"></script>
 </head>
 
 <body>

+ 3 - 1
src/api/login.ts

@@ -6,7 +6,8 @@ export async function loginApi(
   accountValue: string,
   password: string,
   domain: string,
-  rootOrgId: number
+  rootOrgId: number,
+  geeParams: object
 ) {
   return httpApp.post<{ code: string; desc: string; content: any }>(
     "/api/ecs_core/verifyCode/gt/login",
@@ -17,6 +18,7 @@ export async function loginApi(
       domain,
       rootOrgId,
       alwaysOK: true,
+      ...geeParams,
     },
     { setGlobalMask: true }
   );

+ 104 - 0
src/features/UserLogin/GeeTest.vue

@@ -0,0 +1,104 @@
+<script setup lang="ts">
+import { httpApp } from "@/plugins/axiosApp";
+import { watch } from "vue";
+
+const props = defineProps<{ reset: number }>();
+
+const emit = defineEmits<{ (e: "onLoad", value: Captcha): void }>();
+
+watch(
+  () => [props.reset],
+  () => resetGee(),
+  { immediate: true }
+);
+
+let loading = $ref(true);
+
+type Captcha = {
+  appendTo(sel: string): void;
+  onReady(cb: () => void): void;
+  onError(cb: (error: any) => void): void;
+  destroy(): void;
+  getValidate(): any;
+};
+async function resetGee() {
+  function handler(captchaObj: Captcha) {
+    emit("onLoad", captchaObj);
+    // console.log("emited", captchaObj);
+    // 将验证码加到id为captcha的元素里,同时会有三个input的值用于表单提交
+    captchaObj.appendTo("#captcha");
+    captchaObj.onReady(() => (loading = false));
+    captchaObj.onError((error: any) => {
+      // 出错啦,可以提醒用户稍后进行重试
+      // error 包含error_code、msg
+      logger({
+        key: "极验",
+        cnl: ["local", "server"],
+        pgu: "AUTO",
+        ext: {
+          errorCode: error.error_code,
+          msg: error.msg,
+        },
+      });
+      $message.error("资源加载失败,请关闭程序,检查网络状况后重试。", {
+        duration: 1 * 60,
+        closable: true,
+      });
+    });
+  }
+
+  let res = null;
+  try {
+    res = await httpApp.post("/api/ecs_core/verifyCode/register", {
+      user_id: localStorage.getItem("uuidForEcs"),
+      client_type: "Web",
+    });
+  } catch (error: any) {
+    logger({
+      key: "极验",
+      cnl: ["local", "server"],
+      act: "register接口调用失败",
+      stk: error.stack,
+      dtl: error.message,
+      ext: { errorName: error.name },
+    });
+    $message.error("资源注册失败,请关闭程序,检查网络状况后重试。", {
+      duration: 5 * 60,
+      closable: true,
+    });
+    return;
+  }
+  // console.log(res);
+  const data = res.data;
+  if (!data.success) {
+    logger({
+      key: "极验",
+      cnl: ["local", "server"],
+      dtl: "极验API宕机",
+    });
+  }
+  // @ts-ignore
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+  window.initGeetest(
+    {
+      gt: data.gt,
+      challenge: data.challenge,
+      new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机
+      offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
+      product: "float", // 产品形式,包括:float,popup
+      width: "100%",
+    },
+    handler
+  );
+}
+</script>
+
+<template>
+  <div>
+    <!-- <label>完成验证:</label> -->
+    <div id="captcha">
+      <p v-if="loading" class="show">正在加载验证码......</p>
+      <!-- 重新获取验证码 -->
+    </div>
+  </div>
+</template>

+ 79 - 17
src/features/UserLogin/UserLogin.vue

@@ -13,6 +13,7 @@ import { DOMAIN, VITE_GIT_REPO_VERSION } from "@/constants/constants";
 import { onMounted, onUnmounted, watch } from "vue";
 import { Person, LockClosed, CloseCircleOutline } from "@vicons/ionicons5";
 import { FormRules, FormItemInst } from "naive-ui";
+import GeeTest from "./GeeTest.vue";
 
 logger({
   cnl: ["console", "local", "server"],
@@ -110,23 +111,67 @@ const fromRules: FormRules = {
 let errorInfo = $ref("");
 watch([formValue], () => (errorInfo = ""));
 
+type Captcha = {
+  appendTo(sel: string): void;
+  onReady(cb: () => void): void;
+  onError(cb: (error: any) => void): void;
+  destroy(): void;
+  getValidate(): any;
+};
+let captchaObj: Captcha = $ref();
+let resetGeeTime = $ref(0);
+resetGeeTime = Date.now();
+// 超过60秒,刷新极验;优化刷新次数,当用户真正想输入时
+watch(
+  () => [formValue.accountType, formValue.accountValue, formValue.password],
+  () => {
+    if (isGeeTestEnabled && Date.now() - resetGeeTime > 60 * 1000) {
+      captchaObj?.destroy();
+      resetGeeTime = Date.now();
+    }
+  }
+);
+
 const router = useRouter();
 
 async function loginForuser() {
   if (await formRef.validate().catch(() => true)) return;
 
+  if (isGeeTestEnabled) {
+    if (!captchaObj?.getValidate()) {
+      $message.error("请完成验证");
+      loginBtnLoading = false;
+      return;
+    }
+  }
+
   logger({
     pgn: "登录页面",
+    cnl: ["local", "server"],
     act: "login clicked",
     ext: { UA: navigator.userAgent },
   });
   errorInfo = "";
+
+  let geeParams = {};
+  if (isGeeTestEnabled) {
+    // const geeForm = document.querySelector(".geetest_form").children;
+    const geeRes = captchaObj.getValidate();
+    geeParams = {
+      user_id: localStorage.getItem("uuidForEcs"),
+      client_type: "Web",
+      challenge: geeRes.geetest_challenge, // geeForm[0].value,
+      validate: geeRes.geetest_validate, // geeForm[1].value,
+      seccode: geeRes.geetest_seccode, // geeForm[2].value,
+    };
+  }
   const res = await loginApi(
     formValue.accountType,
     formValue.accountValue,
     formValue.password,
     domain,
-    store.QECSConfig.ROOT_ORG_ID
+    store.QECSConfig.ROOT_ORG_ID,
+    geeParams
   );
 
   if (res.data.code === "200") {
@@ -135,6 +180,8 @@ async function loginForuser() {
     store.user = res.data.content;
   } else {
     errorInfo = res.data.desc;
+    captchaObj.destroy();
+    resetGeeTime = Date.now();
     logger({
       pgu: "AUTO",
       act: "点击登录-res-error",
@@ -144,21 +191,33 @@ async function loginForuser() {
     return;
   }
 
-  const [{ data: student }, { data: specialty }] = await Promise.all([
-    getStudentInfoBySessionApi(),
-    getStudentSpecialtyNameListApi(),
-  ]);
-  const user = {
-    ...res.data.content,
-    ...student,
-    specialty: specialty.join(),
-    schoolDomain: domain,
-  };
-
-  store.user = user;
-  createUserDetailLog();
-
-  void router.push({ name: "ChangePassword" });
+  try {
+    const [{ data: student }, { data: specialty }] = await Promise.all([
+      getStudentInfoBySessionApi(),
+      getStudentSpecialtyNameListApi(),
+    ]);
+    const user = {
+      ...res.data.content,
+      ...student,
+      specialty: specialty.join(),
+      schoolDomain: domain,
+    };
+
+    store.user = user;
+    createUserDetailLog();
+
+    void router.push({ name: "ChangePassword" });
+  } catch (error) {
+    logger({
+      cnl: ["local", "server"],
+      act: "登录失败",
+      dtl: "getStudentInfoBySession失败",
+    });
+    $message.error("获取学生信息失败,请重试!", {
+      duration: 15,
+      closable: true,
+    });
+  }
 }
 
 function closeApp() {
@@ -247,7 +306,10 @@ function closeApp() {
               class="form-item-style"
               style="height: 40px; margin-top: 0px"
             >
-              <!-- <GeeTest :reset="resetGeeTime" @onLoad="handleGtResult" /> -->
+              <GeeTest
+                :reset="resetGeeTime"
+                @onLoad="(v) => (captchaObj = v)"
+              />
             </n-form-item>
 
             <div

+ 1 - 1
src/utils/logger.ts

@@ -16,7 +16,7 @@ type LogDetail = {
   /** default log */
   level?: "debug" | "log" | "warn" | "error";
   /** channels. 往哪些渠道放日志? default: ['console'] */
-  cnl?: ("console" | "local" | "server" | "bd")[];
+  cnl: ("console" | "local" | "server" | "bd")[];
   /** 操作的类型,方便在日志里面查找相同类型的操作 */
   key?: string;
   /** page name */