加入自动刷新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
@@ -152,7 +152,6 @@ fun Application.User(config: AppConfig) {
generateRefreshToken(config, userId) generateRefreshToken(config, userId)
) )
) )
BaseResponse(data = generateAccessToken(config, userId))
} catch (ex: Exception) { } catch (ex: Exception) {
BaseResponse(status = false, message = "token解析错误", data = null) BaseResponse(status = false, message = "token解析错误", data = null)
} }
@@ -21,7 +21,7 @@ object TokenUtils {
} }
fun generateRefreshToken(config: AppConfig, userId: UUID): Token { fun generateRefreshToken(config: AppConfig, userId: UUID): Token {
return generateToken(config, userId, 10 * 24 * 60 * 60, "refresh_token") return generateToken(config, userId, 20 * 24 * 60 * 60, "refresh_token")
} }
fun getUserIdByToken(call: ApplicationCall) : UUID{ fun getUserIdByToken(call: ApplicationCall) : UUID{
@@ -13,7 +13,6 @@ fun Application.configureCORS() {
anyHost() anyHost()
allowHost("localhost:8089") allowHost("localhost:8089")
allowHost("127.0.0.1:8089") allowHost("127.0.0.1:8089")
allowHost("171.212.101.199:8089")
allowHost("ai.ronsunny.cn:8089") allowHost("ai.ronsunny.cn:8089")
// 进一步配置 CORS // 进一步配置 CORS
+1 -1
View File
@@ -24,7 +24,7 @@
- ### 数据库 - ### 数据库
- **Radis**:内存数据库 - **Redis**:内存数据库
- **PostgreSQL**:实验室业务数据库 - **PostgreSQL**:实验室业务数据库
- **Milvus**:实验室向量数据库 - **Milvus**:实验室向量数据库
- **SQLServer**F8业务数据库 - **SQLServer**F8业务数据库
+9 -11
View File
@@ -8,6 +8,7 @@ export namespace AuthApi {
account?: string; account?: string;
password?: string; password?: string;
} }
/** 注册 */ /** 注册 */
export interface RegisterParams { export interface RegisterParams {
account?: string; account?: string;
@@ -29,8 +30,8 @@ export namespace AuthApi {
} }
export interface RefreshTokenResult { export interface RefreshTokenResult {
data: string; accessToken: Token;
status: number; refreshToken: Token;
} }
} }
@@ -58,26 +59,23 @@ export async function sendCodeApi(str: string) {
export async function registerApi(data: AuthApi.RegisterParams) { export async function registerApi(data: AuthApi.RegisterParams) {
// 密码加密 // 密码加密
data.password = sha256(data.password?.toString() || ''); data.password = sha256(data.password?.toString() || '');
return requestClient.post<any>('/user/register', data); return requestClient.post<AuthApi.LoginResult>('/user/register', data);
} }
/** /**
* 刷新accessToken * 刷新accessToken
*/ */
export async function refreshTokenApi() { export async function refreshTokenApi(refreshToken: null | string) {
return baseRequestClient.post<AuthApi.RefreshTokenResult>( return requestClient.post<AuthApi.RefreshTokenResult>('/user/refreshToken', {
'/user/refreshToken', refreshToken,
{ });
withCredentials: true,
},
);
} }
/** /**
* 退出登录 * 退出登录
*/ */
export async function logoutApi() { export async function logoutApi() {
return baseRequestClient.post('/auth/logout', { return baseRequestClient.post('/auth/logout', null, {
withCredentials: true, withCredentials: true,
}); });
} }
+8 -4
View File
@@ -53,10 +53,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
*/ */
async function doRefreshToken() { async function doRefreshToken() {
const accessStore = useAccessStore(); const accessStore = useAccessStore();
const resp = await refreshTokenApi(); const resp = await refreshTokenApi(accessStore.refreshToken);
const newToken = resp.data; const newAccessToken = resp.accessToken.token;
accessStore.setAccessToken(newToken); const newRefreshToken = resp.refreshToken.token;
return newToken; if (newAccessToken) {
accessStore.setAccessToken(newAccessToken);
accessStore.setRefreshToken(newRefreshToken);
}
return newAccessToken;
} }
function formatToken(token: null | string) { function formatToken(token: null | string) {
+2 -1
View File
@@ -12,7 +12,8 @@ export const overridesPreferences = defineOverridesPreferences({
layout: 'header-sidebar-nav', layout: 'header-sidebar-nav',
defaultHomePath: '/workspace', // 默认首页路径 defaultHomePath: '/workspace', // 默认首页路径
enablePreferences: false, // 是否启用偏好设置 enablePreferences: false, // 是否启用偏好设置
loginExpiredMode: 'page', // 登录过期模式 不用弹窗登录 跳转到页面登录,防止一些界面不会再加载 enableRefreshToken: true, // 启动刷新token模式
loginExpiredMode: 'modal', // 登录过期模式 不用弹窗登录 跳转到页面登录,防止一些界面不会再加载
}, },
theme: { theme: {
mode: 'light', mode: 'light',
+2 -1
View File
@@ -33,11 +33,12 @@ export const useAuthStore = defineStore('auth', () => {
let userInfo: null | UserInfo = null; let userInfo: null | UserInfo = null;
try { try {
loginLoading.value = true; loginLoading.value = true;
const { accessToken } = await loginApi(params); const { accessToken, refreshToken } = await loginApi(params);
// 如果成功获取到 accessToken // 如果成功获取到 accessToken
if (accessToken) { if (accessToken) {
accessStore.setAccessToken(accessToken.token); accessStore.setAccessToken(accessToken.token);
accessStore.setRefreshToken(refreshToken.token);
// 获取用户信息并存储到 accessStore 中 // 获取用户信息并存储到 accessStore 中
const [fetchUserInfoResult, accessCodes] = await Promise.all([ 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 { onActivated, onDeactivated, onMounted, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Button, message } from 'ant-design-vue'; import { Button, message } from 'ant-design-vue';
import * as api from '#/api'; import * as api from '#/api';
import CreateYSATaskModal from './CreateYSATaskModal.vue';
const list = ref<any[]>([]); const list = ref<any[]>([]);
const error = ref<null | string>(null); const error = ref<null | string>(null);
const filterKeyword = ref(''); const filterKeyword = ref('');
@@ -26,14 +23,6 @@ function refreshList() {
loadList(); loadList();
message.success('列表加载完成'); message.success('列表加载完成');
} }
const [BaseModal, baseModalApi] = useVbenModal({
// 连接抽离的组件
connectedComponent: CreateYSATaskModal,
});
function createTask() {
baseModalApi.open();
}
async function selectItem(item: any) { async function selectItem(item: any) {
selectedItem.value = item; selectedItem.value = item;
@@ -159,18 +148,12 @@ onActivated(() => {
<template> <template>
<div class="flex h-[90dvh] w-full flex-col"> <div class="flex h-[90dvh] w-full flex-col">
<BaseModal /> <BaseModal />
<CreateYSATaskModal />
<div class="flex h-[90dvh] w-full bg-gray-50"> <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="flex w-64 flex-col border-r bg-white p-4">
<!-- 按钮组 --> <!-- 按钮组 -->
<div class="mb-4 flex justify-between space-x-2"> <div class="mb-4 flex justify-between space-x-2">
<Button <Button type="primary" class="flex-1" :disabled="true">
type="primary"
@click="createTask"
class="flex-1"
:disabled="true"
>
新建任务 新建任务
</Button> </Button>
<Button @click="refreshList" class="flex-1"> 刷新列表 </Button> <Button @click="refreshList" class="flex-1"> 刷新列表 </Button>
@@ -35,7 +35,7 @@ const props = withDefaults(defineProps<Props>(), {
showForgetPassword: true, showForgetPassword: true,
showQrcodeLogin: true, showQrcodeLogin: true,
showRegister: true, showRegister: true,
showRememberMe: true, showRememberMe: false,
showThirdPartyLogin: true, showThirdPartyLogin: true,
submitButtonText: '', submitButtonText: '',
subTitle: '', subTitle: '',
@@ -53,7 +53,7 @@ export const authenticateResponseInterceptor = ({
}: { }: {
client: RequestClient; client: RequestClient;
doReAuthenticate: () => Promise<void>; doReAuthenticate: () => Promise<void>;
doRefreshToken: () => Promise<string>; doRefreshToken: () => Promise<null | string>;
enableRefreshToken: boolean; enableRefreshToken: boolean;
formatToken: (token: string) => null | string; formatToken: (token: string) => null | string;
}): ResponseInterceptorConfig => { }): ResponseInterceptorConfig => {
@@ -88,11 +88,13 @@ export const authenticateResponseInterceptor = ({
try { try {
const newToken = await doRefreshToken(); const newToken = await doRefreshToken();
if (!newToken) {
throw new Error('刷新 token 失败');
}
// 处理队列中的请求 // 处理队列中的请求
client.refreshTokenQueue.forEach((callback) => callback(newToken)); client.refreshTokenQueue.forEach((callback) => callback(newToken));
// 清空队列 // 清空队列
client.refreshTokenQueue = []; client.refreshTokenQueue = [];
return client.request(error.config.url, { ...error.config }); return client.request(error.config.url, { ...error.config });
} catch (refreshError) { } catch (refreshError) {
// 如果刷新 token 失败,处理错误(如强制登出或跳转登录页面) // 如果刷新 token 失败,处理错误(如强制登出或跳转登录页面)