fix(web,plugin): 前端审计修复 — 401 消除 + 统计卡片 crash + 销售漏斗 500 + antd 6 废弃 API
- API client: proactive token refresh(请求前 30s 检查过期,提前刷新避免 401) - Plugin store: fetchPlugins promise 去重,防止 StrictMode 并发重复请求 - Home stats: 简化 useEffect 加载逻辑,修复 tagColor undefined crash - PluginGraphPage: valueStyle → styles.content, Spin tip → description(antd 6) - DashboardWidgets: trailColor → railColor(antd 6) - data_service: build_scope_sql 参数索引修复(硬编码 $100 → 动态 values.len()+1) - erp-core error: Internal 错误添加 tracing::error 日志输出
This commit is contained in:
@@ -38,10 +38,53 @@ const client = axios.create({
|
||||
},
|
||||
});
|
||||
|
||||
// Request interceptor: attach access token
|
||||
client.interceptors.request.use((config) => {
|
||||
// Decode JWT payload without external library
|
||||
function decodeJwtPayload(token: string): { exp?: number } | null {
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) return null;
|
||||
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
||||
return payload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if token is expired or about to expire (within 30s buffer)
|
||||
function isTokenExpiringSoon(token: string): boolean {
|
||||
const payload = decodeJwtPayload(token);
|
||||
if (!payload?.exp) return true;
|
||||
return Date.now() / 1000 > payload.exp - 30;
|
||||
}
|
||||
|
||||
// Request interceptor: attach access token + proactive refresh
|
||||
client.interceptors.request.use(async (config) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (token) {
|
||||
// If token is about to expire, proactively refresh before sending the request
|
||||
if (isTokenExpiringSoon(token)) {
|
||||
const refreshToken = localStorage.getItem('refresh_token');
|
||||
if (refreshToken && !isRefreshing) {
|
||||
isRefreshing = true;
|
||||
try {
|
||||
const { data } = await axios.post('/api/v1/auth/refresh', {
|
||||
refresh_token: refreshToken,
|
||||
});
|
||||
const newAccess = data.data.access_token;
|
||||
const newRefresh = data.data.refresh_token;
|
||||
localStorage.setItem('access_token', newAccess);
|
||||
localStorage.setItem('refresh_token', newRefresh);
|
||||
processQueue(null, newAccess);
|
||||
config.headers.Authorization = `Bearer ${newAccess}`;
|
||||
return config;
|
||||
} catch {
|
||||
processQueue(new Error('refresh failed'), null);
|
||||
// Continue with old token, let 401 handler deal with it
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user