Files
2026-03-18 15:18:41 +08:00

311 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API 测试面板 - Spring Boot</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px 20px; text-align: center; margin-bottom: 20px; border-radius: 10px; }
.header h1 { font-size: 2em; }
.nav { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; justify-content: center; }
.nav a { padding: 10px 20px; background: white; border-radius: 20px; text-decoration: none; color: #333; font-size: 0.9em; }
.nav a:hover, .nav a.active { background: #667eea; color: white; }
.card { background: white; border-radius: 10px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
.card h3 { color: #667eea; margin-bottom: 15px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
.api-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 15px; }
.api-item { background: #f8f9fa; border-radius: 8px; padding: 15px; border-left: 4px solid; }
.api-item.get { border-color: #28a745; }
.api-item.post { border-color: #007bff; }
.api-item.put { border-color: #ffc107; }
.api-item.delete { border-color: #dc3545; }
.method { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 0.75em; font-weight: bold; margin-right: 8px; }
.method.get { background: #28a745; color: white; }
.method.post { background: #007bff; color: white; }
.method.put { background: #ffc107; color: #333; }
.method.delete { background: #dc3545; color: white; }
.btn { padding: 8px 16px; border: none; border-radius: 5px; cursor: pointer; font-size: 0.9em; margin-top: 10px; }
.btn-primary { background: #667eea; color: white; }
.btn-primary:hover { background: #5a6fd6; }
.result { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; margin-top: 10px; font-family: monospace; font-size: 0.85em; overflow-x: auto; max-height: 200px; overflow-y: auto; display: none; }
.result.show { display: block; }
.url { font-family: monospace; color: #666; font-size: 0.9em; word-break: break-all; }
.tabs { display: flex; gap: 5px; margin-bottom: 20px; border-bottom: 2px solid #eee; }
.tab { padding: 10px 20px; cursor: pointer; border-bottom: 2px solid transparent; }
.tab.active { border-bottom-color: #667eea; color: #667eea; font-weight: 600; }
.tab-content { display: none; }
.tab-content.active { display: block; }
.json-input { width: 100%; min-height: 100px; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-family: monospace; font-size: 0.9em; }
.profile-banner { background:#fff7e6;border-left:4px solid #fa8c16;padding:12px 14px;border-radius:8px;margin-bottom:18px; color:#874d00; }
.tools { margin: 10px 0 16px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔌 API 测试面板</h1>
<p>在线测试所有 RESTful API</p>
</div>
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="ioc.html">📦 IoC</a>
<a href="aop.html">🔪 AOP</a>
<a href="mybatis.html">💾 MyBatis</a>
<a href="transaction.html">🔄 事务</a>
<a href="users.html">👥 用户</a>
<a href="api.html" class="active">🔌 API</a>
</div>
<div id="profileBanner" class="profile-banner">正在读取 profile...</div>
<div class="tools">
<button class="btn btn-primary" onclick="copyCurl()">复制当前示例 cURLGET /api/users</button>
<a class="btn btn-primary" href="verify-lab.html">进入修复验证实验室</a>
</div>
<div class="tabs">
<div class="tab active" onclick="switchTab('user')">👥 用户 API</div>
<div class="tab" onclick="switchTab('product')">📦 产品 API</div>
<div class="tab" onclick="switchTab('order')">🛒 订单 API</div>
<div class="tab" onclick="switchTab('learning')">📚 学习 API</div>
</div>
<!-- 用户 API -->
<div id="userTab" class="tab-content active">
<div class="card">
<h3>用户管理 API</h3>
<div class="api-grid">
<div class="api-item get">
<span class="method get">GET</span>
<strong>获取所有用户</strong>
<div class="url">/api/users</div>
<button class="btn btn-primary" onclick="testApi('GET', '/api/users', null, 'userResult1')">测试</button>
<div id="userResult1" class="result"></div>
</div>
<div class="api-item get">
<span class="method get">GET</span>
<strong>获取单个用户</strong>
<div class="url">/api/users/{id}</div>
<input type="number" id="userId" placeholder="用户ID" style="width:80px;padding:5px;margin-top:5px;">
<button class="btn btn-primary" onclick="testApi('GET', '/api/users/' + document.getElementById('userId').value, null, 'userResult2')">测试</button>
<div id="userResult2" class="result"></div>
</div>
<div class="api-item post">
<span class="method post">POST</span>
<strong>创建用户</strong>
<div class="url">/api/users</div>
<textarea class="json-input" id="createUserJson">{
"username": "testuser",
"email": "test@example.com",
"phone": "13800138000",
"bio": "测试用户"
}</textarea>
<button class="btn btn-primary" onclick="testApi('POST', '/api/users', document.getElementById('createUserJson').value, 'userResult3')">测试</button>
<div id="userResult3" class="result"></div>
</div>
<div class="api-item get">
<span class="method get">GET</span>
<strong>搜索用户</strong>
<div class="url">/api/users/search?username={name}</div>
<input type="text" id="searchName" placeholder="用户名关键词" style="padding:5px;margin-top:5px;">
<button class="btn btn-primary" onclick="testApi('GET', '/api/users/search?username=' + encodeURIComponent(document.getElementById('searchName').value), null, 'userResult4')">测试</button>
<div id="userResult4" class="result"></div>
</div>
</div>
</div>
</div>
<!-- 产品 API -->
<div id="productTab" class="tab-content">
<div class="card">
<h3>产品管理 API</h3>
<div class="api-grid">
<div class="api-item get">
<span class="method get">GET</span>
<strong>获取所有产品</strong>
<div class="url">/api/products</div>
<button class="btn btn-primary" onclick="testApi('GET', '/api/products', null, 'productResult1')">测试</button>
<div id="productResult1" class="result"></div>
</div>
<div class="api-item post">
<span class="method post">POST</span>
<strong>创建产品</strong>
<div class="url">/api/products</div>
<textarea class="json-input" id="createProductJson">{
"name": "iPhone 15",
"description": "最新款苹果手机",
"price": 5999.00,
"stockQuantity": 100,
"category": "手机"
}</textarea>
<button class="btn btn-primary" onclick="testApi('POST', '/api/products', document.getElementById('createProductJson').value, 'productResult2')">测试</button>
<div id="productResult2" class="result"></div>
</div>
</div>
</div>
</div>
<!-- 订单 API -->
<div id="orderTab" class="tab-content">
<div class="card">
<h3>订单管理 API (演示事务)</h3>
<div class="api-grid">
<div class="api-item get">
<span class="method get">GET</span>
<strong>获取所有订单</strong>
<div class="url">/api/orders</div>
<button class="btn btn-primary" onclick="testApi('GET', '/api/orders', null, 'orderResult1')">测试</button>
<div id="orderResult1" class="result"></div>
</div>
<div class="api-item post">
<span class="method post">POST</span>
<strong>创建订单</strong>
<div class="url">/api/orders</div>
<textarea class="json-input" id="createOrderJson">{
"userId": 1,
"productId": 1,
"quantity": 1,
"rollback": false
}</textarea>
<button class="btn btn-primary" onclick="testApi('POST', '/api/orders', document.getElementById('createOrderJson').value, 'orderResult2')">测试</button>
<div id="orderResult2" class="result"></div>
</div>
<div class="api-item post">
<span class="method post">POST</span>
<strong>创建订单(触发回滚)</strong>
<div class="url">/api/orders (rollback=true)</div>
<textarea class="json-input" id="rollbackOrderJson">{
"userId": 1,
"productId": 1,
"quantity": 1,
"rollback": true
}</textarea>
<button class="btn btn-primary" onclick="testApi('POST', '/api/orders', document.getElementById('rollbackOrderJson').value, 'orderResult3')">测试</button>
<div id="orderResult3" class="result"></div>
</div>
</div>
</div>
</div>
<!-- 学习 API -->
<div id="learningTab" class="tab-content">
<div class="card">
<h3>学习 API</h3>
<div class="api-grid">
<div class="api-item get">
<span class="method get">GET</span>
<strong>IoC - 查看所有 Bean</strong>
<div class="url">/api/learning/ioc/beans</div>
<button class="btn btn-primary" onclick="testApi('GET', '/api/learning/ioc/beans', null, 'learnResult1')">测试</button>
<div id="learnResult1" class="result"></div>
</div>
<div class="api-item get">
<span class="method get">GET</span>
<strong>AOP - 概念</strong>
<div class="url">/api/learning/aop/concepts</div>
<button class="btn btn-primary" onclick="testApi('GET', '/api/learning/aop/concepts', null, 'learnResult2')">测试</button>
<div id="learnResult2" class="result"></div>
</div>
<div class="api-item get">
<span class="method get">GET</span>
<strong>MyBatis - 配置</strong>
<div class="url">/api/learning/mybatis/config</div>
<button class="btn btn-primary" onclick="testApi('GET', '/api/learning/mybatis/config', null, 'learnResult3')">测试</button>
<div id="learnResult3" class="result"></div>
</div>
<div class="api-item get">
<span class="method get">GET</span>
<strong>事务 - 传播行为</strong>
<div class="url">/api/learning/transaction/propagation</div>
<button class="btn btn-primary" onclick="testApi('GET', '/api/learning/transaction/propagation', null, 'learnResult4')">测试</button>
<div id="learnResult4" class="result"></div>
</div>
</div>
</div>
</div>
</div>
<script>
function switchTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(tab + 'Tab').classList.add('active');
}
async function loadProfileBanner() {
try {
const res = await fetch('/api/profile');
const data = await res.json();
const enabled = (data.enabledModules || []).join(', ');
document.getElementById('profileBanner').textContent = `当前 profile: ${data.profile} | 鉴权模式: ${data.authType || 'none'} | 已启用模块: ${enabled}`;
} catch (e) {
document.getElementById('profileBanner').textContent = '当前 profile 读取失败,请检查 /api/profile';
}
}
async function copyCurl() {
const curl = `curl -X GET "${window.location.origin}/api/users"`;
try {
await navigator.clipboard.writeText(curl);
alert('已复制: ' + curl);
} catch (e) {
alert('复制失败,请手动复制: ' + curl);
}
}
async function testApi(method, url, body, resultId) {
const resultDiv = document.getElementById(resultId);
resultDiv.classList.add('show');
resultDiv.textContent = '请求中...';
try {
const options = {
method: method,
headers: {}
};
if (body) {
options.headers['Content-Type'] = 'application/json';
options.body = body;
}
const startTime = Date.now();
const res = await fetch(url, options);
const duration = Date.now() - startTime;
const data = await res.json();
resultDiv.innerHTML = `<strong>${res.status} ${res.statusText}</strong> (${duration}ms)\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
resultDiv.innerHTML = `<strong style="color:#ff6b6b;">Error</strong>\n\n${e.message}`;
}
}
loadProfileBanner();
</script>
</body>
</html>