加入自动刷新RefreshToken的逻辑
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
- ### 数据库
|
- ### 数据库
|
||||||
|
|
||||||
- **Radis**:内存数据库
|
- **Redis**:内存数据库
|
||||||
- **PostgreSQL**:实验室业务数据库
|
- **PostgreSQL**:实验室业务数据库
|
||||||
- **Milvus**:实验室向量数据库
|
- **Milvus**:实验室向量数据库
|
||||||
- **SQLServer**:F8业务数据库
|
- **SQLServer**:F8业务数据库
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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 失败,处理错误(如强制登出或跳转登录页面)
|
||||||
|
|||||||
Reference in New Issue
Block a user