| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>API测试工具 - svc-code</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
-
- body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
- line-height: 1.6;
- color: #333;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- padding: 20px;
- }
-
- .container {
- max-width: 1000px;
- margin: 0 auto;
- background: white;
- border-radius: 12px;
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
- overflow: hidden;
- }
-
- .header {
- background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
- color: white;
- padding: 30px;
- text-align: center;
- }
-
- .header h1 {
- font-size: 2.5rem;
- margin-bottom: 10px;
- }
-
- .header p {
- opacity: 0.9;
- font-size: 1.1rem;
- }
-
- .content {
- padding: 30px;
- }
-
- .section {
- margin-bottom: 30px;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- overflow: hidden;
- }
-
- .section-header {
- background: #f9fafb;
- padding: 15px 20px;
- border-bottom: 1px solid #e5e7eb;
- font-weight: 600;
- color: #374151;
- }
-
- .section-body {
- padding: 20px;
- }
-
- .config-info {
- background: #f0f9ff;
- border: 1px solid #bae6fd;
- border-radius: 6px;
- padding: 15px;
- margin-bottom: 20px;
- }
-
- .config-info code {
- background: #e0f2fe;
- padding: 2px 6px;
- border-radius: 4px;
- font-family: 'Monaco', 'Menlo', monospace;
- font-size: 0.9em;
- }
-
- .buttons {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- margin-bottom: 20px;
- }
-
- button {
- padding: 12px 24px;
- border: none;
- border-radius: 6px;
- font-size: 1rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- gap: 8px;
- }
-
- button:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
- }
-
- button:active {
- transform: translateY(0);
- }
-
- .btn-primary {
- background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
- color: white;
- }
-
- .btn-secondary {
- background: #f3f4f6;
- color: #374151;
- border: 1px solid #d1d5db;
- }
-
- .btn-success {
- background: linear-gradient(135deg, #10b981 0%, #059669 100%);
- color: white;
- }
-
- .btn-danger {
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
- color: white;
- }
-
- .btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- transform: none !important;
- box-shadow: none !important;
- }
-
- .output {
- background: #1e293b;
- color: #e2e8f0;
- border-radius: 6px;
- padding: 15px;
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
- font-size: 0.9em;
- line-height: 1.5;
- white-space: pre-wrap;
- max-height: 400px;
- overflow-y: auto;
- margin-top: 20px;
- }
-
- .status {
- padding: 10px 15px;
- border-radius: 6px;
- margin-bottom: 15px;
- display: none;
- }
-
- .status.success {
- background: #d1fae5;
- color: #065f46;
- border: 1px solid #a7f3d0;
- display: block;
- }
-
- .status.error {
- background: #fee2e2;
- color: #991b1b;
- border: 1px solid #fecaca;
- display: block;
- }
-
- .status.info {
- background: #dbeafe;
- color: #1e40af;
- border: 1px solid #bfdbfe;
- display: block;
- }
-
- .instructions {
- background: #fef3c7;
- border: 1px solid #fde68a;
- border-radius: 6px;
- padding: 15px;
- margin-top: 20px;
- }
-
- .instructions h3 {
- color: #92400e;
- margin-bottom: 10px;
- }
-
- .instructions ul {
- padding-left: 20px;
- }
-
- .instructions li {
- margin-bottom: 5px;
- }
-
- .log-item {
- padding: 5px 0;
- border-bottom: 1px solid #2d3748;
- }
-
- .log-item:last-child {
- border-bottom: none;
- }
-
- .log-time {
- color: #94a3b8;
- margin-right: 10px;
- }
-
- .log-level {
- font-weight: bold;
- margin-right: 10px;
- }
-
- .log-level.info { color: #60a5fa; }
- .log-level.success { color: #34d399; }
- .log-level.error { color: #f87171; }
- .log-level.warn { color: #fbbf24; }
-
- @media (max-width: 768px) {
- .container {
- margin: 10px;
- }
-
- .header h1 {
- font-size: 2rem;
- }
-
- .buttons {
- flex-direction: column;
- }
-
- button {
- width: 100%;
- justify-content: center;
- }
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>🔧 API测试工具</h1>
- <p>svc-code 后端API独立测试</p>
- </div>
-
- <div class="content">
- <!-- 配置信息 -->
- <div class="section">
- <div class="section-header">📋 配置信息</div>
- <div class="section-body">
- <div class="config-info">
- <p><strong>后端服务:</strong> <code id="backend-url">http://localhost:8020</code></p>
- <p><strong>API前缀:</strong> <code>/api</code></p>
- <p><strong>测试端点:</strong> <code>/api/health</code>, <code>/api/projects</code></p>
- <p><strong>状态:</strong> <span id="connection-status">等待测试...</span></p>
- </div>
- </div>
- </div>
-
- <!-- 状态显示 -->
- <div id="status-area"></div>
-
- <!-- 测试控制 -->
- <div class="section">
- <div class="section-header">🚀 测试控制</div>
- <div class="section-body">
- <div class="buttons">
- <button id="btn-test-all" class="btn-primary">
- <span>▶️</span> 运行所有测试
- </button>
- <button id="btn-test-health" class="btn-secondary">
- <span>❤️</span> 健康检查
- </button>
- <button id="btn-test-projects" class="btn-success">
- <span>📁</span> 项目列表
- </button>
- <button id="btn-clear" class="btn-danger">
- <span>🗑️</span> 清空日志
- </button>
- </div>
-
- <div id="output" class="output">
- <div id="logs">
- <div class="log-item">
- <span class="log-time" id="current-time">--:--:--</span>
- <span class="log-level info">[INFO]</span>
- <span>API测试工具已就绪!</span>
- </div>
- <div class="log-item">
- <span class="log-time">--:--:--</span>
- <span class="log-level info">[INFO]</span>
- <span>点击上方按钮开始测试</span>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 使用说明 -->
- <div class="section">
- <div class="section-header">📖 使用说明</div>
- <div class="section-body">
- <div class="instructions">
- <h3>如何运行测试:</h3>
- <ul>
- <li><strong>运行所有测试</strong>:点击"运行所有测试"按钮,依次测试健康检查和项目列表API</li>
- <li><strong>单独测试</strong>:点击"健康检查"或"项目列表"按钮测试特定API</li>
- <li><strong>控制台测试</strong>:在浏览器控制台中输入 <code>window.testAPI.runAllTests()</code></li>
- </ul>
-
- <h3>前提条件:</h3>
- <ul>
- <li>后端服务必须正在运行(端口8020)</li>
- <li>确保没有跨域问题(CORS已配置)</li>
- <li>如果测试失败,请检查后端日志</li>
- </ul>
-
- <h3>快速命令:</h3>
- <pre style="background:#1e293b;color:#e2e8f0;padding:10px;border-radius:4px;margin-top:10px;">
- # 启动后端服务
- cd svc-code && go run main.go
-
- # 测试API(命令行)
- curl http://localhost:8020/api/health
- curl http://localhost:8020/api/projects</pre>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <script type="module">
- // 更新时间显示
- function updateTime() {
- const now = new Date();
- const timeStr = now.toTimeString().split(' ')[0];
- document.querySelectorAll('#current-time').forEach(el => {
- el.textContent = timeStr;
- });
- }
- setInterval(updateTime, 1000);
- updateTime();
-
- // 添加日志
- function addLog(level, message) {
- const now = new Date();
- const timeStr = now.toTimeString().split(' ')[0];
- const logItem = document.createElement('div');
- logItem.className = 'log-item';
- logItem.innerHTML = `
- <span class="log-time">${timeStr}</span>
- <span class="log-level ${level}">[${level.toUpperCase()}]</span>
- <span>${message}</span>
- `;
- document.getElementById('logs').prepend(logItem);
-
- // 限制日志数量
- const logs = document.getElementById('logs');
- if (logs.children.length > 50) {
- logs.removeChild(logs.lastChild);
- }
- }
-
- // 显示状态
- function showStatus(type, message) {
- const statusArea = document.getElementById('status-area');
- const statusDiv = document.createElement('div');
- statusDiv.className = `status ${type}`;
- statusDiv.textContent = message;
- statusArea.innerHTML = '';
- statusArea.appendChild(statusDiv);
- }
-
- // API请求函数
- async function apiRequest(endpoint) {
- const backendUrl = document.getElementById('backend-url').textContent;
- const url = `${backendUrl}/api${endpoint}`;
- const startTime = Date.now();
-
- try {
- const response = await fetch(url);
- const duration = Date.now() - startTime;
-
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}`);
- }
-
- const data = await response.json();
- return { success: true, data, duration };
- } catch (error) {
- const duration = Date.now() - startTime;
- return { success: false, error: error.message, duration };
- }
- }
-
- // 测试函数
- async function testHealth() {
- showStatus('info', '正在测试健康检查API...');
- addLog('info', '开始健康检查测试');
-
- const result = await apiRequest('/health');
-
- if (result.success) {
- const isHealthy = result.data?.data?.healthy || result.data?.healthy;
- const message = `健康检查成功!状态: ${isHealthy ? '健康' : '不健康'}, 耗时: ${result.duration}ms`;
- showStatus('success', message);
- addLog('success', message);
- return true;
- } else {
- const message = `健康检查失败: ${result.error}`;
- showStatus('error', message);
- addLog('error', message);
- return false;
- }
- }
-
- async function testProjects() {
- showStatus('info', '正在测试项目列表API...');
- addLog('info', '开始项目列表测试');
-
- const result = await apiRequest('/projects');
-
- if (result.success) {
- const projects = result.data?.data || result.data || [];
- const message = `获取到 ${projects.length} 个项目, 耗时: ${result.duration}ms`;
- showStatus('success', message);
- addLog('success', message);
-
- // 显示项目详情
- if (projects.length > 0) {
- projects.forEach((project, index) => {
- addLog('info', `项目 ${index + 1}: ${project.name} (${project.id})`);
- });
- }
- return true;
- } else {
- const message = `项目列表测试失败: ${result.error}`;
- showStatus('error', message);
- addLog('error', message);
- return false;
- }
- }
-
- async function runAllTests() {
- showStatus('info', '开始运行所有测试...');
- addLog('info', '='.repeat(50));
- addLog('info', '开始运行所有API测试');
- addLog('info', '='.repeat(50));
-
- // 测试健康检查
- const healthOk = await testHealth();
-
- // 只有健康检查通过才测试项目列表
- let projectsOk = false;
- if (healthOk) {
- projectsOk = await testProjects();
- } else {
- addLog('warn', '跳过项目列表测试(健康检查失败)');
- }
-
- // 显示结果
- addLog('info', '='.repeat(50));
- addLog('info', '测试结果汇总:');
- addLog('info', `健康检查: ${healthOk ? '✅ 通过' : '❌ 失败'}`);
- addLog('info', `项目列表: ${projectsOk ? '✅ 通过' : healthOk ? '❌ 失败' : '⏭️ 跳过'}`);
-
- const allPassed = healthOk && projectsOk;
- addLog(allPassed ? 'success' : 'warn',
- allPassed ? '🎉 所有测试通过!' : '⚠️ 部分测试失败');
-
- showStatus(allPassed ? 'success' : 'error',
- allPassed ? '所有测试通过!' : '部分测试失败');
- }
-
- // 按钮事件
- document.getElementById('btn-test-all').addEventListener('click', runAllTests);
- document.getElementById('btn-test-health').addEventListener('click', testHealth);
- document.getElementById('btn-test-projects').addEventListener('click', testProjects);
- document.getElementById('btn-clear').addEventListener('click', () => {
- document.getElementById('logs').innerHTML = `
- <div class="log-item">
- <span class="log-time" id="current-time">--:--:--</span>
- <span class="log-level info">[INFO]</span>
- <span>日志已清空</span>
- </div>
- `;
- addLog('info', '日志已清空');
- document.getElementById('status-area').innerHTML = '';
- });
-
- // 更新连接状态
- async function checkConnection() {
- const backendUrl = document.getElementById('backend-url').textContent;
- const statusEl = document.getElementById('connection-status');
-
- try {
- const response = await fetch(`${backendUrl}/api/health`, { signal: AbortSignal.timeout(3000) });
- if (response.ok) {
- statusEl.textContent = '✅ 连接正常';
- statusEl.style.color = '#10b981';
- } else {
- statusEl.textContent = '⚠️ 连接异常';
- statusEl.style.color = '#f59e0b';
- }
- } catch {
- statusEl.textContent = '❌ 无法连接';
- statusEl.style.color = '#ef4444';
- }
- }
-
- // 初始连接检查
- setTimeout(checkConnection, 1000);
-
- // 在控制台中注册测试函数
- window.testAPI = {
- runAllTests,
- testHealth,
- testProjects,
- checkConnection
- };
-
- addLog('success', '测试工具已初始化,在控制台输入 window.testAPI.runAllTests() 开始测试');
- </script>
- </body>
- </html>
|