# Conflicts: # .gitignore # .husky/pre-commit # .vscode/extensions.json # .vscode/settings.json # README.ja-JP.md # README.md # README.zh-CN.md # apps/backend-mock/api/auth/codes.ts # apps/backend-mock/api/auth/login.post.ts # apps/backend-mock/api/auth/logout.post.ts # apps/backend-mock/api/auth/refresh.post.ts # apps/backend-mock/api/menu/all.ts # apps/backend-mock/api/status.ts # apps/backend-mock/api/table/list.ts # apps/backend-mock/api/test.get.ts # apps/backend-mock/api/test.post.ts # apps/backend-mock/api/user/info.ts # apps/backend-mock/middleware/1.api.ts # apps/backend-mock/nitro.config.ts # apps/backend-mock/package.json # apps/backend-mock/routes/[...].ts # apps/backend-mock/tsconfig.json # apps/backend-mock/utils/cookie-utils.ts # apps/backend-mock/utils/jwt-utils.ts # apps/backend-mock/utils/mock-data.ts # apps/backend-mock/utils/response.ts # apps/web-antd/.env # apps/web-antd/src/adapter/component/index.ts # apps/web-antd/src/adapter/form.ts # apps/web-antd/src/api/request.ts # apps/web-antd/src/layouts/auth.vue # apps/web-antd/src/layouts/basic.vue # apps/web-antd/src/preferences.ts # apps/web-antd/src/router/guard.ts # apps/web-antd/src/router/routes/modules/demos.ts # apps/web-antd/src/store/auth.ts # apps/web-antd/src/views/demos/antd/index.vue # apps/web-antdv-next/vite.config.ts # apps/web-ele/.env # apps/web-ele/index.html # apps/web-ele/package.json # apps/web-ele/src/adapter/component/index.ts # apps/web-ele/src/adapter/form.ts # apps/web-ele/src/adapter/vxe-table.ts # apps/web-ele/src/api/request.ts # apps/web-ele/src/bootstrap.ts # apps/web-ele/src/layouts/auth.vue # apps/web-ele/src/layouts/basic.vue # apps/web-ele/src/locales/index.ts # apps/web-ele/src/locales/langs/en-US/demos.json # apps/web-ele/src/locales/langs/en-US/page.json # apps/web-ele/src/locales/langs/zh-CN/demos.json # apps/web-ele/src/locales/langs/zh-CN/page.json # apps/web-ele/src/preferences.ts # apps/web-ele/src/router/guard.ts # apps/web-ele/src/router/routes/core.ts # apps/web-ele/src/router/routes/modules/dashboard.ts # apps/web-ele/src/router/routes/modules/demos.ts # apps/web-ele/src/router/routes/modules/vben.ts # apps/web-ele/src/store/auth.ts # apps/web-ele/src/views/_core/authentication/code-login.vue # apps/web-ele/src/views/_core/authentication/forget-password.vue # apps/web-ele/src/views/_core/authentication/register.vue # apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue # apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue # apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue # apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue # apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue # apps/web-ele/src/views/dashboard/analytics/index.vue # apps/web-ele/src/views/demos/element/index.vue # apps/web-ele/src/views/demos/form/basic.vue # apps/web-ele/tsconfig.json # apps/web-ele/tsconfig.node.json # apps/web-naive/.env # apps/web-naive/index.html # apps/web-naive/package.json # apps/web-naive/src/adapter/component/index.ts # apps/web-naive/src/adapter/form.ts # apps/web-naive/src/adapter/vxe-table.ts # apps/web-naive/src/api/request.ts # apps/web-naive/src/bootstrap.ts # apps/web-naive/src/layouts/auth.vue # apps/web-naive/src/layouts/basic.vue # apps/web-naive/src/locales/index.ts # apps/web-naive/src/locales/langs/en-US/demos.json # apps/web-naive/src/locales/langs/en-US/page.json # apps/web-naive/src/locales/langs/zh-CN/demos.json # apps/web-naive/src/locales/langs/zh-CN/page.json # apps/web-naive/src/preferences.ts # apps/web-naive/src/router/guard.ts # apps/web-naive/src/router/routes/core.ts # apps/web-naive/src/router/routes/modules/dashboard.ts # apps/web-naive/src/router/routes/modules/demos.ts # apps/web-naive/src/router/routes/modules/vben.ts # apps/web-naive/src/store/auth.ts # apps/web-naive/src/views/_core/authentication/code-login.vue # apps/web-naive/src/views/_core/authentication/forget-password.vue # apps/web-naive/src/views/_core/authentication/register.vue # apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue # apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue # apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue # apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue # apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue # apps/web-naive/src/views/dashboard/analytics/index.vue # apps/web-naive/src/views/demos/form/basic.vue # apps/web-naive/src/views/demos/naive/index.vue # apps/web-naive/tsconfig.json # apps/web-naive/tsconfig.node.json # apps/web-naive/vite.config.ts # docs/.vitepress/components/demo-preview.vue # docs/.vitepress/components/preview-group.vue # docs/.vitepress/config/en.mts # docs/.vitepress/config/index.mts # docs/.vitepress/config/plugins/demo-preview.ts # docs/.vitepress/config/shared.mts # docs/.vitepress/config/zh.mts # docs/.vitepress/theme/components/site-layout.vue # docs/.vitepress/theme/components/vben-contributors.vue # docs/.vitepress/theme/styles/variables.css # docs/package.json # docs/src/_env/adapter/component.ts # docs/src/_env/adapter/form.ts # docs/src/_env/adapter/vxe-table.ts # docs/src/commercial/community.md # docs/src/components/common-ui/vben-count-to-animator.md # docs/src/components/common-ui/vben-drawer.md # docs/src/components/common-ui/vben-form.md # docs/src/components/common-ui/vben-modal.md # docs/src/components/common-ui/vben-vxe-table.md # docs/src/components/layout-ui/page.md # docs/src/demos/vben-drawer/auto-height/drawer.vue # docs/src/demos/vben-drawer/basic/index.vue # docs/src/demos/vben-drawer/dynamic/index.vue # docs/src/demos/vben-drawer/shared-data/drawer.vue # docs/src/demos/vben-drawer/shared-data/index.vue # docs/src/demos/vben-form/api/index.vue # docs/src/demos/vben-form/basic/index.vue # docs/src/demos/vben-form/custom/index.vue # docs/src/demos/vben-form/dynamic/index.vue # docs/src/demos/vben-form/query/index.vue # docs/src/demos/vben-form/rules/index.vue # docs/src/demos/vben-modal/auto-height/modal.vue # docs/src/demos/vben-modal/basic/index.vue # docs/src/demos/vben-modal/dynamic/index.vue # docs/src/demos/vben-modal/shared-data/index.vue # docs/src/demos/vben-modal/shared-data/modal.vue # docs/src/demos/vben-vxe-table/basic/index.vue # docs/src/demos/vben-vxe-table/custom-cell/index.vue # docs/src/demos/vben-vxe-table/edit-row/index.vue # docs/src/demos/vben-vxe-table/fixed/index.vue # docs/src/demos/vben-vxe-table/form/index.vue # docs/src/demos/vben-vxe-table/remote/index.vue # docs/src/demos/vben-vxe-table/tree/index.vue # docs/src/demos/vben-vxe-table/virtual/index.vue # docs/src/en/guide/essentials/development.md # docs/src/en/guide/essentials/external-module.md # docs/src/en/guide/essentials/route.md # docs/src/en/guide/essentials/server.md # docs/src/en/guide/essentials/settings.md # docs/src/en/guide/in-depth/access.md # docs/src/en/guide/in-depth/loading.md # docs/src/en/guide/in-depth/locale.md # docs/src/en/guide/in-depth/theme.md # docs/src/en/guide/introduction/quick-start.md # docs/src/en/guide/introduction/thin.md # docs/src/en/guide/introduction/vben.md # docs/src/en/guide/other/faq.md # docs/src/en/guide/project/dir.md # docs/src/en/guide/project/standard.md # docs/src/en/guide/project/tailwindcss.md # docs/src/en/index.md # docs/src/friend-links/index.md # docs/src/guide/essentials/build.md # docs/src/guide/essentials/development.md # docs/src/guide/essentials/external-module.md # docs/src/guide/essentials/route.md # docs/src/guide/essentials/server.md # docs/src/guide/essentials/settings.md # docs/src/guide/in-depth/access.md # docs/src/guide/in-depth/check-updates.md # docs/src/guide/in-depth/loading.md # docs/src/guide/in-depth/locale.md # docs/src/guide/in-depth/theme.md # docs/src/guide/introduction/quick-start.md # docs/src/guide/introduction/thin.md # docs/src/guide/introduction/vben.md # docs/src/guide/introduction/why.md # docs/src/guide/other/faq.md # docs/src/guide/project/dir.md # docs/src/guide/project/standard.md # docs/src/guide/project/tailwindcss.md # docs/src/index.md # docs/tsconfig.json # package.json # packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue # packages/constants/src/core.ts # packages/effects/common-ui/src/components/index.ts # packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue # packages/effects/common-ui/src/ui/dashboard/workbench/workbench-header.vue # packages/effects/common-ui/src/ui/dashboard/workbench/workbench-project.vue # packages/effects/layouts/src/authentication/authentication.vue # packages/effects/layouts/src/basic/layout.vue # packages/effects/layouts/src/widgets/notification/notification.vue # packages/effects/layouts/src/widgets/notification/types.ts # packages/icons/src/iconify/index.ts # packages/icons/src/svg/index.ts # packages/stores/src/modules/user.ts # playground/src/adapter/component/index.ts # playground/src/store/auth.ts # pnpm-lock.yaml # vben-admin.code-workspace
278 lines
7.7 KiB
TypeScript
278 lines
7.7 KiB
TypeScript
/**
|
||
* 该文件可自行根据业务逻辑进行调整
|
||
*/
|
||
import type { RequestClientOptions } from '@vben/request';
|
||
|
||
import { useAppConfig } from '@vben/hooks';
|
||
import { preferences } from '@vben/preferences';
|
||
import {
|
||
authenticateResponseInterceptor,
|
||
defaultResponseInterceptor,
|
||
errorMessageResponseInterceptor,
|
||
RequestClient,
|
||
} from '@vben/request';
|
||
import { useAccessStore } from '@vben/stores';
|
||
|
||
import { message, Modal } from 'ant-design-vue';
|
||
// eslint-disable-next-line n/no-extraneous-import
|
||
import { Base64 } from 'js-base64';
|
||
|
||
import {
|
||
ENCRYPT_FIELDS,
|
||
isSensitiveField,
|
||
maskSensitiveField,
|
||
TM_TEXT_SUFFIX,
|
||
} from '#/constants/sensitive-fields';
|
||
import { useAuthStore } from '#/store';
|
||
|
||
import { refreshTokenApi } from './core';
|
||
import {downloadByData} from "#/util/tool";
|
||
|
||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||
|
||
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||
const client = new RequestClient({
|
||
...options,
|
||
baseURL,
|
||
});
|
||
|
||
/**
|
||
* 重新认证逻辑
|
||
*/
|
||
/** 防重复:并发 401 只弹一次重新登录提示 */
|
||
let authModalShowing = false;
|
||
async function doReAuthenticate() {
|
||
const accessStore = useAccessStore();
|
||
const authStore = useAuthStore();
|
||
accessStore.setAccessToken(null);
|
||
if (
|
||
preferences.app.loginExpiredMode === 'modal' &&
|
||
accessStore.isAccessChecked
|
||
) {
|
||
accessStore.setLoginExpired(true);
|
||
} else {
|
||
// 被踢/登录失效时用模态框提示,确认后再退出
|
||
if (!authModalShowing) {
|
||
authModalShowing = true;
|
||
await new Promise<void>((resolve) => {
|
||
Modal.warning({
|
||
title: '登录失效',
|
||
content: '账号需重新登录',
|
||
okText: '重新登录',
|
||
centered: true,
|
||
onOk: () => resolve(),
|
||
onCancel: () => resolve(),
|
||
});
|
||
});
|
||
authModalShowing = false;
|
||
}
|
||
await authStore.logout(true);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 刷新token逻辑
|
||
*/
|
||
async function doRefreshToken() {
|
||
const accessStore = useAccessStore();
|
||
const resp = await refreshTokenApi();
|
||
const newToken = resp.data;
|
||
accessStore.setAccessToken(newToken);
|
||
return newToken;
|
||
}
|
||
|
||
function formatToken(token: null | string) {
|
||
return token ? `Bearer ${token}` : null;
|
||
}
|
||
|
||
// 请求头处理
|
||
client.addRequestInterceptor({
|
||
fulfilled: async (config) => {
|
||
const accessStore = useAccessStore();
|
||
|
||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||
config.headers['Accept-Language'] = preferences.app.locale;
|
||
// 加密
|
||
// eslint-disable-next-line eqeqeq
|
||
if (config.data != null) {
|
||
config.data = getRes(config.data, false);
|
||
}
|
||
// eslint-disable-next-line eqeqeq
|
||
if (config.params != null) {
|
||
config.params = getRes(config.params, false);
|
||
}
|
||
return config;
|
||
},
|
||
});
|
||
|
||
// response数据解构
|
||
client.addResponseInterceptor<HttpResponse>({
|
||
fulfilled: (response) => {
|
||
const { data: responseData, status } = response;
|
||
|
||
// 这里吧 data 换成了 result
|
||
const { code, result } = responseData;
|
||
// 判断返回状态码
|
||
if (status >= 200 && status <= 500) {
|
||
switch (code) {
|
||
case 0: {
|
||
// 解密
|
||
// eslint-disable-next-line eqeqeq
|
||
if (result != null) {
|
||
return getRes(result);
|
||
}
|
||
return result;
|
||
}
|
||
case 401: {
|
||
response.status = 401;
|
||
// 401 错误,需要重新登录
|
||
authenticateResponseInterceptor({
|
||
client,
|
||
doReAuthenticate,
|
||
doRefreshToken,
|
||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||
formatToken,
|
||
});
|
||
break;
|
||
}
|
||
case 403:
|
||
case 404:
|
||
case 500: {
|
||
throw Object.assign({}, response, { response });
|
||
}
|
||
}
|
||
}
|
||
if (response.data instanceof Blob) {
|
||
return response;
|
||
}
|
||
|
||
throw Object.assign({}, response, { response });
|
||
},
|
||
});
|
||
|
||
// token过期的处理
|
||
client.addResponseInterceptor(
|
||
authenticateResponseInterceptor({
|
||
client,
|
||
doReAuthenticate,
|
||
doRefreshToken,
|
||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||
formatToken,
|
||
}),
|
||
);
|
||
|
||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||
client.addResponseInterceptor(
|
||
errorMessageResponseInterceptor((msg: string, error) => {
|
||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||
// 当前mock接口返回的错误字段是 error 或者 message
|
||
const responseData = error?.response?.data ?? {};
|
||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||
// 如果没有错误信息,则会根据状态码进行提示
|
||
message.error(errorMessage || msg);
|
||
}),
|
||
);
|
||
|
||
return client;
|
||
}
|
||
|
||
export const requestClient = createRequestClient(apiURL, {
|
||
responseReturn: 'result',
|
||
});
|
||
|
||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||
|
||
/**
|
||
* 加解密方法
|
||
* @param obj
|
||
* @param isDecode
|
||
*/
|
||
function getRes(obj: any, isDecode = true) {
|
||
try {
|
||
if (obj == null || typeof obj !== 'object') {
|
||
return obj;
|
||
}
|
||
for (const key in obj) {
|
||
// 跳过脱敏展示字段,避免重复处理
|
||
if (key.endsWith(TM_TEXT_SUFFIX)) {
|
||
continue;
|
||
}
|
||
if (Array.isArray(obj[key])) {
|
||
obj[key] = getRes(obj[key], isDecode);
|
||
} else if (obj[key] !== null && typeof obj[key] === 'object') {
|
||
obj[key] = getRes(obj[key], isDecode);
|
||
} else if (ENCRYPT_FIELDS.has(key)) {
|
||
let plainValue = isDecode
|
||
? customBase64Decode(obj[key], key)
|
||
: customBase64Encode(obj[key]);
|
||
const isGarbled =
|
||
// eslint-disable-next-line no-control-regex
|
||
/[\u0000-\u0008\v\f\u000E-\u001F\u007F-\u00FF\u{E000}-\u{F8FF}]/u.test(
|
||
plainValue,
|
||
);
|
||
if (isGarbled) {
|
||
plainValue = obj[key];
|
||
}
|
||
obj[key] = plainValue;
|
||
// 解密后写入脱敏展示字段,明文保留在原字段
|
||
if (isDecode && isSensitiveField(key)) {
|
||
obj[key + TM_TEXT_SUFFIX] = maskSensitiveField(key, plainValue);
|
||
}
|
||
}
|
||
}
|
||
return obj;
|
||
} catch (error) {
|
||
console.error('错误', error);
|
||
return obj;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解密
|
||
* @param data
|
||
* @param key
|
||
*/
|
||
function customBase64Decode(data: string, key = '') {
|
||
// return data
|
||
try {
|
||
// 第一次Base64解码
|
||
const decodedFirst = Base64.decode(data);
|
||
// 截取从第30个字符开始往后的内容
|
||
const subStr = decodedFirst.slice(30);
|
||
// 第二次Base64解码
|
||
const base64 = Base64.decode(subStr);
|
||
return base64.length > 0 ? base64 : data;
|
||
} catch (error) {
|
||
console.error(
|
||
`解码Base64字符串【${data}】时发生错误,KEY:【${key}】`,
|
||
error,
|
||
);
|
||
return data;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加密
|
||
* @param data
|
||
*/
|
||
function customBase64Encode(data: string) {
|
||
// eslint-disable-next-line eqeqeq
|
||
if (data == null || data === '') return data;
|
||
// return data
|
||
try {
|
||
// 和加密方法差不多但是是要生成30个随机字符
|
||
let randomString = '';
|
||
for (let i = 0; i < 30; i++) {
|
||
const randomChar = String.fromCodePoint(
|
||
Math.floor(Math.random() * (126 - 32 + 1)) + 32,
|
||
);
|
||
randomString += randomChar;
|
||
}
|
||
const encoded = Base64.encode(data);
|
||
return Base64.encode(randomString + encoded);
|
||
} catch (error) {
|
||
console.error(`加密字符串【${data}】时发生错误`, error);
|
||
return data;
|
||
}
|
||
}
|