chore: init read-star monorepo
This commit is contained in:
48
apps/admin/src/service/api/auth.ts
Normal file
48
apps/admin/src/service/api/auth.ts
Normal file
@ -0,0 +1,48 @@
|
||||
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/login',
|
||||
method: 'post',
|
||||
data: {
|
||||
userName,
|
||||
password,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** Get user info */
|
||||
export function fetchGetUserInfo() {
|
||||
return request<Api.Auth.UserInfo>({ url: '/auth/getUserInfo' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token
|
||||
*
|
||||
* @param refreshToken Refresh token
|
||||
*/
|
||||
export function fetchRefreshToken(refreshToken: string) {
|
||||
return request<Api.Auth.LoginToken>({
|
||||
url: '/auth/refreshToken',
|
||||
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 } })
|
||||
}
|
||||
2
apps/admin/src/service/api/index.ts
Normal file
2
apps/admin/src/service/api/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './auth'
|
||||
export * from './route'
|
||||
20
apps/admin/src/service/api/route.ts
Normal file
20
apps/admin/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 } })
|
||||
}
|
||||
170
apps/admin/src/service/request/index.ts
Normal file
170
apps/admin/src/service/request/index.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
import type { RequestInstanceState } from './type'
|
||||
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'
|
||||
import { $t } from '@/locales'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { getServiceBaseURL } from '@/utils/service'
|
||||
import { localStg } from '@/utils/storage'
|
||||
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared'
|
||||
|
||||
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,
|
||||
headers: {
|
||||
apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2',
|
||||
},
|
||||
},
|
||||
{
|
||||
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 String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE
|
||||
},
|
||||
async onBackendFail(response, instance) {
|
||||
const authStore = useAuthStore()
|
||||
const responseCode = String(response.data.code)
|
||||
|
||||
function handleLogout() {
|
||||
authStore.resetStore()
|
||||
}
|
||||
|
||||
function logoutAndCleanup() {
|
||||
handleLogout()
|
||||
window.removeEventListener('beforeunload', handleLogout)
|
||||
|
||||
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg)
|
||||
}
|
||||
|
||||
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
|
||||
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []
|
||||
if (logoutCodes.includes(responseCode)) {
|
||||
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`
|
||||
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []
|
||||
if (expiredTokenCodes.includes(responseCode)) {
|
||||
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(error) {
|
||||
// when the request is fail, you can show error message
|
||||
|
||||
let message = error.message
|
||||
let backendErrorCode = ''
|
||||
|
||||
// get backend error message and code
|
||||
if (error.code === BACKEND_ERROR_CODE) {
|
||||
message = error.response?.data?.msg || message
|
||||
backendErrorCode = String(error.response?.data?.code || '')
|
||||
}
|
||||
|
||||
// the error message is displayed in the modal
|
||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []
|
||||
if (modalLogoutCodes.includes(backendErrorCode)) {
|
||||
return
|
||||
}
|
||||
|
||||
// when the token is expired, refresh token and retry request, so no need to show error message
|
||||
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []
|
||||
if (expiredTokenCodes.includes(backendErrorCode)) {
|
||||
return
|
||||
}
|
||||
|
||||
showErrorMsg(request.state, message)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
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
apps/admin/src/service/request/shared.ts
Normal file
64
apps/admin/src/service/request/shared.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import type { RequestInstanceState } from './type'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { localStg } from '@/utils/storage'
|
||||
import { fetchRefreshToken } from '../api'
|
||||
|
||||
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
apps/admin/src/service/request/type.ts
Normal file
7
apps/admin/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