|
|
@@ -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>
|