加入自动刷新RefreshToken的逻辑

This commit is contained in:
BBIT-Kai
2025-12-02 13:48:24 +08:00
parent eeada99dbb
commit c53926afd6
12 changed files with 29 additions and 128 deletions
+9 -11
View File
@@ -8,6 +8,7 @@ export namespace AuthApi {
account?: string;
password?: string;
}
/** 注册 */
export interface RegisterParams {
account?: string;
@@ -29,8 +30,8 @@ export namespace AuthApi {
}
export interface RefreshTokenResult {
data: string;
status: number;
accessToken: Token;
refreshToken: Token;
}
}
@@ -58,26 +59,23 @@ export async function sendCodeApi(str: string) {
export async function registerApi(data: AuthApi.RegisterParams) {
// 密码加密
data.password = sha256(data.password?.toString() || '');
return requestClient.post<any>('/user/register', data);
return requestClient.post<AuthApi.LoginResult>('/user/register', data);
}
/**
* 刷新accessToken
*/
export async function refreshTokenApi() {
return baseRequestClient.post<AuthApi.RefreshTokenResult>(
'/user/refreshToken',
{
withCredentials: true,
},
);
export async function refreshTokenApi(refreshToken: null | string) {
return requestClient.post<AuthApi.RefreshTokenResult>('/user/refreshToken', {
refreshToken,
});
}
/**
* 退出登录
*/
export async function logoutApi() {
return baseRequestClient.post('/auth/logout', {
return baseRequestClient.post('/auth/logout', null, {
withCredentials: true,
});
}
+8 -4
View File
@@ -53,10 +53,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
*/
async function doRefreshToken() {
const accessStore = useAccessStore();
const resp = await refreshTokenApi();
const newToken = resp.data;
accessStore.setAccessToken(newToken);
return newToken;
const resp = await refreshTokenApi(accessStore.refreshToken);
const newAccessToken = resp.accessToken.token;
const newRefreshToken = resp.refreshToken.token;
if (newAccessToken) {
accessStore.setAccessToken(newAccessToken);
accessStore.setRefreshToken(newRefreshToken);
}
return newAccessToken;
}
function formatToken(token: null | string) {
+2 -1
View File
@@ -12,7 +12,8 @@ export const overridesPreferences = defineOverridesPreferences({
layout: 'header-sidebar-nav',
defaultHomePath: '/workspace', // 默认首页路径
enablePreferences: false, // 是否启用偏好设置
loginExpiredMode: 'page', // 登录过期模式 不用弹窗登录 跳转到页面登录,防止一些界面不会再加载
enableRefreshToken: true, // 启动刷新token模式
loginExpiredMode: 'modal', // 登录过期模式 不用弹窗登录 跳转到页面登录,防止一些界面不会再加载
},
theme: {
mode: 'light',
+2 -1
View File
@@ -33,11 +33,12 @@ export const useAuthStore = defineStore('auth', () => {
let userInfo: null | UserInfo = null;
try {
loginLoading.value = true;
const { accessToken } = await loginApi(params);
const { accessToken, refreshToken } = await loginApi(params);
// 如果成功获取到 accessToken
if (accessToken) {
accessStore.setAccessToken(accessToken.token);
accessStore.setRefreshToken(refreshToken.token);
// 获取用户信息并存储到 accessStore 中
const [fetchUserInfoResult, accessCodes] = await Promise.all([
@@ -1,86 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Form, Input, message } from 'ant-design-vue';
import { createVideoTask } from '#/api/cv/iva';
const projectName = ref('');
const fileName = ref('');
const selectedFile = ref<File | null>(null);
const fileInputRef = ref<HTMLInputElement | null>(null);
const [Modal, modalApi] = useVbenModal({
title: '新建蚕茧分析任务',
class: 'w-[600px]',
onCancel() {
modalApi.close();
},
onConfirm() {
if (!projectName.value || !selectedFile.value) {
message.warning('请填写项目名并选择蚕茧图片');
return;
}
uploadFile();
},
});
async function uploadFile() {
const formData = new FormData();
formData.append('file', selectedFile.value!);
formData.append('projectName', projectName.value);
await createVideoTask(formData).then(() => {
message.success('任务创建成功,正在处理图像,请稍后刷新列表查看');
modalApi.close();
// 清空表单
projectName.value = '';
fileName.value = '';
selectedFile.value = null;
});
}
function selectFile() {
fileInputRef.value?.click();
}
function handleFileChange(event: Event) {
const files = (event.target as HTMLInputElement).files;
if (files && files.length > 0) {
selectedFile.value = files[0];
fileName.value = files[0].name;
}
}
</script>
<template>
<Modal>
<Form layout="vertical">
<Form.Item label="项目名*" required>
<Input v-model:value="projectName" />
</Form.Item>
<Form.Item label="上传图片*" required>
<div
@click="selectFile"
style="
padding: 16px;
text-align: center;
cursor: pointer;
border: 1px dashed #d9d9d9;
"
>
{{ fileName || '点击选择文件' }}
<input
type="file"
accept="image/*"
ref="fileInputRef"
@change="handleFileChange"
style="display: none"
/>
</div>
</Form.Item>
</Form>
</Modal>
</template>
+1 -18
View File
@@ -3,15 +3,12 @@ import type { EchartsUIType } from '@vben/plugins/echarts';
import { onActivated, onDeactivated, onMounted, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Button, message } from 'ant-design-vue';
import * as api from '#/api';
import CreateYSATaskModal from './CreateYSATaskModal.vue';
const list = ref<any[]>([]);
const error = ref<null | string>(null);
const filterKeyword = ref('');
@@ -26,14 +23,6 @@ function refreshList() {
loadList();
message.success('列表加载完成');
}
const [BaseModal, baseModalApi] = useVbenModal({
// 连接抽离的组件
connectedComponent: CreateYSATaskModal,
});
function createTask() {
baseModalApi.open();
}
async function selectItem(item: any) {
selectedItem.value = item;
@@ -159,18 +148,12 @@ onActivated(() => {
<template>
<div class="flex h-[90dvh] w-full flex-col">
<BaseModal />
<CreateYSATaskModal />
<div class="flex h-[90dvh] w-full bg-gray-50">
<!-- 左侧筛选 + 列表 -->
<div class="flex w-64 flex-col border-r bg-white p-4">
<!-- 按钮组 -->
<div class="mb-4 flex justify-between space-x-2">
<Button
type="primary"
@click="createTask"
class="flex-1"
:disabled="true"
>
<Button type="primary" class="flex-1" :disabled="true">
新建任务
</Button>
<Button @click="refreshList" class="flex-1"> 刷新列表 </Button>
@@ -35,7 +35,7 @@ const props = withDefaults(defineProps<Props>(), {
showForgetPassword: true,
showQrcodeLogin: true,
showRegister: true,
showRememberMe: true,
showRememberMe: false,
showThirdPartyLogin: true,
submitButtonText: '',
subTitle: '',
@@ -53,7 +53,7 @@ export const authenticateResponseInterceptor = ({
}: {
client: RequestClient;
doReAuthenticate: () => Promise<void>;
doRefreshToken: () => Promise<string>;
doRefreshToken: () => Promise<null | string>;
enableRefreshToken: boolean;
formatToken: (token: string) => null | string;
}): ResponseInterceptorConfig => {
@@ -88,11 +88,13 @@ export const authenticateResponseInterceptor = ({
try {
const newToken = await doRefreshToken();
if (!newToken) {
throw new Error('刷新 token 失败');
}
// 处理队列中的请求
client.refreshTokenQueue.forEach((callback) => callback(newToken));
// 清空队列
client.refreshTokenQueue = [];
return client.request(error.config.url, { ...error.config });
} catch (refreshError) {
// 如果刷新 token 失败,处理错误(如强制登出或跳转登录页面)