Преглед изворни кода

用户访问后token续签处理

lingmin_package@163.com пре 2 недеља
родитељ
комит
1f39b868a5
5 измењених фајлова са 862 додато и 55 уклоњено
  1. 316 0
      public/diagnose.html
  2. 227 0
      public/quick-test.html
  3. 76 0
      public/test-redirect.html
  4. 115 0
      src/api/request-simple.ts
  5. 128 55
      src/api/request.ts

+ 316 - 0
public/diagnose.html

@@ -0,0 +1,316 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>401跳转诊断工具</title>
+    <style>
+        * { margin: 0; padding: 0; box-sizing: border-box; }
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background: #f5f7fa;
+            padding: 20px;
+        }
+        .container {
+            max-width: 900px;
+            margin: 0 auto;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 12px rgba(0,0,0,0.1);
+            padding: 30px;
+        }
+        h1 {
+            color: #303133;
+            margin-bottom: 10px;
+        }
+        .subtitle {
+            color: #909399;
+            margin-bottom: 30px;
+        }
+        .test-section {
+            margin: 20px 0;
+            padding: 20px;
+            background: #f9fafc;
+            border-radius: 6px;
+            border-left: 4px solid #409eff;
+        }
+        .test-section h3 {
+            color: #303133;
+            margin-bottom: 15px;
+        }
+        button {
+            padding: 12px 24px;
+            margin: 5px;
+            font-size: 14px;
+            cursor: pointer;
+            background: #409eff;
+            color: white;
+            border: none;
+            border-radius: 4px;
+            transition: all 0.3s;
+        }
+        button:hover {
+            background: #66b1ff;
+        }
+        button.success {
+            background: #67c23a;
+        }
+        button.danger {
+            background: #f56c6c;
+        }
+        .result {
+            margin-top: 15px;
+            padding: 15px;
+            background: white;
+            border-radius: 4px;
+            border: 1px solid #dcdfe6;
+            font-family: 'Courier New', monospace;
+            font-size: 13px;
+            max-height: 300px;
+            overflow-y: auto;
+        }
+        .result-item {
+            margin: 5px 0;
+            padding: 5px;
+        }
+        .success-text { color: #67c23a; }
+        .error-text { color: #f56c6c; }
+        .info-text { color: #409eff; }
+        .warning-text { color: #e6a23c; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>🔍 401跳转诊断工具</h1>
+        <p class="subtitle">用于诊断前端401响应跳转问题</p>
+
+        <!-- 测试1: 基本跳转功能 -->
+        <div class="test-section">
+            <h3>测试1: 基本跳转功能</h3>
+            <p>测试 window.location.href 是否能正常工作</p>
+            <button onclick="test1()">执行测试1</button>
+            <div id="result1" class="result" style="display:none;"></div>
+        </div>
+
+        <!-- 测试2: localStorage操作 -->
+        <div class="test-section">
+            <h3>测试2: localStorage操作</h3>
+            <p>测试是否能正常读写localStorage</p>
+            <button onclick="test2()">执行测试2</button>
+            <div id="result2" class="result" style="display:none;"></div>
+        </div>
+
+        <!-- 测试3: 模拟401响应 -->
+        <div class="test-section">
+            <h3>测试3: 模拟401响应</h3>
+            <p>向后台发送无效token,测试是否返回401</p>
+            <button onclick="test3()">执行测试3</button>
+            <div id="result3" class="result" style="display:none;"></div>
+        </div>
+
+        <!-- 测试4: 检查前端代码 -->
+        <div class="test-section">
+            <h3>测试4: 检查前端代码</h3>
+            <p>检查request.ts是否包含最新的401处理代码</p>
+            <button onclick="test4()">执行测试4</button>
+            <div id="result4" class="result" style="display:none;"></div>
+        </div>
+
+        <!-- 测试5: 完整流程测试 -->
+        <div class="test-section">
+            <h3>测试5: 完整流程测试</h3>
+            <p>模拟完整的401错误处理流程</p>
+            <button onclick="test5()">执行测试5</button>
+            <div id="result5" class="result" style="display:none;"></div>
+        </div>
+
+        <!-- 一键诊断 -->
+        <div class="test-section" style="border-left-color: #67c23a;">
+            <h3>🚀 一键诊断</h3>
+            <p>自动执行所有测试</p>
+            <button class="success" onclick="runAllTests()">运行所有测试</button>
+        </div>
+    </div>
+
+    <script>
+        function log(resultId, message, type = 'info') {
+            const result = document.getElementById(resultId);
+            result.style.display = 'block';
+            const item = document.createElement('div');
+            item.className = `result-item ${type}-text`;
+            item.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
+            result.appendChild(item);
+            result.scrollTop = result.scrollHeight;
+        }
+
+        function clearLog(resultId) {
+            const result = document.getElementById(resultId);
+            result.innerHTML = '';
+            result.style.display = 'none';
+        }
+
+        // 测试1: 基本跳转功能
+        function test1() {
+            clearLog('result1');
+            log('result1', '开始测试基本跳转功能...', 'info');
+            
+            log('result1', '✓ window.location 对象存在', 'success');
+            log('result1', `当前URL: ${window.location.href}`, 'info');
+            
+            log('result1', '3秒后将跳转到登录页...', 'warning');
+            let countdown = 3;
+            const timer = setInterval(() => {
+                log('result1', `${countdown}...`, 'warning');
+                countdown--;
+                if (countdown < 0) {
+                    clearInterval(timer);
+                    log('result1', '执行跳转: window.location.href = "/login"', 'info');
+                    window.location.href = '/login';
+                }
+            }, 1000);
+        }
+
+        // 测试2: localStorage操作
+        function test2() {
+            clearLog('result2');
+            log('result2', '开始测试localStorage操作...', 'info');
+            
+            try {
+                // 写入测试
+                localStorage.setItem('test_key', 'test_value');
+                log('result2', '✓ localStorage.setItem 成功', 'success');
+                
+                // 读取测试
+                const value = localStorage.getItem('test_key');
+                if (value === 'test_value') {
+                    log('result2', '✓ localStorage.getItem 成功', 'success');
+                } else {
+                    log('result2', '✗ localStorage.getItem 失败', 'error');
+                }
+                
+                // 删除测试
+                localStorage.removeItem('test_key');
+                log('result2', '✓ localStorage.removeItem 成功', 'success');
+                
+                // 检查token
+                const token = localStorage.getItem('sso_access_token');
+                if (token) {
+                    log('result2', `当前token: ${token.substring(0, 30)}...`, 'info');
+                } else {
+                    log('result2', '当前没有token', 'info');
+                }
+                
+                log('result2', '✓ localStorage功能正常', 'success');
+            } catch (error) {
+                log('result2', `✗ localStorage测试失败: ${error.message}`, 'error');
+            }
+        }
+
+        // 测试3: 模拟401响应
+        async function test3() {
+            clearLog('result3');
+            log('result3', '开始测试401响应...', 'info');
+            
+            const baseURL = 'http://localhost:8000'; // 修改为你的后台地址
+            const testURL = `${baseURL}/api/v1/system/user/profile`;
+            
+            log('result3', `请求URL: ${testURL}`, 'info');
+            log('result3', '使用无效token: invalid_token_for_test', 'info');
+            
+            try {
+                const response = await fetch(testURL, {
+                    method: 'GET',
+                    headers: {
+                        'Authorization': 'Bearer invalid_token_for_test',
+                        'Content-Type': 'application/json'
+                    }
+                });
+                
+                log('result3', `响应状态码: ${response.status}`, 'info');
+                
+                if (response.status === 401) {
+                    log('result3', '✓ 后台正确返回401', 'success');
+                    
+                    const data = await response.json();
+                    log('result3', `响应数据: ${JSON.stringify(data)}`, 'info');
+                    
+                    log('result3', '前端应该在此时跳转到登录页', 'warning');
+                } else {
+                    log('result3', `✗ 后台返回了${response.status}而不是401`, 'error');
+                }
+            } catch (error) {
+                log('result3', `✗ 请求失败: ${error.message}`, 'error');
+                log('result3', '可能原因: 后台服务未启动或CORS问题', 'warning');
+            }
+        }
+
+        // 测试4: 检查前端代码
+        async function test4() {
+            clearLog('result4');
+            log('result4', '开始检查前端代码...', 'info');
+            
+            try {
+                const response = await fetch('/src/api/request.ts');
+                const code = await response.text();
+                
+                log('result4', `✓ 成功读取request.ts (${code.length}字符)`, 'success');
+                
+                // 检查关键代码
+                const checks = [
+                    { text: '>>> 检测到401错误', name: '401检测日志' },
+                    { text: 'window.location.href', name: '跳转代码' },
+                    { text: 'localStorage.removeItem', name: '清除token代码' },
+                    { text: 'response.status === 401', name: '401判断' }
+                ];
+                
+                checks.forEach(check => {
+                    if (code.includes(check.text)) {
+                        log('result4', `✓ 包含${check.name}`, 'success');
+                    } else {
+                        log('result4', `✗ 缺少${check.name}`, 'error');
+                    }
+                });
+                
+            } catch (error) {
+                log('result4', `✗ 无法读取request.ts: ${error.message}`, 'error');
+                log('result4', '这是正常的,生产环境无法直接读取源码', 'info');
+            }
+        }
+
+        // 测试5: 完整流程测试
+        function test5() {
+            clearLog('result5');
+            log('result5', '开始完整流程测试...', 'info');
+            
+            log('result5', '步骤1: 设置无效token', 'info');
+            localStorage.setItem('sso_access_token', 'invalid_token_for_test');
+            log('result5', '✓ 已设置无效token', 'success');
+            
+            log('result5', '步骤2: 3秒后刷新页面', 'info');
+            log('result5', '页面刷新后应该自动跳转到登录页', 'warning');
+            
+            let countdown = 3;
+            const timer = setInterval(() => {
+                log('result5', `${countdown}秒后刷新...`, 'warning');
+                countdown--;
+                if (countdown < 0) {
+                    clearInterval(timer);
+                    log('result5', '正在刷新页面...', 'info');
+                    location.reload();
+                }
+            }, 1000);
+        }
+
+        // 运行所有测试
+        async function runAllTests() {
+            await test2();
+            await new Promise(resolve => setTimeout(resolve, 1000));
+            await test3();
+            await new Promise(resolve => setTimeout(resolve, 1000));
+            await test4();
+            
+            alert('诊断完成!\n\n请查看各个测试的结果。\n\n如果测试3显示后台返回401,但页面没有跳转,\n说明前端代码可能没有正确加载。\n\n请重启前端服务器并清除浏览器缓存。');
+        }
+    </script>
+</body>
+</html>

+ 227 - 0
public/quick-test.html

@@ -0,0 +1,227 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>一键测试401跳转</title>
+    <style>
+        * { margin: 0; padding: 0; box-sizing: border-box; }
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 20px;
+        }
+        .container {
+            background: white;
+            border-radius: 16px;
+            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
+            padding: 40px;
+            max-width: 600px;
+            width: 100%;
+        }
+        h1 {
+            color: #2c3e50;
+            margin-bottom: 10px;
+            font-size: 28px;
+        }
+        .subtitle {
+            color: #7f8c8d;
+            margin-bottom: 30px;
+            font-size: 14px;
+        }
+        .test-box {
+            background: #f8f9fa;
+            border-radius: 12px;
+            padding: 25px;
+            margin: 20px 0;
+            border: 2px solid #e9ecef;
+        }
+        .test-box h3 {
+            color: #495057;
+            margin-bottom: 15px;
+            font-size: 18px;
+        }
+        .test-box p {
+            color: #6c757d;
+            margin-bottom: 15px;
+            line-height: 1.6;
+        }
+        button {
+            width: 100%;
+            padding: 16px;
+            font-size: 16px;
+            font-weight: 600;
+            cursor: pointer;
+            border: none;
+            border-radius: 8px;
+            transition: all 0.3s;
+            text-transform: uppercase;
+            letter-spacing: 0.5px;
+        }
+        .btn-primary {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+        }
+        .btn-primary:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
+        }
+        .btn-success {
+            background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
+            color: white;
+        }
+        .btn-success:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 10px 20px rgba(17, 153, 142, 0.4);
+        }
+        .status {
+            margin-top: 20px;
+            padding: 15px;
+            border-radius: 8px;
+            font-size: 14px;
+            display: none;
+        }
+        .status.show {
+            display: block;
+        }
+        .status.info {
+            background: #d1ecf1;
+            color: #0c5460;
+            border: 1px solid #bee5eb;
+        }
+        .status.success {
+            background: #d4edda;
+            color: #155724;
+            border: 1px solid #c3e6cb;
+        }
+        .status.error {
+            background: #f8d7da;
+            color: #721c24;
+            border: 1px solid #f5c6cb;
+        }
+        .countdown {
+            font-size: 48px;
+            font-weight: bold;
+            text-align: center;
+            margin: 20px 0;
+            color: #667eea;
+        }
+        .steps {
+            background: #fff3cd;
+            border: 1px solid #ffeaa7;
+            border-radius: 8px;
+            padding: 15px;
+            margin: 20px 0;
+        }
+        .steps h4 {
+            color: #856404;
+            margin-bottom: 10px;
+        }
+        .steps ol {
+            margin-left: 20px;
+            color: #856404;
+        }
+        .steps li {
+            margin: 5px 0;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>🚀 一键测试401跳转</h1>
+        <p class="subtitle">快速验证前端401响应处理是否正常</p>
+
+        <div class="steps">
+            <h4>⚠️ 测试前确认:</h4>
+            <ol>
+                <li>前端开发服务器正在运行</li>
+                <li>已重启服务器并清除浏览器缓存</li>
+                <li>后台服务正常运行</li>
+            </ol>
+        </div>
+
+        <div class="test-box">
+            <h3>测试1: 基本跳转功能</h3>
+            <p>测试 window.location.href 是否能正常跳转到登录页</p>
+            <button class="btn-primary" onclick="test1()">开始测试</button>
+            <div id="status1" class="status"></div>
+            <div id="countdown1" class="countdown"></div>
+        </div>
+
+        <div class="test-box">
+            <h3>测试2: 完整401流程</h3>
+            <p>设置无效token并刷新页面,测试完整的401处理流程</p>
+            <button class="btn-success" onclick="test2()">开始测试</button>
+            <div id="status2" class="status"></div>
+            <div id="countdown2" class="countdown"></div>
+        </div>
+    </div>
+
+    <script>
+        function showStatus(id, message, type) {
+            const status = document.getElementById(id);
+            status.textContent = message;
+            status.className = `status show ${type}`;
+        }
+
+        function showCountdown(id, seconds, callback) {
+            const countdown = document.getElementById(id);
+            countdown.textContent = seconds;
+            
+            const timer = setInterval(() => {
+                seconds--;
+                countdown.textContent = seconds;
+                
+                if (seconds <= 0) {
+                    clearInterval(timer);
+                    countdown.textContent = '';
+                    callback();
+                }
+            }, 1000);
+        }
+
+        function test1() {
+            console.log('=== 测试1: 基本跳转功能 ===');
+            showStatus('status1', '准备跳转到登录页...', 'info');
+            
+            showCountdown('countdown1', 3, () => {
+                console.log('执行跳转: window.location.href = "/login"');
+                showStatus('status1', '正在跳转...', 'success');
+                window.location.href = '/login';
+            });
+        }
+
+        function test2() {
+            console.log('=== 测试2: 完整401流程 ===');
+            
+            // 设置无效token
+            console.log('步骤1: 设置无效token');
+            localStorage.setItem('sso_access_token', 'invalid_token_for_test_' + Date.now());
+            showStatus('status2', '已设置无效token,准备刷新页面...', 'info');
+            
+            showCountdown('countdown2', 3, () => {
+                console.log('步骤2: 刷新页面');
+                showStatus('status2', '正在刷新页面...', 'success');
+                console.log('页面刷新后应该自动跳转到登录页');
+                location.reload();
+            });
+        }
+
+        // 页面加载时的提示
+        window.addEventListener('load', () => {
+            console.log('=== 401跳转测试页面已加载 ===');
+            console.log('请点击按钮开始测试');
+            console.log('');
+            console.log('测试1: 验证基本跳转功能');
+            console.log('测试2: 验证完整401处理流程');
+            console.log('');
+            console.log('如果测试1成功但测试2失败,说明前端代码有问题');
+            console.log('如果测试1失败,说明浏览器阻止了跳转');
+        });
+    </script>
+</body>
+</html>

+ 76 - 0
public/test-redirect.html

@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>测试跳转功能</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            max-width: 600px;
+            margin: 50px auto;
+            padding: 20px;
+            text-align: center;
+        }
+        button {
+            padding: 15px 30px;
+            margin: 10px;
+            font-size: 18px;
+            cursor: pointer;
+            background: #409eff;
+            color: white;
+            border: none;
+            border-radius: 5px;
+        }
+        button:hover {
+            background: #66b1ff;
+        }
+        .info {
+            background: #f0f9ff;
+            padding: 15px;
+            margin: 20px 0;
+            border-left: 4px solid #409eff;
+            text-align: left;
+        }
+        .countdown {
+            font-size: 24px;
+            color: #409eff;
+            margin: 20px 0;
+        }
+    </style>
+</head>
+<body>
+    <h1>跳转功能测试</h1>
+    
+    <div class="info">
+        <p><strong>测试说明:</strong></p>
+        <p>点击下面的按钮,页面将在3秒后跳转到登录页</p>
+        <p>这个测试用于验证 window.location.href 跳转是否正常工作</p>
+    </div>
+
+    <button onclick="testRedirect()">测试跳转到登录页</button>
+    
+    <div id="countdown" class="countdown"></div>
+
+    <script>
+        function testRedirect() {
+            let seconds = 3;
+            const countdownEl = document.getElementById('countdown');
+            
+            const timer = setInterval(() => {
+                countdownEl.textContent = `${seconds} 秒后跳转...`;
+                seconds--;
+                
+                if (seconds < 0) {
+                    clearInterval(timer);
+                    countdownEl.textContent = '正在跳转...';
+                    console.log('执行跳转: window.location.href = "/login"');
+                    window.location.href = '/login';
+                }
+            }, 1000);
+            
+            countdownEl.textContent = `${seconds} 秒后跳转...`;
+        }
+    </script>
+</body>
+</html>

+ 115 - 0
src/api/request-simple.ts

@@ -0,0 +1,115 @@
+/**
+ * 简化版request - 用于测试401跳转
+ * 如果标准版本不工作,可以临时替换使用这个版本
+ */
+import axios from 'axios'
+import type { InternalAxiosRequestConfig, AxiosResponse } from 'axios'
+import { ElMessage } from 'element-plus'
+import { getToken, getRefreshToken, setToken as saveToken } from '@/utils/auth'
+
+// 获取基础 URL
+const getBaseURL = () => {
+  let url = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
+  if (url.includes('localhost') && typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
+    url = url.replace('localhost', window.location.hostname)
+  }
+  return url
+}
+
+// 创建axios实例
+const request = axios.create({
+  baseURL: getBaseURL(),
+  timeout: 30000,
+  headers: {
+    'Content-Type': 'application/json'
+  }
+})
+
+// 请求拦截器
+request.interceptors.request.use(
+  (config: InternalAxiosRequestConfig) => {
+    const token = getToken()
+    if (token && config.headers) {
+      config.headers.Authorization = `Bearer ${token}`
+    }
+    return config
+  },
+  (error) => {
+    return Promise.reject(error)
+  }
+)
+
+// 响应拦截器 - 简化版
+request.interceptors.response.use(
+  (response: AxiosResponse) => {
+    // Token自动更新
+    const newToken = response.headers['x-new-token']
+    if (newToken) {
+      const refreshToken = getRefreshToken()
+      saveToken(newToken, refreshToken || undefined)
+      console.log('[简化版] Token已自动刷新')
+    }
+    
+    const { code, message } = response.data ?? {}
+    if (code === 0 || code === 200 || typeof code === 'undefined') {
+      return response.data
+    }
+    
+    const errorMsg = message || '请求失败'
+    if (!response.config.headers?.['Skip-Error-Message']) {
+      ElMessage.error(errorMsg)
+    }
+    const businessError = new Error(errorMsg)
+    // @ts-ignore
+    businessError.code = code
+    // @ts-ignore
+    businessError.isBusinessError = true
+    return Promise.reject(businessError)
+  },
+  (error) => {
+    console.log('[简化版] 拦截器捕获错误:', error.response?.status)
+    
+    // 401错误 - 最简单的处理
+    if (error.response?.status === 401) {
+      console.log('[简化版] 检测到401,立即跳转')
+      
+      // 清除所有认证信息
+      localStorage.clear()
+      sessionStorage.clear()
+      
+      // 显示提示
+      ElMessage.error('登录已过期,请重新登录')
+      
+      // 立即跳转
+      window.location.href = '/login'
+      
+      // 阻止后续执行
+      return new Promise(() => {})
+    }
+    
+    // 其他错误
+    const skipErrorMessage = error.config?.headers?.['Skip-Error-Message']
+    if (error.response) {
+      const { status, data } = error.response
+      if (!skipErrorMessage) {
+        if (status === 403) {
+          ElMessage.error('权限不足')
+        } else if (status === 404) {
+          ElMessage.error('请求的资源不存在')
+        } else if (status === 500) {
+          ElMessage.error('服务器内部错误')
+        } else {
+          ElMessage.error(data?.message || '请求失败')
+        }
+      }
+    } else {
+      if (!skipErrorMessage) {
+        ElMessage.error('网络连接失败')
+      }
+    }
+    
+    return Promise.reject(error)
+  }
+)
+
+export default request

+ 128 - 55
src/api/request.ts

@@ -1,8 +1,8 @@
 import axios from 'axios'
-import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosRequestConfig } from 'axios'
+import type { InternalAxiosRequestConfig, AxiosResponse } from 'axios'
 import { ElMessage } from 'element-plus'
 import { useAuthStore } from '@/stores/auth'
