diff --git a/src/api/authService.ts b/src/api/authService.ts index 395c5f0..3a94daf 100644 --- a/src/api/authService.ts +++ b/src/api/authService.ts @@ -1,15 +1,30 @@ -import axios from 'axios'; +import apiClient from './axiosInstance'; +import type { TokenResponse, HTTPValidationError } from './types'; -const API_BASE_URL = '/api/auth'; +const API_BASE_URL = '/auth'; export const authService = { - async login(username: string, password: string) { - const response = await axios.post(`${API_BASE_URL}/login`, { username, password }); - return response.data; + async login(username: string, password: string): Promise { + try { + const response = await apiClient.post(`${API_BASE_URL}/login`, { username, password }); + return response.data; + } catch (error: any) { + if (error.response?.data?.detail?.[0]?.msg) { + throw new Error(error.response.data.detail[0].msg); + } + throw new Error('Login failed'); + } }, - async refreshToken(refreshToken: string) { - const response = await axios.post(`${API_BASE_URL}/refresh`, { refresh_token: refreshToken }); - return response.data; + async refreshToken(refreshToken: string): Promise { + try { + const response = await apiClient.post(`${API_BASE_URL}/refresh`, { refresh_token: refreshToken }); + return response.data; + } catch (error: any) { + if (error.response?.data?.detail?.[0]?.msg) { + throw new Error(error.response.data.detail[0].msg); + } + throw new Error('Token refresh failed'); + } } }; \ No newline at end of file diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts new file mode 100644 index 0000000..0350ea3 --- /dev/null +++ b/src/api/axiosInstance.ts @@ -0,0 +1,67 @@ +import axios from 'axios'; +import { authService } from './authService'; + +// 创建axios实例 +const apiClient = axios.create({ + baseURL: '/api', + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 请求拦截器 +apiClient.interceptors.request.use( + async (config) => { + // 从localStorage获取access_token + const accessToken = localStorage.getItem('access_token'); + + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +apiClient.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // 如果401错误且不是刷新token的请求 + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + // 获取refresh_token + const refreshToken = localStorage.getItem('refresh_token'); + if (!refreshToken) { + throw new Error('No refresh token available'); + } + + // 刷新token + const { access_token, refresh_token } = await authService.refreshToken(refreshToken); + + // 保存新token + localStorage.setItem('access_token', access_token); + localStorage.setItem('refresh_token', refresh_token); + + // 重试原始请求 + originalRequest.headers.Authorization = `Bearer ${access_token}`; + return apiClient(originalRequest); + } catch (refreshError) { + // 刷新token失败,跳转到登录页 + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } +); + +export default apiClient; \ No newline at end of file diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..e04b692 --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,41 @@ +export interface UserResponse { + id: number; + username: string; + role: UserRole; + description?: string; + created_at: string; + updated_at: string; +} + +export interface UserCreate { + username: string; + password: string; + role?: UserRole; + description?: string; +} + +export interface UserUpdate { + username?: string; + role?: UserRole; + description?: string; +} + +export interface TokenResponse { + access_token: string; + refresh_token: string; + token_type: string; + access_token_exp: number; + refresh_token_exp: number; +} + +export interface HTTPValidationError { + detail: ValidationError[]; +} + +export interface ValidationError { + loc: (string | number)[]; + msg: string; + type: string; +} + +export type UserRole = 'system_admin' | 'admin' | 'user'; \ No newline at end of file diff --git a/src/api/userService.ts b/src/api/userService.ts new file mode 100644 index 0000000..7dda43e --- /dev/null +++ b/src/api/userService.ts @@ -0,0 +1,27 @@ +import apiClient from './axiosInstance'; +import type { UserResponse, UserCreate, UserUpdate } from './types'; + +const API_BASE_URL = '/users'; + +export const userService = { + async getUsers(page = 1, limit = 100, role?: string): Promise { + const response = await apiClient.get(API_BASE_URL, { + params: { page, limit, role } + }); + return response.data; + }, + + async createUser(userData: UserCreate): Promise { + const response = await apiClient.post(API_BASE_URL, userData); + return response.data; + }, + + async updateUser(userId: number, userData: UserUpdate): Promise { + const response = await apiClient.put(`${API_BASE_URL}/${userId}`, userData); + return response.data; + }, + + async deleteUser(userId: number): Promise { + await apiClient.delete(`${API_BASE_URL}/${userId}`); + } +}; \ No newline at end of file