init
This commit is contained in:
58
src/service/api/auth.ts
Normal file
58
src/service/api/auth.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { request } from '../request';
|
||||
|
||||
/**
|
||||
* Login
|
||||
*
|
||||
* @param userName User name
|
||||
* @param password Password
|
||||
*/
|
||||
export function fetchLogin(username: string, password: string) {
|
||||
return request<Api.Auth.LoginToken>({
|
||||
url: '/auth/sign-in/username',
|
||||
method: 'post',
|
||||
data: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchSendPhoneNumberOtp(phoneNumber: string) {
|
||||
return request<string>({
|
||||
url: '/auth/phone-number/send-otp',
|
||||
method: 'post',
|
||||
data: {
|
||||
phoneNumber
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Get user info */
|
||||
export function fetchGetUserInfo() {
|
||||
return request<Api.Auth.UserInfo>({ url: '/auth/account-info' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token
|
||||
*
|
||||
* @param refreshToken Refresh token
|
||||
*/
|
||||
export function fetchRefreshToken(refreshToken: string) {
|
||||
return request<Api.Auth.LoginToken>({
|
||||
url: '/auth/refresh-token',
|
||||
method: 'post',
|
||||
data: {
|
||||
refreshToken
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* return custom backend error
|
||||
*
|
||||
* @param code error code
|
||||
* @param msg error message
|
||||
*/
|
||||
export function fetchCustomBackendError(code: string, msg: string) {
|
||||
return request({ url: '/auth/error', params: { code, msg } });
|
||||
}
|
||||
50
src/service/api/client.ts
Normal file
50
src/service/api/client.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { ref } from 'vue';
|
||||
import { treaty } from '@elysiajs/eden';
|
||||
import type { App } from '@riwa/api-types';
|
||||
import { getServiceBaseURL } from '@/utils/service';
|
||||
import { localStg } from '@/utils/storage';
|
||||
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
const token = localStg.get('token');
|
||||
|
||||
const client = treaty<App>(baseURL, {
|
||||
headers: {
|
||||
Authorization: token || ''
|
||||
}
|
||||
});
|
||||
|
||||
async function safeClient<T, E>(
|
||||
requestFactory: () => Promise<{ data: T; error: E }>,
|
||||
options: { silent?: boolean; immediate?: boolean } = {}
|
||||
) {
|
||||
const { immediate = true } = options;
|
||||
const data = ref<T>();
|
||||
const error = ref<E>();
|
||||
|
||||
const executeRequest = async () => {
|
||||
const res = await requestFactory();
|
||||
|
||||
if (res.error) {
|
||||
if (!options.silent) {
|
||||
window.$message?.error('An error occurred while processing your request.');
|
||||
}
|
||||
|
||||
throw res.error;
|
||||
}
|
||||
data.value = res.data;
|
||||
error.value = res.error;
|
||||
};
|
||||
|
||||
if (immediate) {
|
||||
await executeRequest();
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
refresh: executeRequest
|
||||
};
|
||||
}
|
||||
|
||||
export { client, safeClient };
|
||||
3
src/service/api/index.ts
Normal file
3
src/service/api/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './route';
|
||||
export * from './client';
|
||||
20
src/service/api/route.ts
Normal file
20
src/service/api/route.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { request } from '../request';
|
||||
|
||||
/** get constant routes */
|
||||
export function fetchGetConstantRoutes() {
|
||||
return request<Api.Route.MenuRoute[]>({ url: '/route/getConstantRoutes' });
|
||||
}
|
||||
|
||||
/** get user routes */
|
||||
export function fetchGetUserRoutes() {
|
||||
return request<Api.Route.UserRoute>({ url: '/route/getUserRoutes' });
|
||||
}
|
||||
|
||||
/**
|
||||
* whether the route is exist
|
||||
*
|
||||
* @param routeName route name
|
||||
*/
|
||||
export function fetchIsRouteExist(routeName: string) {
|
||||
return request<boolean>({ url: '/route/isRouteExist', params: { routeName } });
|
||||
}
|
||||
133
src/service/request/index.ts
Normal file
133
src/service/request/index.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { getServiceBaseURL } from '@/utils/service';
|
||||
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
|
||||
import type { RequestInstanceState } from './type';
|
||||
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
|
||||
export const request = createFlatRequest(
|
||||
{
|
||||
baseURL: `${baseURL}/api`
|
||||
},
|
||||
{
|
||||
defaultState: {
|
||||
errMsgStack: [],
|
||||
refreshTokenPromise: null
|
||||
} as RequestInstanceState,
|
||||
transform(response: AxiosResponse<App.Service.Response<any>>) {
|
||||
return response.data.data;
|
||||
},
|
||||
async onRequest(config) {
|
||||
const Authorization = getAuthorization();
|
||||
Object.assign(config.headers, { Authorization });
|
||||
|
||||
return config;
|
||||
},
|
||||
isBackendSuccess(response) {
|
||||
// when the backend response code is "0000"(default), it means the request is success
|
||||
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
|
||||
return response.status === 200;
|
||||
},
|
||||
async onBackendFail(response, instance) {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
function handleLogout() {
|
||||
authStore.resetStore();
|
||||
}
|
||||
|
||||
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
|
||||
if (response.status === 401) {
|
||||
handleLogout();
|
||||
return null;
|
||||
}
|
||||
|
||||
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
|
||||
// const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||
// if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
|
||||
// request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
|
||||
|
||||
// // prevent the user from refreshing the page
|
||||
// window.addEventListener('beforeunload', handleLogout);
|
||||
|
||||
// window.$dialog?.error({
|
||||
// title: $t('common.error'),
|
||||
// content: response.data.msg,
|
||||
// positiveText: $t('common.confirm'),
|
||||
// maskClosable: false,
|
||||
// closeOnEsc: false,
|
||||
// onPositiveClick() {
|
||||
// logoutAndCleanup();
|
||||
// },
|
||||
// onClose() {
|
||||
// logoutAndCleanup();
|
||||
// }
|
||||
// });
|
||||
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
|
||||
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
|
||||
if (response.status === 403) {
|
||||
const success = await handleExpiredRequest(request.state);
|
||||
if (success) {
|
||||
const Authorization = getAuthorization();
|
||||
Object.assign(response.config.headers, { Authorization });
|
||||
|
||||
return instance.request(response.config) as Promise<AxiosResponse>;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
onError() {
|
||||
showErrorMsg(request.state, 'backend error');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const demoRequest = createRequest(
|
||||
{
|
||||
baseURL: otherBaseURL.demo
|
||||
},
|
||||
{
|
||||
transform(response: AxiosResponse<App.Service.DemoResponse>) {
|
||||
return response.data.result;
|
||||
},
|
||||
async onRequest(config) {
|
||||
const { headers } = config;
|
||||
|
||||
// set token
|
||||
const token = localStg.get('token');
|
||||
const Authorization = token ? `Bearer ${token}` : null;
|
||||
Object.assign(headers, { Authorization });
|
||||
|
||||
return config;
|
||||
},
|
||||
isBackendSuccess(response) {
|
||||
// when the backend response code is "200", it means the request is success
|
||||
// you can change this logic by yourself
|
||||
return response.data.status === '200';
|
||||
},
|
||||
async onBackendFail(_response) {
|
||||
// when the backend response code is not "200", it means the request is fail
|
||||
// for example: the token is expired, refresh token and retry request
|
||||
},
|
||||
onError(error) {
|
||||
// when the request is fail, you can show error message
|
||||
|
||||
let message = error.message;
|
||||
|
||||
// show backend error message
|
||||
if (error.code === BACKEND_ERROR_CODE) {
|
||||
message = error.response?.data?.message || message;
|
||||
}
|
||||
|
||||
window.$message?.error(message);
|
||||
}
|
||||
}
|
||||
);
|
||||
64
src/service/request/shared.ts
Normal file
64
src/service/request/shared.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { fetchRefreshToken } from '../api';
|
||||
import type { RequestInstanceState } from './type';
|
||||
|
||||
export function getAuthorization() {
|
||||
const token = localStg.get('token');
|
||||
const Authorization = token ? `Bearer ${token}` : null;
|
||||
|
||||
return Authorization;
|
||||
}
|
||||
|
||||
/** refresh token */
|
||||
async function handleRefreshToken() {
|
||||
const { resetStore } = useAuthStore();
|
||||
|
||||
const rToken = localStg.get('refreshToken') || '';
|
||||
const { error, data } = await fetchRefreshToken(rToken);
|
||||
if (!error) {
|
||||
localStg.set('token', data.token);
|
||||
localStg.set('refreshToken', data.refreshToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
resetStore();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function handleExpiredRequest(state: RequestInstanceState) {
|
||||
if (!state.refreshTokenPromise) {
|
||||
state.refreshTokenPromise = handleRefreshToken();
|
||||
}
|
||||
|
||||
const success = await state.refreshTokenPromise;
|
||||
|
||||
setTimeout(() => {
|
||||
state.refreshTokenPromise = null;
|
||||
}, 1000);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
export function showErrorMsg(state: RequestInstanceState, message: string) {
|
||||
if (!state.errMsgStack?.length) {
|
||||
state.errMsgStack = [];
|
||||
}
|
||||
|
||||
const isExist = state.errMsgStack.includes(message);
|
||||
|
||||
if (!isExist) {
|
||||
state.errMsgStack.push(message);
|
||||
|
||||
window.$message?.error(message, {
|
||||
onLeave: () => {
|
||||
state.errMsgStack = state.errMsgStack.filter(msg => msg !== message);
|
||||
|
||||
setTimeout(() => {
|
||||
state.errMsgStack = [];
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
7
src/service/request/type.ts
Normal file
7
src/service/request/type.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface RequestInstanceState {
|
||||
/** the promise of refreshing token */
|
||||
refreshTokenPromise: Promise<boolean> | null;
|
||||
/** the request error message stack */
|
||||
errMsgStack: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
Reference in New Issue
Block a user