-import { getToken, getRefreshToken } from '@/utils/auth'
+import { getToken, getRefreshToken, setToken as saveToken } from '@/utils/auth'
 import router from '@/router'
 
 // 获取基础 URL
@@ -41,14 +41,73 @@ request.interceptors.request.use(
 // 响应拦截器
 request.interceptors.response.use(
   (response: AxiosResponse) => {
+    // 检查响应头中是否有新的token(滑动过期机制)
+    // 注意:axios会将响应头的key转为小写
+    const newToken = response.headers['x-new-token'] || response.headers['X-New-Token']
+    
+    console.log('>>> [前端响应拦截器] 检查新token')
+    console.log('>>> [前端响应拦截器] 响应头keys:', Object.keys(response.headers))
+    console.log('>>> [前端响应拦截器] x-new-token:', response.headers['x-new-token'])
+    console.log('>>> [前端响应拦截器] X-New-Token:', response.headers['X-New-Token'])
+    console.log('>>> [前端响应拦截器] newToken存在:', !!newToken)
+    
+    if (newToken) {
+      console.log('>>> [前端响应拦截器] 发现新token,准备更新')
+      console.log('>>> [前端响应拦截器] 新token前20字符:', newToken.substring(0, 20))
+      
+      // 获取旧token用于对比
+      const oldToken = getToken()
+      console.log('>>> [前端响应拦截器] 旧token前20字符:', oldToken?.substring(0, 20))
+      
+      // 更新本地存储的token,保留原有的refresh token
+      const refreshToken = getRefreshToken()
+      saveToken(newToken, refreshToken || undefined)
+      
+      // 验证是否更新成功
+      const updatedToken = getToken()
+      console.log('>>> [前端响应拦截器] 更新后token前20字符:', updatedToken?.substring(0, 20))
+      console.log('>>> [前端响应拦截器] Token更新成功:', updatedToken === newToken)
+      
+      console.log('✅ Token已自动刷新')
+    } else {
+      console.log('>>> [前端响应拦截器] 没有新token')
+    }
+    
     const { code, message } = response.data ?? {}
 
+    // 检查业务错误码是否为401(无效的访问令牌)
+    if (code === 401) {
+      console.log('>>> 检测到业务错误码401(HTTP状态码为200)')
+      console.log('>>> 立即跳转到登录页')
+      
+      // 清除所有存储
+      try {
+        localStorage.clear()
+        sessionStorage.clear()
+      } catch (e) {
+        console.error('清除存储失败:', e)
+      }
+      
+      // 显示错误提示
+      const errorMsg = message || '无效的访问令牌'
+      if (!response.config.headers?.['Skip-Error-Message']) {
+        ElMessage.error(errorMsg + ',请重新登录')
+      }
+      
+      // 立即跳转
+      console.log('>>> 执行跳转: window.location.href = "/login"')
+      window.location.href = '/login'
+      
+      // 阻止后续执行
+      return new Promise(() => {})
+    }
+
     // 成功响应(兼容 code=0 和 code=200,以及无 code 的纯数据/Blob 返回)
     if (code === 0 || code === 200 || typeof code === 'undefined') {
       return response.data
     }
     
-    // 业务错误
+    // 其他业务错误
     const errorMsg = message || '请求失败'
     if (!response.config.headers?.['Skip-Error-Message']) {
       ElMessage.error(errorMsg)
@@ -60,64 +119,78 @@ request.interceptors.response.use(
     businessError.isBusinessError = true
     return Promise.reject(businessError)
   },
-  async (error) => {
+  (error) => {
     const { response } = error
     const skipErrorMessage = error.config?.headers?.['Skip-Error-Message']
     
+    console.log('=== 响应拦截器捕获错误 ===', {
+      status: response?.status,
+      url: error.config?.url,
+      hasResponse: !!response
+    })
+    
+    // 处理401错误 - 最激进的方式,确保一定跳转
+    if (response && response.status === 401) {
+      console.log('>>> 检测到401错误')
+      console.log('>>> 当前URL:', window.location.href)
+      
+      // 1. 立即清除所有存储
+      console.log('>>> 清除所有存储')
+      try {
+        localStorage.clear()
+        sessionStorage.clear()
+      } catch (e) {
+        console.error('清除存储失败:', e)
+      }
+      
+      // 2. 显示错误提示(不等待)
+      console.log('>>> 显示错误提示')
+      try {
+        ElMessage.error('无效的访问令牌,请重新登录')
+      } catch (e) {
+        console.error('显示消息失败:', e)
+      }
+      
+      // 3. 立即跳转 - 使用多种方式确保成功
+      console.log('>>> 准备跳转到登录页')
+      
+      // 方式1: 立即使用window.location.href
+      console.log('>>> 方式1: window.location.href')
+      window.location.href = '/login'
+      
+      // 方式2: 备用 - 使用location.replace(会替换历史记录)
+      setTimeout(() => {
+        console.log('>>> 方式2: location.replace (备用)')
+        location.replace('/login')
+      }, 50)
+      
+      // 方式3: 最后的保障 - 使用window.location.assign
+      setTimeout(() => {
+        console.log('>>> 方式3: window.location.assign (最后保障)')
+        window.location.assign('/login')
+      }, 100)
+      
+      console.log('>>> 401处理完成,已触发跳转')
+      
+      // 阻止后续代码执行
+      return new Promise(() => {})
+    }
+    
+    // 处理其他HTTP错误
     if (response) {
       const { status, data } = response
       
-      switch (status) {
-        case 401:
-          // 未授权,尝试刷新token
-          const authStore = useAuthStore()
-          const refreshToken = getRefreshToken()
-          
-          if (refreshToken && !error.config._retry) {
-            error.config._retry = true
-            
-            try {
-              const success = await authStore.refreshToken()
-              if (success) {
-                // 重新发送原请求
-                const token = getToken()
-                if (token) {
-                  error.config.headers.Authorization = `Bearer ${token}`
-                }
-                return request(error.config)
-              }
-            } catch (refreshError) {
-              console.error('刷新token失败:', refreshError)
-            }
-          }
-          
-          // 刷新失败或没有refresh token,跳转到登录页
-          await authStore.logout()
-          router.push('/login')
-          if (!skipErrorMessage) {
-            ElMessage.error('登录已过期,请重新登录')
-          }
-          break
-          
-        case 403:
-          if (!skipErrorMessage) ElMessage.error('权限不足')
-          router.push('/unauthorized')
-          break
-          
-        case 404:
-          if (!skipErrorMessage) ElMessage.error('请求的资源不存在')
-          break
-          
-        case 429:
-          if (!skipErrorMessage) ElMessage.error('请求过于频繁,请稍后再试')
-          break
-          
-        case 500:
-          if (!skipErrorMessage) ElMessage.error('服务器内部错误')
-          break
-          
-        default:
-          if (!skipErrorMessage) ElMessage.error(data?.message || '请求失败')
+      if (status === 403) {
+        if (!skipErrorMessage) ElMessage.error('权限不足')
+        router.push('/unauthorized')
+      } else if (status === 404) {
+        if (!skipErrorMessage) ElMessage.error('请求的资源不存在')
+      } else if (status === 429) {
+        if (!skipErrorMessage) ElMessage.error('请求过于频繁,请稍后再试')
+      } else if (status === 500) {
+        if (!skipErrorMessage) ElMessage.error('服务器内部错误')
+      } else {
+        if (!skipErrorMessage) ElMessage.error(data?.message || '请求失败')
       }
     } else {
       // 网络错误