update
This commit is contained in:
434
query/query-login/query-login.ts
Normal file
434
query/query-login/query-login.ts
Normal file
@@ -0,0 +1,434 @@
|
||||
import { Query, BaseQuery } from '@kevisual/query';
|
||||
import type { Result, DataOpts } from '@kevisual/query/query';
|
||||
import { setBaseResponse } from '@kevisual/query/query';
|
||||
import { LoginCacheStore, CacheStore } from './login-cache.ts';
|
||||
import { Cache } from './login-cache.ts';
|
||||
|
||||
export type QueryLoginOpts = {
|
||||
query?: Query;
|
||||
isBrowser?: boolean;
|
||||
onLoad?: () => void;
|
||||
storage?: Storage;
|
||||
cache: Cache;
|
||||
};
|
||||
export type QueryLoginData = {
|
||||
username?: string;
|
||||
password: string;
|
||||
email?: string;
|
||||
};
|
||||
export type QueryLoginResult = {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
export class QueryLogin extends BaseQuery {
|
||||
/**
|
||||
* query login cache, 非实际操作, 一个cache的包裹模块
|
||||
*/
|
||||
cacheStore: CacheStore;
|
||||
isBrowser: boolean;
|
||||
load?: boolean;
|
||||
storage: Storage;
|
||||
onLoad?: () => void;
|
||||
|
||||
constructor(opts?: QueryLoginOpts) {
|
||||
super({
|
||||
query: opts?.query || new Query(),
|
||||
});
|
||||
this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts?.cache! });
|
||||
this.isBrowser = opts?.isBrowser ?? true;
|
||||
this.init();
|
||||
this.onLoad = opts?.onLoad;
|
||||
this.storage = opts?.storage || localStorage;
|
||||
}
|
||||
setQuery(query: Query) {
|
||||
this.query = query;
|
||||
}
|
||||
private async init() {
|
||||
await this.cacheStore.init();
|
||||
this.load = true;
|
||||
this.onLoad?.();
|
||||
}
|
||||
async post<T = any>(data: any, opts?: DataOpts) {
|
||||
try {
|
||||
return this.query.post<T>({ path: 'user', ...data }, opts);
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
return {
|
||||
code: 400,
|
||||
} as any;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 登录,
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
async login(data: QueryLoginData) {
|
||||
const res = await this.post<QueryLoginResult>({ key: 'login', ...data });
|
||||
if (res.code === 200) {
|
||||
const { accessToken, refreshToken } = res?.data || {};
|
||||
this.storage.setItem('token', accessToken || '');
|
||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* 手机号登录
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
async loginByCode(data: { phone: string; code: string }) {
|
||||
const res = await this.post<QueryLoginResult>({ path: 'sms', key: 'login', data });
|
||||
if (res.code === 200) {
|
||||
const { accessToken, refreshToken } = res?.data || {};
|
||||
this.storage.setItem('token', accessToken || '');
|
||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* 设置token
|
||||
* @param token
|
||||
*/
|
||||
async setLoginToken(token: { accessToken: string; refreshToken: string }) {
|
||||
const { accessToken, refreshToken } = token;
|
||||
this.storage.setItem('token', accessToken || '');
|
||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
||||
}
|
||||
async loginByWechat(data: { code: string }) {
|
||||
const res = await this.post<QueryLoginResult>({ path: 'wx', key: 'open-login', code: data.code });
|
||||
if (res.code === 200) {
|
||||
const { accessToken, refreshToken } = res?.data || {};
|
||||
this.storage.setItem('token', accessToken || '');
|
||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* 检测微信登录,登陆成功后,调用onSuccess,否则调用onError
|
||||
* @param param0
|
||||
*/
|
||||
async checkWechat({ onSuccess, onError }: { onSuccess?: (res: QueryLoginResult) => void; onError?: (res: any) => void }) {
|
||||
const url = new URL(window.location.href);
|
||||
const code = url.searchParams.get('code');
|
||||
const state = url.searchParams.get('state');
|
||||
if (code && state) {
|
||||
const res = await this.loginByWechat({ code });
|
||||
if (res.code === 200) {
|
||||
onSuccess?.(res.data);
|
||||
} else {
|
||||
onError?.(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 登陆成功,需要获取用户信息进行缓存
|
||||
* @param param0
|
||||
*/
|
||||
async beforeSetLoginUser({ accessToken, refreshToken, check401 }: { accessToken?: string; refreshToken?: string; check401?: boolean }) {
|
||||
if (accessToken && refreshToken) {
|
||||
const resUser = await this.getMe(accessToken, check401);
|
||||
if (resUser.code === 200) {
|
||||
const user = resUser.data;
|
||||
if (user) {
|
||||
this.cacheStore.setLoginUser({
|
||||
user,
|
||||
id: user.id,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
});
|
||||
} else {
|
||||
console.error('登录失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 刷新token
|
||||
* @param refreshToken
|
||||
* @returns
|
||||
*/
|
||||
async queryRefreshToken(refreshToken?: string) {
|
||||
const _refreshToken = refreshToken || this.cacheStore.getRefreshToken();
|
||||
let data = { refreshToken: _refreshToken };
|
||||
if (!_refreshToken) {
|
||||
await this.cacheStore.clearCurrentUser();
|
||||
return {
|
||||
code: 401,
|
||||
message: '请先登录',
|
||||
data: {} as any,
|
||||
};
|
||||
}
|
||||
return this.post(
|
||||
{ key: 'refreshToken', data },
|
||||
{
|
||||
afterResponse: async (response, ctx) => {
|
||||
setBaseResponse(response);
|
||||
return response as any;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 检查401错误,并刷新token, 如果refreshToken存在,则刷新token, 否则返回401
|
||||
* 拦截请求,请使用run401Action, 不要直接使用 afterCheck401ToRefreshToken
|
||||
* @param response
|
||||
* @param ctx
|
||||
* @param refetch
|
||||
* @returns
|
||||
*/
|
||||
async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) {
|
||||
const that = this;
|
||||
if (response?.code === 401) {
|
||||
const hasRefreshToken = await that.cacheStore.getRefreshToken();
|
||||
if (hasRefreshToken) {
|
||||
const res = await that.queryRefreshToken(hasRefreshToken);
|
||||
if (res.code === 200) {
|
||||
const { accessToken, refreshToken } = res?.data || {};
|
||||
that.storage.setItem('token', accessToken || '');
|
||||
await that.beforeSetLoginUser({ accessToken, refreshToken, check401: false });
|
||||
if (refetch && ctx && ctx.req && ctx.req.url && ctx.fetch) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
const url = ctx.req?.url;
|
||||
const body = ctx.req?.body;
|
||||
const headers = ctx.req?.headers;
|
||||
const res = await ctx.fetch(url, {
|
||||
method: 'POST',
|
||||
body: body,
|
||||
headers: { ...headers, Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
setBaseResponse(res);
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
that.storage.removeItem('token');
|
||||
await that.cacheStore.clearCurrentUser();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return response as any;
|
||||
}
|
||||
/**
|
||||
* 一个简单的401处理, 如果401,则刷新token, 如果refreshToken不存在,则返回401
|
||||
* refetch 是否重新请求, 会有bug,无限循环,按需要使用
|
||||
* TODO:
|
||||
* @param response
|
||||
* @param ctx
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
async run401Action(
|
||||
response: Result,
|
||||
ctx?: { req?: any; res?: any; fetch?: any },
|
||||
opts?: {
|
||||
/**
|
||||
* 是否重新请求, 会有bug,无限循环,按需要使用
|
||||
*/
|
||||
refetch?: boolean;
|
||||
/**
|
||||
* check之后的回调
|
||||
*/
|
||||
afterCheck?: (res: Result) => any;
|
||||
/**
|
||||
* 401处理后, 还是401, 则回调
|
||||
*/
|
||||
afterAlso401?: (res: Result) => any;
|
||||
},
|
||||
) {
|
||||
const that = this;
|
||||
const refetch = opts?.refetch ?? false;
|
||||
if (response?.code === 401) {
|
||||
if (that.query.stop === true) {
|
||||
return { code: 500, success: false, message: 'refresh token loading...' };
|
||||
}
|
||||
that.query.stop = true;
|
||||
const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch);
|
||||
that.query.stop = false;
|
||||
opts?.afterCheck?.(res);
|
||||
if (res.code === 401) {
|
||||
opts?.afterAlso401?.(res);
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return response as any;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param token
|
||||
* @returns
|
||||
*/
|
||||
async getMe(token?: string, check401: boolean = true) {
|
||||
const _token = token || this.storage.getItem('token');
|
||||
const that = this;
|
||||
return that.post(
|
||||
{ key: 'me' },
|
||||
{
|
||||
beforeRequest: async (config) => {
|
||||
if (config.headers) {
|
||||
config.headers['Authorization'] = `Bearer ${_token}`;
|
||||
}
|
||||
if (!_token) {
|
||||
return false;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
afterResponse: async (response, ctx) => {
|
||||
if (response?.code === 401 && check401 && !token) {
|
||||
return await that.afterCheck401ToRefreshToken(response, ctx);
|
||||
}
|
||||
return response as any;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 检查本地用户,如果本地用户存在,则返回本地用户,否则返回null
|
||||
* @returns
|
||||
*/
|
||||
async checkLocalUser() {
|
||||
const user = await this.cacheStore.getCurrentUser();
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 检查本地token是否存在,简单的判断是否已经属于登陆状态
|
||||
* @returns
|
||||
*/
|
||||
async checkLocalToken() {
|
||||
const token = this.storage.getItem('token');
|
||||
return !!token;
|
||||
}
|
||||
/**
|
||||
* 检查本地用户列表
|
||||
* @returns
|
||||
*/
|
||||
async getToken() {
|
||||
const token = this.storage.getItem('token');
|
||||
return token || '';
|
||||
}
|
||||
async beforeRequest(opts: any = {}) {
|
||||
const token = this.storage.getItem('token');
|
||||
if (token) {
|
||||
opts.headers = { ...opts.headers, Authorization: `Bearer ${token}` };
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
/**
|
||||
* 请求更新,切换用户, 使用switchUser
|
||||
* @param username
|
||||
* @returns
|
||||
*/
|
||||
private async postSwitchUser(username: string) {
|
||||
return this.post({ key: 'switchCheck', data: { username } });
|
||||
}
|
||||
/**
|
||||
* 切换用户
|
||||
* @param username
|
||||
* @returns
|
||||
*/
|
||||
async switchUser(username: string) {
|
||||
const localUserList = await this.cacheStore.getCurrentUserList();
|
||||
const user = localUserList.find((userItem) => userItem.user!.username === username);
|
||||
if (user) {
|
||||
this.storage.setItem('token', user.accessToken || '');
|
||||
await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken });
|
||||
return {
|
||||
code: 200,
|
||||
data: {
|
||||
accessToken: user.accessToken,
|
||||
refreshToken: user.refreshToken,
|
||||
},
|
||||
success: true,
|
||||
message: '切换用户成功',
|
||||
};
|
||||
}
|
||||
const res = await this.postSwitchUser(username);
|
||||
|
||||
if (res.code === 200) {
|
||||
const { accessToken, refreshToken } = res?.data || {};
|
||||
this.storage.setItem('token', accessToken || '');
|
||||
await this.beforeSetLoginUser({ accessToken, refreshToken });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* 退出登陆,去掉token, 并删除缓存
|
||||
* @returns
|
||||
*/
|
||||
async logout() {
|
||||
this.storage.removeItem('token');
|
||||
const users = await this.cacheStore.getCurrentUserList();
|
||||
const tokens = users
|
||||
.map((user) => {
|
||||
return user?.accessToken;
|
||||
})
|
||||
.filter(Boolean);
|
||||
this.cacheStore.delValue();
|
||||
return this.post<Result>({ key: 'logout', data: { tokens } });
|
||||
}
|
||||
/**
|
||||
* 检查用户名的组,这个用户是否存在
|
||||
* @param username
|
||||
* @returns
|
||||
*/
|
||||
async hasUser(username: string) {
|
||||
const that = this;
|
||||
return this.post<Result>(
|
||||
{
|
||||
path: 'org',
|
||||
key: 'hasUser',
|
||||
data: {
|
||||
username,
|
||||
},
|
||||
},
|
||||
{
|
||||
afterResponse: async (response, ctx) => {
|
||||
if (response?.code === 401) {
|
||||
const res = await that.afterCheck401ToRefreshToken(response, ctx, true);
|
||||
return res;
|
||||
}
|
||||
return response as any;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 检查登录状态
|
||||
* @param token
|
||||
* @returns
|
||||
*/
|
||||
async checkLoginStatus(token: string) {
|
||||
const res = await this.post({
|
||||
path: 'user',
|
||||
key: 'checkLoginStatus',
|
||||
loginToken: token,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
const accessToken = res.data?.accessToken;
|
||||
this.storage.setItem('token', accessToken || '');
|
||||
await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken });
|
||||
return res;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 使用web登录,创建url地址, 需要MD5和jsonwebtoken
|
||||
*/
|
||||
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
|
||||
const randomId = Math.random().toString(36).substring(2, 15);
|
||||
const timestamp = Date.now();
|
||||
const tokenSecret = 'xiao' + randomId;
|
||||
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
||||
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
||||
// 10分钟过期
|
||||
expiresIn: 60 * 10, // 10分钟
|
||||
});
|
||||
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
|
||||
return { url, token, tokenSecret };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user