update add login modules
This commit is contained in:
188
packages/kv-login/src/modules/login-handle.ts
Normal file
188
packages/kv-login/src/modules/login-handle.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { query } from './query.ts';
|
||||
import { createMessage } from '../pages/kv-message.ts';
|
||||
import { WX_MP_APP_ID } from '../pages/kv-login.ts';
|
||||
export const message = createMessage();
|
||||
type LoginOpts = {
|
||||
loginMethod: 'password' | 'phone' | 'wechat' | 'wechat-mp',
|
||||
data: any,
|
||||
el: HTMLElement
|
||||
}
|
||||
export const redirectHome = () => {
|
||||
console.log('重定向到首页')
|
||||
const href = window.location.href;
|
||||
const url = new URL(href);
|
||||
const redirect = url.searchParams.get('redirect');
|
||||
if (redirect) {
|
||||
const href = decodeURIComponent(redirect);
|
||||
window.open(href, '_self');
|
||||
} else {
|
||||
window.open('/root/home', '_self');
|
||||
}
|
||||
}
|
||||
export const loginHandle = async (opts: LoginOpts) => {
|
||||
const { loginMethod, data, el } = opts
|
||||
switch (loginMethod) {
|
||||
case 'password':
|
||||
await loginByPassword(data)
|
||||
break
|
||||
case 'phone':
|
||||
await loginByPhone(data)
|
||||
break
|
||||
case 'wechat-mp':
|
||||
await loginByWeChatMp(data)
|
||||
break
|
||||
case 'wechat':
|
||||
await loginByWeChat(data)
|
||||
break
|
||||
default:
|
||||
console.warn('未知的登录方式:', loginMethod)
|
||||
}
|
||||
}
|
||||
|
||||
const loginByPassword = async (data: { username: string, password: string }) => {
|
||||
console.log('使用用户名密码登录:', data)
|
||||
let needLogin = true; // 这里可以根据实际情况决定是否需要登录, 只能判断密码登录和手机号登录
|
||||
|
||||
const isLogin = await query.checkLocalToken()
|
||||
if (isLogin) {
|
||||
const loginUser = await query.checkLocalUser()
|
||||
if (loginUser?.username === data?.username) {
|
||||
const res = await query.getMe()
|
||||
if (res.code === 200) {
|
||||
needLogin = false
|
||||
console.log('已登录,跳过登录步骤')
|
||||
message.success('已登录')
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!needLogin) {
|
||||
redirectHome()
|
||||
return;
|
||||
}
|
||||
const res = await query.login({
|
||||
username: data.username,
|
||||
password: data.password
|
||||
})
|
||||
if (res.code === 200) {
|
||||
console.log('登录成功')
|
||||
message.success('登录成功')
|
||||
redirectHome()
|
||||
} else {
|
||||
message.error(`登录失败: ${res.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const loginByPhone = async (data: { phone: string, code: string }) => {
|
||||
console.log('使用手机号登录:', data)
|
||||
}
|
||||
|
||||
const loginByWeChat = async (data: { wechatCode: string }) => {
|
||||
console.log('使用微信登录:', data)
|
||||
}
|
||||
const loginByWeChatMp = async (data: { wechatMpCode: string }) => {
|
||||
console.log('使用微信公众号登录:', data)
|
||||
}
|
||||
|
||||
const clearCode = () => {
|
||||
const url = new URL(window.location.href);
|
||||
// 清理 URL 中的 code 参数
|
||||
url.searchParams.delete('code');
|
||||
url.searchParams.delete('state');
|
||||
window.history.replaceState({}, document.title, url.toString());
|
||||
}
|
||||
export const checkWechat = async () => {
|
||||
const url = new URL(window.location.href);
|
||||
const code = url.searchParams.get('code');
|
||||
const state = url.searchParams.get('state');
|
||||
if (state?.includes?.('-')) {
|
||||
// 公众号登录流程,不在这里处理
|
||||
return;
|
||||
}
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
const res = await query.loginByWechat({ code });
|
||||
if (res.code === 200) {
|
||||
message.success('登录成功');
|
||||
redirectHome();
|
||||
} else {
|
||||
message.error(res.message || '登录失败');
|
||||
clearCode();
|
||||
}
|
||||
};
|
||||
|
||||
export const checkMpWechat = async () => {
|
||||
const url = new URL(window.location.href);
|
||||
const originState = url.searchParams.get('state');
|
||||
const [mpLogin, state] = originState ? originState.split('-') : [null, null];
|
||||
console.log('检查微信公众号登录流程:', mpLogin, state, originState);
|
||||
if (mpLogin === '1') {
|
||||
// 手机端扫描的时候访问的链接,跳转到微信公众号授权页面
|
||||
checkMpWechatInWx()
|
||||
} else if (mpLogin === '2') {
|
||||
const code = url.searchParams.get('code');
|
||||
// 推送登录成功状态到扫码端
|
||||
const res2 = await query.post({
|
||||
path: 'wx',
|
||||
key: 'mplogin',
|
||||
state,
|
||||
code
|
||||
})
|
||||
if (res2.code === 200) {
|
||||
message.success('登录成功');
|
||||
} else {
|
||||
message.error(res2.message || '登录失败');
|
||||
}
|
||||
closePage();
|
||||
}
|
||||
}
|
||||
const isWechat = () => {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
return /micromessenger/i.test(ua);
|
||||
};
|
||||
|
||||
const closePage = (time = 2000) => {
|
||||
if (!isWechat()) {
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, time);
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (window.WeixinJSBridge) {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
window.WeixinJSBridge.call('closeWindow');
|
||||
}, time);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, time);
|
||||
}
|
||||
};
|
||||
const checkMpWechatInWx = async () => {
|
||||
const wxAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect`
|
||||
const appid = WX_MP_APP_ID;
|
||||
const url = new URL(window.location.href);
|
||||
const originState = url.searchParams.get('state');
|
||||
let [mpLogin, state] = originState ? originState.split('-') : [null, null];
|
||||
|
||||
const redirectURL = new URL(url.pathname, url.origin);
|
||||
state = '2-' + state; // 标记为第二步登录
|
||||
const redirect_uri = encodeURIComponent(redirectURL.toString())
|
||||
document.body.innerHTML = `<p>正在准备跳转到微信公众号授权页面...</p>`;
|
||||
const scope = `snsapi_userinfo`
|
||||
if (!state) {
|
||||
alert('Invalid state. Please try again later.');
|
||||
closePage();
|
||||
return;
|
||||
}
|
||||
const link = wxAuthUrl.replace('APPID', appid).replace('REDIRECT_URI', redirect_uri).replace('SCOPE', scope).replace('STATE', state);
|
||||
setTimeout(() => {
|
||||
window.location.href = link;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
checkMpWechat();
|
||||
}, 100);
|
||||
10
packages/kv-login/src/modules/query.ts
Normal file
10
packages/kv-login/src/modules/query.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Query } from '@kevisual/query'
|
||||
import { QueryLoginBrowser } from '@kevisual/query-login';
|
||||
|
||||
|
||||
export const queryBase = new Query()
|
||||
|
||||
export const query = new QueryLoginBrowser({
|
||||
query: queryBase,
|
||||
})
|
||||
|
||||
57
packages/kv-login/src/modules/wx-mp/qr.ts
Normal file
57
packages/kv-login/src/modules/wx-mp/qr.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import QRCode, { QRCodeToDataURLOptions } from 'qrcode';
|
||||
import { redirectHome } from '../login-handle.ts';
|
||||
import { query } from '../query.ts';
|
||||
export const useCreateLoginQRCode = (el?: HTMLCanvasElement) => {
|
||||
var opts: QRCodeToDataURLOptions = {
|
||||
errorCorrectionLevel: 'H',
|
||||
type: 'image/jpeg',
|
||||
margin: 1,
|
||||
width: 300,
|
||||
};
|
||||
let timer: any = null;
|
||||
const createQrcode = async (state: string) => {
|
||||
const url = new URL(window.location.href);
|
||||
const loginUrl = new URL(url.pathname, url.origin);
|
||||
loginUrl.searchParams.set('state', '1-' + state);
|
||||
console.log('生成登录二维码链接:', loginUrl.toString());
|
||||
var img = el || document.getElementById('qrcode')! as HTMLCanvasElement;
|
||||
const res = await QRCode.toDataURL(img!, loginUrl.toString(), opts);
|
||||
};
|
||||
const checkLogin = async (state: string) => {
|
||||
const res = await fetch(`/api/router?path=wx&key=checkLogin&state=${state}`).then((res) => res.json());
|
||||
if (res.code === 200) {
|
||||
console.log(res);
|
||||
const token = res.data;
|
||||
if (token) {
|
||||
localStorage.setItem('token', token.accessToken);
|
||||
await query.setLoginToken(token);
|
||||
}
|
||||
clear();
|
||||
setTimeout(() => {
|
||||
redirectHome();
|
||||
}, 1000);
|
||||
} else {
|
||||
timer = setTimeout(() => {
|
||||
checkLogin(state);
|
||||
console.log('继续检测登录状态');
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
// 随机生成一个state
|
||||
const state = Math.random().toString(36).substring(2, 15);
|
||||
createQrcode(state);
|
||||
checkLogin(state);
|
||||
const timer2 = setInterval(() => {
|
||||
const state = Math.random().toString(36).substring(2, 15);
|
||||
clearTimeout(timer); // 清除定时器
|
||||
createQrcode(state); // 90秒后更新二维码
|
||||
checkLogin(state);
|
||||
console.log('更新二维码');
|
||||
}, 90000);
|
||||
const clear = () => {
|
||||
clearTimeout(timer);
|
||||
clearInterval(timer2);
|
||||
console.log('停止检测登录状态');
|
||||
}
|
||||
return { createQrcode, clear };
|
||||
};
|
||||
21
packages/kv-login/src/modules/wx/load-js.ts
Normal file
21
packages/kv-login/src/modules/wx/load-js.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// <script src="https://turing.captcha.qcloud.com/TCaptcha.js"></script>
|
||||
|
||||
export const dynimicLoadTcapTcha = async (): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.type = 'text/javascript'
|
||||
script.id = 'tencent-captcha'
|
||||
if (document.getElementById('tencent-captcha')) {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
script.src = 'https://turing.captcha.qcloud.com/TCaptcha.js'
|
||||
script.onload = () => {
|
||||
resolve(true)
|
||||
}
|
||||
script.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
document.body.appendChild(script)
|
||||
})
|
||||
}
|
||||
70
packages/kv-login/src/modules/wx/tencent-captcha.ts
Normal file
70
packages/kv-login/src/modules/wx/tencent-captcha.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// 定义回调函数
|
||||
export function callback(res: any) {
|
||||
// 第一个参数传入回调结果,结果如下:
|
||||
// ret Int 验证结果,0:验证成功。2:用户主动关闭验证码。
|
||||
// ticket String 验证成功的票据,当且仅当 ret = 0 时 ticket 有值。
|
||||
// CaptchaAppId String 验证码应用ID。
|
||||
// bizState Any 自定义透传参数。
|
||||
// randstr String 本次验证的随机串,后续票据校验时需传递该参数。
|
||||
console.log('callback:', res);
|
||||
// res(用户主动关闭验证码)= {ret: 2, ticket: null}
|
||||
// res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}
|
||||
// res(请求验证码发生错误,验证码自动返回terror_前缀的容灾票据) = {ret: 0, ticket: "String", randstr: "String", errorCode: Number, errorMessage: "String"}
|
||||
// 此处代码仅为验证结果的展示示例,真实业务接入,建议基于ticket和errorCode情况做不同的业务处理
|
||||
if (res.ret === 0) {
|
||||
// 复制结果至剪切板
|
||||
var str = '【randstr】->【' + res.randstr + '】 【ticket】->【' + res.ticket + '】';
|
||||
var ipt = document.createElement('input');
|
||||
ipt.value = str;
|
||||
document.body.appendChild(ipt);
|
||||
ipt.select();
|
||||
document.body.removeChild(ipt);
|
||||
alert('1. 返回结果(randstr、ticket)已复制到剪切板,ctrl+v 查看。 2. 打开浏览器控制台,查看完整返回结果。');
|
||||
}
|
||||
}
|
||||
export type TencentCaptcha = {
|
||||
actionDuration?: number;
|
||||
appid?: string;
|
||||
bizState?: any;
|
||||
randstr?: string;
|
||||
ret: number;
|
||||
sid?: string;
|
||||
ticket?: string;
|
||||
errorCode?: number;
|
||||
errorMessage?: string;
|
||||
verifyDuration?: number;
|
||||
};
|
||||
// 定义验证码触发事件
|
||||
export const checkCaptcha = (captchaAppId: string): Promise<TencentCaptcha> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (res: TencentCaptcha) => {
|
||||
console.log('callback:', res);
|
||||
if (res.ret === 0) {
|
||||
resolve(res);
|
||||
} else {
|
||||
reject(res);
|
||||
}
|
||||
};
|
||||
const appid = captchaAppId;
|
||||
try {
|
||||
// 生成一个验证码对象
|
||||
// CaptchaAppId:登录验证码控制台,从【验证管理】页面进行查看。如果未创建过验证,请先新建验证。注意:不可使用客户端类型为小程序的CaptchaAppId,会导致数据统计错误。
|
||||
//callback:定义的回调函数
|
||||
// @ts-ignore
|
||||
var captcha = new TencentCaptcha(appid, callback, {});
|
||||
// 调用方法,显示验证码
|
||||
captcha.show();
|
||||
} catch (error) {
|
||||
// 加载异常,调用验证码js加载错误处理函数
|
||||
var ticket = 'terror_1001_' + appid + '_' + Math.floor(new Date().getTime() / 1000);
|
||||
// 生成容灾票据或自行做其它处理
|
||||
callback({
|
||||
ret: 0,
|
||||
randstr: '@' + Math.random().toString(36).substring(2),
|
||||
ticket: ticket,
|
||||
errorCode: 1001,
|
||||
errorMessage: 'jsload_error',
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
61
packages/kv-login/src/modules/wx/ws-login.ts
Normal file
61
packages/kv-login/src/modules/wx/ws-login.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
type WxLoginConfig = {
|
||||
redirect_uri?: string;
|
||||
appid?: string;
|
||||
scope?: string;
|
||||
state?: string;
|
||||
style?: string;
|
||||
};
|
||||
export const createLogin = async (config?: WxLoginConfig) => {
|
||||
let redirect_uri = config?.redirect_uri;
|
||||
const { appid } = config || {};
|
||||
if (!redirect_uri) {
|
||||
redirect_uri = window.location.href;
|
||||
}
|
||||
const url = new URL(redirect_uri); // remove code and state params
|
||||
url.searchParams.delete('code');
|
||||
url.searchParams.delete('state');
|
||||
redirect_uri = url.toString();
|
||||
|
||||
console.log('redirect_uri', redirect_uri);
|
||||
if (!appid) {
|
||||
console.error('appid is not cant be empty');
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
const obj = new WxLogin({
|
||||
self_redirect: false,
|
||||
id: 'weixinLogin', // 需要显示的容器id
|
||||
appid: appid, // 微信开放平台appid wx*******
|
||||
scope: 'snsapi_login', // 网页默认即可 snsapi_userinfo
|
||||
redirect_uri: encodeURIComponent(redirect_uri), // 授权成功后回调的url
|
||||
state: Math.ceil(Math.random() * 1000), // 可设置为简单的随机数加session用来校验
|
||||
stylelite: true, // 是否使用简洁模式
|
||||
// https://juejin.cn/post/6982473580063752223
|
||||
href: "data:text/css;base64,LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDIwMHB4O30NCi5pbXBvd2VyQm94IC50aXRsZSB7ZGlzcGxheTogbm9uZTt9DQouaW1wb3dlckJveCAuaW5mbyB7d2lkdGg6IDIwMHB4O30NCi5zdGF0dXNfaWNvbiB7ZGlzcGxheTogbm9uZX0NCi5pbXBvd2VyQm94IC5zdGF0dXMge3RleHQtYWxpZ246IGNlbnRlcjt9"
|
||||
});
|
||||
const login = document.querySelector('#weixinLogin')
|
||||
if (login) {
|
||||
// login 下的 iframe 样式调整
|
||||
const iframe = login.querySelector('iframe');
|
||||
if (iframe) {
|
||||
// iframe.style.width = '200px';
|
||||
iframe.style.height = '300px';
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
export const wxId = 'weixinLogin';
|
||||
export function setWxerwma(config?: WxLoginConfig) {
|
||||
const s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
s.src = '//res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js';
|
||||
s.id = 'weixinLogin-js';
|
||||
if (document.getElementById('weixinLogin-js')) {
|
||||
createLogin(config);
|
||||
return;
|
||||
}
|
||||
const wxElement = document.body.appendChild(s);
|
||||
wxElement.onload = function () {
|
||||
createLogin(config);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user