mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-12 09:18:14 +08:00
🌱 继续优化验证码登录,尝试引入geetest
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-icon @click="confirmCUD" style="cursor: pointer" title="修改用户数据目录"
|
||||
<v-icon @click="confirmCUD()" style="cursor: pointer" title="修改用户数据目录"
|
||||
>mdi-pencil
|
||||
</v-icon>
|
||||
 
|
||||
@@ -45,7 +45,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-icon @click="confirmCLD" style="cursor: pointer" title="清理日志文件">mdi-delete</v-icon>
|
||||
<v-icon @click="confirmCLD()" style="cursor: pointer" title="清理日志文件"
|
||||
>mdi-delete</v-icon
|
||||
>
|
||||
 
|
||||
<v-icon @click="openPath('log')" style="cursor: pointer" title="打开日志目录"
|
||||
>mdi-folder-open
|
||||
|
||||
59
src/components/func/geetest.ts
Normal file
59
src/components/func/geetest.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @file component/func/geetest.ts
|
||||
* @description 封装自定义 geetest 组件,通过函数调用的方式,简化 geetest 的使用
|
||||
* @since Beta v0.5.1
|
||||
*/
|
||||
|
||||
import { h, render } from "vue";
|
||||
import type { ComponentInternalInstance, VNode } from "vue";
|
||||
|
||||
import geetest from "./geetest.vue";
|
||||
|
||||
const geetestId = "tg-func-geetest";
|
||||
|
||||
/**
|
||||
* @description 自定义 geetest 组件
|
||||
* @since Beta v0.5.1
|
||||
* @extends ComponentInternalInstance
|
||||
* @property {Function} exposeProxy.displayBox 弹出 geetest 验证
|
||||
* @return GeetestInstance
|
||||
*/
|
||||
interface GeetestInstance extends ComponentInternalInstance {
|
||||
exposeProxy: {
|
||||
displayBox: (
|
||||
props: TGApp.Plugins.Mys.Geetest.reqResp,
|
||||
) => Promise<TGApp.Plugins.Mys.Geetest.validateResp | false>;
|
||||
};
|
||||
}
|
||||
|
||||
const renderBox = (props: TGApp.Plugins.Mys.Geetest.reqResp): VNode => {
|
||||
const container = document.createElement("div");
|
||||
container.id = geetestId;
|
||||
const boxVNode: VNode = h(geetest, props);
|
||||
render(boxVNode, container);
|
||||
document.body.appendChild(container);
|
||||
return boxVNode;
|
||||
};
|
||||
|
||||
let geetestInstance: VNode;
|
||||
|
||||
/**
|
||||
* @function showGeetest
|
||||
* @since Beta v0.5.1
|
||||
* @description 弹出 geetest 验证
|
||||
* @param {TGApp.Plugins.Mys.Geetest.reqResp} props geetest 验证的参数
|
||||
* @return {Promise<TGApp.Plugins.Mys.Geetest.validateResp>} 验证成功返回验证数据
|
||||
*/
|
||||
async function showGeetest(
|
||||
props: TGApp.Plugins.Mys.Geetest.reqResp,
|
||||
): Promise<TGApp.Plugins.Mys.Geetest.validateResp | false> {
|
||||
if (geetestInstance !== undefined) {
|
||||
const boxVue = <GeetestInstance>geetestInstance.component;
|
||||
return boxVue.exposeProxy.displayBox(props);
|
||||
} else {
|
||||
geetestInstance = renderBox(props);
|
||||
return await showGeetest(props);
|
||||
}
|
||||
}
|
||||
|
||||
export default showGeetest;
|
||||
182
src/components/func/geetest.vue
Normal file
182
src/components/func/geetest.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<transition name="func-geetest-outer">
|
||||
<div v-show="show || showOuter" class="geetest-overlay" @click.self.prevent>
|
||||
<transition name="func-geetest-inner">
|
||||
<div v-show="showInner" class="geetest-box">
|
||||
<div class="geetest-top">
|
||||
<div class="geetest-title">请完成如下极验测试</div>
|
||||
</div>
|
||||
<div id="verify" class="geetest-mid">
|
||||
<div id="geetest" ref="geetestRef"></div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
interface GeetestProps {
|
||||
gt: string;
|
||||
challenge: string;
|
||||
new_captcha: number;
|
||||
success: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<GeetestProps>(), {
|
||||
gt: "",
|
||||
challenge: "",
|
||||
new_captcha: 0,
|
||||
success: 0,
|
||||
});
|
||||
|
||||
const show = ref<boolean>(false);
|
||||
const showOuter = ref<boolean>(false);
|
||||
const showInner = ref<boolean>(false);
|
||||
|
||||
const geetestRef = ref<HTMLElement>(<HTMLElement>document.getElementById("geetest"));
|
||||
|
||||
watch(show, () => {
|
||||
if (show.value) {
|
||||
showOuter.value = true;
|
||||
setTimeout(() => {
|
||||
showInner.value = true;
|
||||
}, 100);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
showInner.value = false;
|
||||
}, 100);
|
||||
setTimeout(() => {
|
||||
showOuter.value = false;
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await displayBox(props);
|
||||
});
|
||||
|
||||
async function displayBox(
|
||||
props: TGApp.Plugins.Mys.Geetest.reqResp,
|
||||
): Promise<TGApp.Plugins.Mys.Geetest.validateResp | false> {
|
||||
if (!props.gt || !props.challenge) return false;
|
||||
show.value = true;
|
||||
return await new Promise<TGApp.Plugins.Mys.Geetest.validateResp>((resolve) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
initGeetest(
|
||||
{
|
||||
gt: props.gt,
|
||||
challenge: props.challenge,
|
||||
offline: false,
|
||||
new_captcha: true,
|
||||
product: "custom",
|
||||
area: "#verify",
|
||||
width: "250px",
|
||||
},
|
||||
(captchaObj: TGApp.Plugins.Mys.Geetest.GeetestCaptcha) => {
|
||||
geetestRef.value.innerHTML = "";
|
||||
captchaObj.appendTo("#geetest");
|
||||
captchaObj.onSuccess(async () => {
|
||||
const validate = captchaObj.getValidate();
|
||||
resolve(validate);
|
||||
captchaObj.onClose(() => {
|
||||
show.value = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
displayBox,
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.func-geetest-outer-enter-active,
|
||||
.func-geetest-outer-leave-active,
|
||||
.func-geetest-inner-enter-active {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.func-geetest-inner-leave-active {
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.func-geetest-inner-enter-from {
|
||||
opacity: 0;
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.func-geetest-inner-enter-to,
|
||||
.func-geetest-inner-leave-from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.func-geetest-outer-enter-to,
|
||||
.func-geetest-outer-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.func-geetest-outer-enter-from,
|
||||
.func-geetest-outer-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.func-geetest-inner-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.geetest-overlay {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(20px);
|
||||
background: rgb(0 0 0 / 50%);
|
||||
}
|
||||
|
||||
.geetest-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--box-bg-1);
|
||||
color: var(--app-page-content);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.geetest-top {
|
||||
border-bottom: 1px solid var(--common-shadow-4);
|
||||
font-family: var(--font-title);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.geetest-title {
|
||||
color: var(--common-text-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.geetest-mid {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background: var(--box-bg-2);
|
||||
}
|
||||
|
||||
#verify {
|
||||
width: 256px;
|
||||
height: 320px;
|
||||
}
|
||||
</style>
|
||||
@@ -10,6 +10,7 @@ import { createVuetify } from "vuetify";
|
||||
import App from "./App.vue";
|
||||
import router from "./router/index.js";
|
||||
import store from "./store/index.js";
|
||||
import "https://static.geetest.com/static/js/gt.0.4.9.js";
|
||||
|
||||
import "@mdi/font/css/materialdesignicons.css";
|
||||
import "vuetify/styles";
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showConfirm from "../../components/func/confirm.js";
|
||||
import showGeetest from "../../components/func/geetest.js";
|
||||
import showSnackbar from "../../components/func/snackbar.js";
|
||||
import Mys from "../../plugins/Mys/index.js";
|
||||
|
||||
@@ -33,16 +34,8 @@ async function tryCaptchaLogin(): Promise<void> {
|
||||
text: "+86",
|
||||
});
|
||||
if (!phone) return;
|
||||
const captchaResp = await Mys.User.getCaptcha(phone);
|
||||
console.log("[captchaResp]", captchaResp);
|
||||
if ("retcode" in captchaResp) {
|
||||
showSnackbar({
|
||||
text: `[${captchaResp.retcode}] ${captchaResp.message}`,
|
||||
color: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const action_type = captchaResp.action_type;
|
||||
const action_type = await tryGetCaptcha(phone);
|
||||
if (!action_type) return;
|
||||
const captcha = await showConfirm({
|
||||
mode: "input",
|
||||
title: "请输入验证码",
|
||||
@@ -52,6 +45,25 @@ async function tryCaptchaLogin(): Promise<void> {
|
||||
const loginResp = await Mys.User.login(phone, captcha, action_type);
|
||||
console.log("[loginResp]", loginResp);
|
||||
}
|
||||
|
||||
async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | false> {
|
||||
const captchaResp = await Mys.User.getCaptcha(phone, aigis);
|
||||
console.log("[captchaResp]", captchaResp);
|
||||
if ("retcode" in captchaResp) {
|
||||
if (!captchaResp.data || captchaResp.data === "") {
|
||||
showSnackbar({
|
||||
text: `[${captchaResp.retcode}] ${captchaResp.message}`,
|
||||
color: "error",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
const aigis: TGApp.Plugins.Mys.CaptchaLogin.CaptchaAigis = JSON.parse(captchaResp.data);
|
||||
const resp = await showGeetest(aigis.data);
|
||||
const aigisStr = btoa(`${aigis.session_id};${JSON.stringify(resp)}`);
|
||||
return await tryGetCaptcha(phone, aigisStr);
|
||||
}
|
||||
return captchaResp.action_type;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.test-box {
|
||||
|
||||
@@ -40,12 +40,13 @@ function rsaEncrypt(data: string): string {
|
||||
/**
|
||||
* @description 获取短信验证码
|
||||
* @since Beta v0.5.1
|
||||
* @todo retcode 为-3101时,表示需要进行验证,需要从resp.headers["x-rpc-aigis"]中获取相关数据
|
||||
* @param {string} phone - 手机号
|
||||
* @param {string} [aigis] - 验证数据
|
||||
* @returns {Promise<TGApp.Plugins.Mys.CaptchaLogin.CaptchaData | TGApp.BBS.Response.Base>}
|
||||
*/
|
||||
export async function getCaptcha(
|
||||
phone: string,
|
||||
aigis?: string,
|
||||
): Promise<TGApp.Plugins.Mys.CaptchaLogin.CaptchaData | TGApp.BBS.Response.BaseWithData> {
|
||||
const url = "https://passport-api.mihoyo.com/account/ma-cn-verifier/verifier/createLoginCaptcha";
|
||||
const device_fp = getDeviceInfo("device_fp");
|
||||
@@ -54,7 +55,7 @@ export async function getCaptcha(
|
||||
const device_model = getDeviceInfo("product");
|
||||
const body = { area_code: rsaEncrypt("+86"), mobile: rsaEncrypt(phone) };
|
||||
const header: Record<string, string> = {
|
||||
"x-rpc-aigis": "",
|
||||
"x-rpc-aigis": aigis || "",
|
||||
"x-rpc-app_version": TGConstant.BBS.VERSION,
|
||||
"x-rpc-client_type": "2",
|
||||
"x-rpc-app_id": TGConstant.BBS.APP_ID,
|
||||
|
||||
15
src/plugins/Mys/types/CaptchaLogin.d.ts
vendored
15
src/plugins/Mys/types/CaptchaLogin.d.ts
vendored
@@ -38,6 +38,21 @@ declare namespace TGApp.Plugins.Mys.CaptchaLogin {
|
||||
action_type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 触发验证的序列化数据
|
||||
* @since Beta v0.5.1
|
||||
* @interface CaptchaAigis
|
||||
* @property {string} session_id 会话 id
|
||||
* @property {number} mmt_type mmt 类型
|
||||
* @property {TGApp.Plugins.Mys.Geetest.getData} data 数据
|
||||
* @return CaptchaBody
|
||||
*/
|
||||
interface CaptchaAigis {
|
||||
session_id: string;
|
||||
mmt_type: number;
|
||||
data: TGApp.Plugins.Mys.Geetest.reqResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 短信验证码登录返回数据
|
||||
* @since Beta v0.5.1
|
||||
|
||||
98
src/plugins/Mys/types/Geetest.d.ts
vendored
Normal file
98
src/plugins/Mys/types/Geetest.d.ts
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @file plugins/Mys/types/Geetest.d.ts
|
||||
* @description Mys 插件 Geetest 类型定义文件
|
||||
* @since Beta v0.5.1
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description Mys 插件 Geetest 类型
|
||||
* @since Beta v0.5.1
|
||||
* @namespace TGApp.Plugins.Mys.Geetest
|
||||
* @memberof TGApp.Plugins.Mys
|
||||
*/
|
||||
declare namespace TGApp.Plugins.Mys.Geetest {
|
||||
/**
|
||||
* @description 极验验证的响应数据
|
||||
* @since Beta v0.5.1
|
||||
* @interface reqResp
|
||||
* @property {string} gt - 极验验证 gt
|
||||
* @property {string} challenge - 极验验证 challenge
|
||||
* @property {number} new_captcha - 极验验证 new_captcha
|
||||
* @property {number} success - 极验验证 success
|
||||
* @return reqResp
|
||||
*/
|
||||
interface reqResp {
|
||||
gt: string;
|
||||
challenge: string;
|
||||
new_captcha: number;
|
||||
success: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 极验验证的请求数据
|
||||
* @since Beta v0.5.1
|
||||
* @interface postData
|
||||
* @property {string} challenge - 极验验证 challenge
|
||||
* @property {string} validate - 极验验证 validate
|
||||
* @return postData
|
||||
*/
|
||||
interface postData {
|
||||
challenge: string;
|
||||
validate: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 极验验证的请求方法-请求参数
|
||||
* @since Beta v0.5.1
|
||||
* @interface InitGeetestParams
|
||||
* @property {string} gt - 极验验证 gt
|
||||
* @property {string} challenge - 极验验证 challenge
|
||||
* @property {boolean} offline - 极验验证 offline
|
||||
* @property {boolean} new_captcha - 极验验证 new_captcha
|
||||
* @property {string} product - 极验验证 product
|
||||
* @property {string} width - 极验验证 width
|
||||
* @property {string} area - 极验验证 area
|
||||
* @return InitGeetestParams
|
||||
*/
|
||||
interface InitGeetestParams {
|
||||
gt: string;
|
||||
challenge: string;
|
||||
offline: boolean;
|
||||
new_captcha: boolean;
|
||||
product: string;
|
||||
width: string;
|
||||
area: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Geetest 插件 captchaObj
|
||||
* @since Beta v0.5.1
|
||||
* @interface GeetestCaptcha
|
||||
* @property {Function} appendTo
|
||||
* @property {Function} getValidate
|
||||
* @property {Function} onSuccess
|
||||
* @property {Function} onClose
|
||||
* @return GeetestCaptcha
|
||||
*/
|
||||
interface GeetestCaptcha {
|
||||
appendTo: (selector: string) => void;
|
||||
getValidate: () => validateResp;
|
||||
onSuccess: (callback: () => void) => void;
|
||||
onClose: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Geetest 插件 validate
|
||||
* @since Beta v0.5.1
|
||||
* @interface validateResp
|
||||
* @property {string} geetest_challenge
|
||||
* @property {string} geetest_validate
|
||||
* @property {string} geetest_seccode
|
||||
* @return validateResp
|
||||
*/
|
||||
interface validateResp {
|
||||
geetest_challenge: string;
|
||||
geetest_validate: string;
|
||||
geetest_seccode: string;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user