ApiCraft-Web
項目介紹
ApiCraft-Web 是一個輕量級的 API 測試工具,提供了簡潔直觀的界面,幫助開發者快速測試和調試 HTTP 接口。
功能特點
- 支持多種 HTTP 請求方法(GET、POST、PUT、DELETE)
- 可配置請求參數(Query Parameters)
- 可配置請求頭(Headers)
- JSON 格式的請求體編輯器,支持語法高亮和格式化
- 美觀的響應數據展示
- 顯示響應狀態碼、響應時間和數據大小
- 跨域支持
技術棧
前端
- Vue 3
- TailwindCSS
- CodeMirror 6
- Axios
后端
- Spring Boot 2.3.4
- RestTemplate
- Lombok
快速開始
環境要求
- Node.js 14+
- JDK 8+
- Maven 3+
安裝和運行
- 克隆項目
git clone https://gitee.com/anxwefndu/ApiCraft-Web.git
cd ApiCraft-Web
- 啟動后端服務
cd SpringBoot
mvn spring-boot:run
- 啟動前端服務
cd code
npm install
npm run serve
- 訪問應用
打開瀏覽器訪問:http://localhost:8081
使用說明
-
發送請求
- 選擇請求方法(GET、POST、PUT、DELETE)
- 輸入目標 URL
- 根據需要添加查詢參數、請求頭和請求體
- 點擊"發送請求"按鈕
-
查看響應
- 響應狀態碼
- 響應時間
- 數據大小
- 格式化的響應數據
開發計劃
- 支持更多請求方法
- 請求歷史記錄
- 接口集合管理
- 環境變量配置
- 響應數據導出
- 暗色主題支持
源碼下載
ApiCraft-Web
演示截圖
1.系統首頁
2.接口請求
核心源碼
code/src/App.vue
<script setup>
import {ref, reactive} from 'vue';
import axios from 'axios';
import Message from '@/utils/message';
import JsonEditor from '@/components/JsonEditor.vue';// 請求方法
const method = ref('GET');// 請求URL
const url = ref('');// 當前選中的參數類型標簽
const activeTab = ref('params'); // params, headers, body// 請求參數
const requestData = reactive({params: [{ key: '', value: '' }],headers: [{ key: 'Content-Type', value: 'application/json' },],body: ''
});// 響應數據
const response = reactive({status: '',time: '',size: '',data: null,loading: false
});// 添加參數
const addParam = () => {requestData.params.push({ key: '', value: '' });
};// 刪除參數
const removeParam = (index) => {requestData.params.splice(index, 1);
};// 添加請求頭
const addHeader = () => {requestData.headers.push({ key: '', value: '' });
};// 刪除請求頭
const removeHeader = (index) => {requestData.headers.splice(index, 1);
};// 切換參數類型標簽
const switchTab = (tab) => {activeTab.value = tab;
};const jsonEditor = ref();
const hasJsonError = ref(false);const formatJsonBody = () => {jsonEditor.value?.formatJson();
};const handleJsonError = (error) => {hasJsonError.value = !!error;
};// 發送請求
const sendRequest = async () => {if (!url.value) {Message.warning('請輸入請求URL');return;}if (hasJsonError.value) {Message.error('請求體 JSON 格式錯誤');return;}response.loading = true;try {// 構建請求參數const queryParams = {};const headers = {};requestData.params.forEach(param => {if (param.key && param.value) {queryParams[param.key] = param.value;}});requestData.headers.forEach(header => {if (header.key && header.value) {headers[header.key] = header.value;}});const requestBody = activeTab.value === 'body' ? requestData.body : null;// 發送請求到后端代理const result = await axios.post('http://localhost:8080/api/proxy', {url: url.value,method: method.value,headers: headers,queryParams: queryParams,body: requestBody});// 更新響應數據response.status = `${result.data.status} ${result.data.status === 200 ? 'OK' : ''}`;response.time = `${result.data.responseTime}ms`;response.size = result.data.contentLength;response.data = result.data.data;Message.success('請求成功');} catch (error) {Message.error(error.message || '請求失敗');response.status = '500 Error';response.data = error.message;} finally {response.loading = false;}
};// 添加格式化響應數據的函數
const formatResponseData = (data) => {if (typeof data === 'string') {try {// 嘗試解析字符串為 JSONreturn JSON.stringify(JSON.parse(data), null, 2);} catch {// 如果不是 JSON 字符串,直接返回原始字符串,去掉多余的引號return data.replace(/^"|"$/g, '');}}// 如果是對象,格式化為 JSONreturn JSON.stringify(data, null, 2);
};
</script><template><div class="bg-gray-50" style="width: 100%; height: 100%"><div class="w-[1200px] mx-auto"><nav class="h-16 bg-white shadow flex items-center justify-between px-8"><div class="flex items-center space-x-2"><span class="text-2xl font-['Pacifico'] text-primary">logo</span><span class="text-lg font-medium">API工具</span></div><button class="w-10 h-10 rounded-button flex items-center justify-center hover:bg-gray-100 transition-colors"><i class="fas fa-sun text-gray-600"></i></button></nav><main class="py-12"><div class="mx-auto"><div class="bg-white rounded-lg shadow p-8"><h2 class="text-lg font-semibold mb-4">接口測試</h2><div class="space-y-4"><div class="grid grid-cols-1 md:grid-cols-4 gap-4"><div><label class="block text-sm font-medium text-gray-700 mb-1">請求方法</label><div class="relative"><select v-model="method"class="block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-primary focus:border-primary rounded-button"><option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option></select></div></div><div class="md:col-span-3"><label class="block text-sm font-medium text-gray-700 mb-1">請求URL</label><div class="flex"><input type="text" v-model="url"class="flex-1 min-w-0 block w-full px-3 py-2 rounded-l-button border border-gray-300 focus:outline-none focus:ring-primary focus:border-primary"><buttonclass="bg-primary hover:bg-blue-600 text-white px-4 py-2 rounded-r-button text-sm font-medium" @click="sendRequest">發送</button></div></div></div><div><label class="block text-sm font-medium text-gray-700 mb-1">請求參數</label><div class="overflow-hidden border border-gray-300 rounded-button"><div class="bg-gray-50 px-4 py-2 border-b border-gray-300"><div class="flex items-center space-x-4"><button class="text-sm font-medium text-gray-500" :class="activeTab === 'params' ? ' text-primary ' : ''" @click="switchTab('params')">Query Params</button><button class="text-sm font-medium text-gray-500" :class="activeTab === 'headers' ? ' text-primary ' : ''" @click="switchTab('headers')">Headers</button><button class="text-sm font-medium text-gray-500" :class="activeTab === 'body' ? ' text-primary ' : ''" @click="switchTab('body')">Body</button></div></div><div class="bg-white"><div class="space-y-3 p-4" v-show="activeTab === 'params'"><template v-for="(param, index) in requestData.params" :key="index"><div class="grid grid-cols-12 gap-4 items-center"><div class="col-span-3"><input type="text" v-model="param.key" placeholder="參數名"class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary"></div><div class="col-span-8"><input type="text" v-model="param.value" placeholder="參數值"class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary"></div><div class="col-span-1"><button class="text-gray-500 hover:text-gray-700" @click="removeParam(index)"><i class="fas fa-trash fa-icon"></i></button></div></div></template><button class="text-sm text-primary hover:text-blue-600 flex items-center space-x-1"><i class="fas fa-plus fa-icon"></i><span @click="addParam">添加參數</span></button></div><div class="space-y-3 p-4" v-show="activeTab === 'headers'"><template v-for="(header, index) in requestData.headers" :key="index"><div class="grid grid-cols-12 gap-4 items-center"><div class="col-span-3"><input type="text" v-model="header.key" placeholder="參數名"class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary"></div><div class="col-span-8"><input type="text" v-model="header.value" placeholder="參數值"class="block w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-primary focus:border-primary"></div><div class="col-span-1"><button class="text-gray-500 hover:text-gray-700" @click="removeHeader(index)"><i class="fas fa-trash fa-icon"></i></button></div></div></template><button class="text-sm text-primary hover:text-blue-600 flex items-center space-x-1"><i class="fas fa-plus fa-icon"></i><span @click="addHeader">添加請求頭</span></button></div><div class="space-y-3" v-if="activeTab === 'body'"><JsonEditorv-model="requestData.body"height="500px"ref="jsonEditor"@error="handleJsonError"/><div class="flex justify-end space-x-2" style="margin-bottom: 0.75rem; margin-right: 0.75rem"><button@click="formatJsonBody"class="text-sm text-primary hover:text-blue-600 flex items-center space-x-1"><i class="fas fa-code fa-icon"></i><span>格式化</span></button></div></div></div></div></div><div><label class="block text-sm font-medium text-gray-700 mb-1">響應結果</label><div v-if="response.data" class="border border-gray-300 rounded-button overflow-hidden"><div class="bg-gray-50 px-4 py-2 border-b border-gray-300 flex items-center justify-between"><div class="flex items-center space-x-2"><span class="text-sm font-medium">狀態: {{ response.status }}</span><span class="text-sm text-gray-500">時間: {{ response.time }}</span><span class="text-sm text-gray-500">大小: {{ response.size }}</span></div><button @click="sendRequest" class="text-sm text-primary hover:text-blue-600"><i class="fas fa-redo fa-icon mr-1"></i><span>重新請求</span></button></div><div class="bg-white p-4"><pre class="whitespace-pre-wrap">{{ formatResponseData(response.data) }}</pre></div></div></div></div></div><div class="mt-12 bg-white rounded-lg shadow p-8"><h2 class="text-xl font-medium mb-6">使用說明</h2><div class="space-y-4 text-gray-600"><div class="flex items-start space-x-3"><div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fas fa-check text-sm text-primary"></i></div><p>在請求 URL 輸入框中輸入完整的 API 地址,選擇對應的請求方法(GET、POST、PUT、DELETE)</p></div><div class="flex items-start space-x-3"><div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fas fa-check text-sm text-primary"></i></div><p>在請求參數區域可以設置 Query 參數、Headers 以及請求體(Body),支持多個參數的添加和刪除</p></div><div class="flex items-start space-x-3"><div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fas fa-check text-sm text-primary"></i></div><p>發送請求后,可以在響應結果區域查看狀態碼、響應時間、數據大小等信息,支持重新發送請求</p></div><div class="flex items-start space-x-3"><div class="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fas fa-check text-sm text-primary"></i></div><p>響應數據會以格式化的 JSON 形式展示,方便查看和分析接口返回的數據結構</p></div></div></div></div></main></div></div>
</template><style>
body {min-height: 100vh;
}#app {min-height: 100vh;
}body::-webkit-scrollbar {width: 15px;
}body::-webkit-scrollbar-track {background: #f1f5f9;border-radius: 8px;
}body::-webkit-scrollbar-thumb {background: #6366f1;border-radius: 8px;border: 2px solid #f1f5f9;
}body::-webkit-scrollbar-thumb:hover {background: #4f46e5;
}
</style>
SpringBoot/src/main/java/com/boot/service/ApiService.java
package com.boot.service;import com.boot.model.ApiRequest;
import com.boot.model.ApiResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;import java.util.Map;@Service
public class ApiService {private final RestTemplate restTemplate;public ApiService() {this.restTemplate = new RestTemplate();}public ApiResponse executeRequest(ApiRequest request) {long startTime = System.currentTimeMillis();ApiResponse response = new ApiResponse();try {// 構建請求頭HttpHeaders headers = new HttpHeaders();if (request.getHeaders() != null) {request.getHeaders().forEach(headers::add);}// 構建URL(包含查詢參數)UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(request.getUrl());if (request.getQueryParams() != null) {for (Map.Entry<String, String> entry : request.getQueryParams().entrySet()) {builder.queryParam(entry.getKey(), entry.getValue());}}// 構建請求實體HttpEntity<?> httpEntity = new HttpEntity<>(request.getBody(), headers);// 不要對 URL 組件進行編碼String finalUrl = builder.build(false).toUri().toString();// 修改執行請求部分,使用 String.class 接收響應ResponseEntity<String> responseEntity = restTemplate.exchange(finalUrl,HttpMethod.valueOf(request.getMethod().toUpperCase()),httpEntity,String.class);// 設置響應信息response.setStatus(responseEntity.getStatusCodeValue());// 嘗試將響應轉換為 JSON 對象String responseBody = responseEntity.getBody();try {ObjectMapper mapper = new ObjectMapper();Object jsonData = mapper.readValue(responseBody, Object.class);response.setData(jsonData);} catch (JsonProcessingException e) {// 如果不是 JSON 格式,直接返回字符串response.setData(responseBody);}response.setResponseTime(System.currentTimeMillis() - startTime);response.setContentLength(responseEntity.getHeaders().getContentLength() != -1? responseEntity.getHeaders().getContentLength() + " bytes": "unknown");} catch (Exception e) {e.printStackTrace();response.setStatus(500);String message = e.getMessage();if (message.contains("[")) {response.setData(message.substring(message.indexOf("[")));}response.setResponseTime(System.currentTimeMillis() - startTime);}return response;}
}