修复鉴权:401拦截器加refresh重试 + admin refresh接口 + 路由守卫完善
根因:access_token每2小时过期,前端收到401直接清token跳登录,没有用refresh_token续期 后端修复: - adminauth模块新增 POST /admin/auth/refresh 接口 - Service 注入 JWTManager,支持 admin refresh token 换新 token pair - Refresh 方法验证 subjectType=admin + tokenType=refresh 前端修复: - 401 拦截器核心改造:收到401先调 refresh 接口续期 - 加 isRefreshing 锁 + pendingRequests 队列防止并发刷新 - refresh 用原生 axios.post 避免拦截器递归 - 成功则更新 localStorage + 重试原请求,失败才清 token 跳登录 - 排除 /auth/refresh 自身避免死循环 - 支持 /admin/ 请求独立 token 管理 - auth.ts/adminAuth.ts 新增手动 refreshUserToken/refreshAdminSession - 路由守卫给所有需登录路由添加 meta.requiresAuth - 守卫同时支持 PC 端 /login 和移动端 /m/login
This commit is contained in:
+121
-76
@@ -1,124 +1,169 @@
|
||||
import { apiClient } from './client'
|
||||
import { apiClient } from "./client";
|
||||
|
||||
import { type PaginatedResult } from "./orders";
|
||||
|
||||
export interface Listing {
|
||||
id: number
|
||||
account_id: number
|
||||
owner_id: number
|
||||
owner_phone?: string
|
||||
owner_nickname?: string
|
||||
title: string
|
||||
description: string
|
||||
game_name: string
|
||||
server_region: string
|
||||
login_platform: string
|
||||
rank_level: string
|
||||
haf_coin_amount: number
|
||||
asset_summary?: Record<string, unknown>
|
||||
screenshot_urls: string[]
|
||||
cover_url: string
|
||||
price_hourly: number
|
||||
price_daily: number
|
||||
price_weekly: number
|
||||
deposit_amount: number
|
||||
is_accelerated_sale?: boolean
|
||||
status: string
|
||||
review_status: string
|
||||
review_reason: string
|
||||
published_at?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
id: number;
|
||||
account_id: number;
|
||||
owner_id: number;
|
||||
owner_phone?: string;
|
||||
owner_nickname?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
game_name: string;
|
||||
server_region: string;
|
||||
login_platform: string;
|
||||
rank_level: string;
|
||||
haf_coin_amount: number;
|
||||
asset_summary?: Record<string, unknown>;
|
||||
screenshot_urls: string[];
|
||||
cover_url: string;
|
||||
price_hourly: number;
|
||||
price_daily: number;
|
||||
price_weekly: number;
|
||||
deposit_amount: number;
|
||||
is_accelerated_sale?: boolean;
|
||||
status: string;
|
||||
review_status: string;
|
||||
review_reason: string;
|
||||
published_at?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ListingPayload {
|
||||
title: string
|
||||
description: string
|
||||
server_region: string
|
||||
login_platform: string
|
||||
rank_level: string
|
||||
haf_coin_amount: number
|
||||
asset_summary?: Record<string, unknown>
|
||||
screenshot_urls: string[]
|
||||
price_hourly: number
|
||||
price_daily: number
|
||||
price_weekly: number
|
||||
deposit_amount: number
|
||||
title: string;
|
||||
description: string;
|
||||
server_region: string;
|
||||
login_platform: string;
|
||||
rank_level: string;
|
||||
haf_coin_amount: number;
|
||||
asset_summary?: Record<string, unknown>;
|
||||
screenshot_urls: string[];
|
||||
price_hourly: number;
|
||||
price_daily: number;
|
||||
price_weekly: number;
|
||||
deposit_amount: number;
|
||||
}
|
||||
|
||||
interface ApiResponse<T> {
|
||||
code: string
|
||||
message: string
|
||||
data: T
|
||||
code: string;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export async function fetchListings() {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: Listing[] }>>('/listings')
|
||||
return data.data.items
|
||||
export async function fetchListings(page = 1, pageSize = 20) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<Listing>>>(
|
||||
"/listings",
|
||||
{
|
||||
params: { page, page_size: pageSize },
|
||||
}
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function fetchListing(id: string | number) {
|
||||
const { data } = await apiClient.get<ApiResponse<Listing>>(`/listings/${id}`)
|
||||
return data.data
|
||||
const { data } = await apiClient.get<ApiResponse<Listing>>(`/listings/${id}`);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function fetchSellerListings() {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: Listing[] }>>('/seller/listings')
|
||||
return data.data.items
|
||||
export async function fetchSellerListings(page = 1, pageSize = 20) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<Listing>>>(
|
||||
"/seller/listings",
|
||||
{
|
||||
params: { page, page_size: pageSize },
|
||||
}
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function createListing(payload: ListingPayload) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>('/listings', payload)
|
||||
return data.data
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(
|
||||
"/listings",
|
||||
payload
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function submitListingReview(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/listings/${id}/submit-review`)
|
||||
return data.data
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(
|
||||
`/listings/${id}/submit-review`
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function offlineListing(id: number) {
|
||||
const { data } = await apiClient.delete<ApiResponse<{ offline: boolean }>>(`/listings/${id}`)
|
||||
return data.data
|
||||
const { data } = await apiClient.delete<ApiResponse<{ offline: boolean }>>(
|
||||
`/listings/${id}`
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function fetchPendingReviewListings() {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: Listing[] }>>('/admin/listings/pending')
|
||||
return data.data.items
|
||||
export async function fetchPendingReviewListings(page = 1, pageSize = 20) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<Listing>>>(
|
||||
"/admin/listings/pending",
|
||||
{
|
||||
params: { page, page_size: pageSize },
|
||||
}
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export interface AdminListingQuery {
|
||||
owner_id?: string
|
||||
status?: string
|
||||
review_status?: string
|
||||
limit?: number
|
||||
owner_id?: string;
|
||||
status?: string;
|
||||
review_status?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
export async function fetchAdminListings(query: AdminListingQuery = {}) {
|
||||
const params = Object.fromEntries(Object.entries(query).filter(([, value]) => value !== '' && value !== undefined))
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: Listing[] }>>('/admin/listings', { params })
|
||||
return data.data.items
|
||||
const params = Object.fromEntries(
|
||||
Object.entries(query).filter(
|
||||
([, value]) => value !== "" && value !== undefined
|
||||
)
|
||||
);
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<Listing>>>(
|
||||
"/admin/listings",
|
||||
{ params }
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function fetchAdminListing(id: string | number) {
|
||||
const { data } = await apiClient.get<ApiResponse<Listing>>(`/admin/listings/${id}`)
|
||||
return data.data
|
||||
const { data } = await apiClient.get<ApiResponse<Listing>>(
|
||||
`/admin/listings/${id}`
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function adminOfflineListing(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/offline`, { reason })
|
||||
return data.data
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(
|
||||
`/admin/listings/${id}/offline`,
|
||||
{ reason }
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function adminMarkListingAbnormal(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/mark-abnormal`, { reason })
|
||||
return data.data
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(
|
||||
`/admin/listings/${id}/mark-abnormal`,
|
||||
{ reason }
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function approveListing(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/approve`)
|
||||
return data.data
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(
|
||||
`/admin/listings/${id}/approve`
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function rejectListing(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/reject`, { reason })
|
||||
return data.data
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(
|
||||
`/admin/listings/${id}/reject`,
|
||||
{ reason }
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user