2026-03-07 08:37:40 +00:00
|
|
|
|
<!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; }
|
2026-03-18 15:18:41 +08:00
|
|
|
|
.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; }
|
2026-03-07 08:37:40 +00:00
|
|
|
|
</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>
|
|
|
|
|
|
|
2026-03-18 15:18:41 +08:00
|
|
|
|
<div id="profileBanner" class="profile-banner">正在读取 profile...</div>
|
|
|
|
|
|
<div class="tools">
|
|
|
|
|
|
<button class="btn btn-primary" onclick="copyCurl()">复制当前示例 cURL(GET /api/users)</button>
|
|
|
|
|
|
<a class="btn btn-primary" href="verify-lab.html">进入修复验证实验室</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-07 08:37:40 +00:00
|
|
|
|
<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');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 15:18:41 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-07 08:37:40 +00:00
|
|
|
|
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}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-18 15:18:41 +08:00
|
|
|
|
|
|
|
|
|
|
loadProfileBanner();
|
2026-03-07 08:37:40 +00:00
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|