update:优化好了,决定使用d1存储
和claude互相折磨了一天,终于糊出来了个差不多能用的
参考的这个帖子的佬的源码:来看看你使用的api转了几手,是官转、azure还是逆向,还是掺假的!
部署要创建并绑定一个D1数据库
配置列如下,名称应填为:requests
const CONFIG = {
WORKER_DOMAIN: '<填你自己的域名>', // worker 的域名
};
const HTML_CONTENT = `
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API 测试工具</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<style>
body {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background-color: #FAF9F6;
}
.loading-dots:after {
content: ' .';
animation: dots 1.5s steps(5, end) infinite;
}
@keyframes dots {
0%, 20% { content: ' .' }
40% { content: ' ..' }
60% { content: ' ...' }
80%, 100% { content: ' ....' }
}
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
pre {
transition: all 0.2s ease;
}
input:focus, button:focus {
outline: none;
ring-color: #B47464;
}
.btn-primary {
background-color: #B47464;
transition: background-color 0.2s ease;
}
.btn-primary:hover {
background-color: #9A6557;
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-12">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-7xl mx-auto">
<!-- 左侧面板:表单和日志 -->
<div class="bg-white rounded-2xl shadow-lg p-8">
<h1 class="text-4xl font-semibold mb-8 text-center text-gray-800">API 测试工具</h1>
<form id="apiForm" class="space-y-6">
<div>
<label class="block text-base font-medium text-gray-700 mb-2">API URL</label>
<input type="text" id="url"
class="block w-full rounded-xl border-gray-300 shadow-sm p-3 border focus:border-[#B47464] focus:ring-2 focus:ring-[#B47464]"
placeholder="https://api.example.com/v1/chat/completions">
</div>
<div>
<label class="block text-base font-medium text-gray-700 mb-2">API Key</label>
<input type="password" id="key"
class="block w-full rounded-xl border-gray-300 shadow-sm p-3 border focus:border-[#B47464] focus:ring-2 focus:ring-[#B47464]">
</div>
<div>
<label class="block text-base font-medium text-gray-700 mb-2">模型</label>
<input type="text" id="model"
class="block w-full rounded-xl border-gray-300 shadow-sm p-3 border focus:border-[#B47464] focus:ring-2 focus:ring-[#B47464]"
value="gpt-4o" placeholder="gpt-4o">
</div>
<button type="submit"
class="w-full btn-primary text-white rounded-xl py-3 text-lg font-medium focus:outline-none focus:ring-2 focus:ring-[#B47464] focus:ring-offset-2">
发送请求
</button>
</form>
<div class="mt-8 space-y-6">
<div class="border-t border-gray-100 pt-6">
<h2 class="text-xl font-medium mb-3 text-gray-800">请求状态</h2>
<pre id="status" class="bg-gray-50 p-4 rounded-xl overflow-x-auto text-gray-700"></pre>
</div>
<div class="border-t border-gray-100 pt-6">
<h2 class="text-xl font-medium mb-3 text-gray-800">
请求日志
<span id="logsStatus" class="text-sm font-normal text-[#B47464] loading-dots">正在获取日志</span>
</h2>
<div id="logsContainer" class="bg-gray-50 p-4 rounded-xl">
<pre id="logs" class="overflow-x-auto text-gray-700"></pre>
</div>
</div>
</div>
</div>
<!-- 右侧面板:响应结果 -->
<div id="result" class="hidden lg:block bg-white rounded-2xl shadow-lg p-8 h-full sticky top-4">
<h2 class="text-xl font-medium mb-3 text-gray-800">响应结果</h2>
<pre id="response" class="bg-gray-50 p-4 rounded-xl overflow-x-auto text-gray-700 h-[calc(100vh-12rem)] overflow-y-auto"></pre>
</div>
</div>
</div>
<script>
const form = document.getElementById('apiForm');
const result = document.getElementById('result');
const statusEl = document.getElementById('status');
const logsEl = document.getElementById('logs');
const logsStatus = document.getElementById('logsStatus');
const responseEl = document.getElementById('response');
form.addEventListener('submit', async (e) => {
e.preventDefault();
result.classList.remove('hidden');
logsStatus.style.display = 'inline';
logsEl.textContent = '';
responseEl.textContent = '';
const url = document.getElementById('url').value;
const key = document.getElementById('key').value;
const model = document.getElementById('model').value;
try {
const response = await fetch('/v1/serve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, key, model })
});
if (!response.body) {
throw new Error('ReadableStream 不可用');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let done = false;
let buffer = '';
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
if (value) {
buffer += decoder.decode(value, { stream: true });
let lines = buffer.split('\\n');
buffer = lines.pop();
lines.forEach(line => {
if (line.trim() !== '') {
try {
const data = JSON.parse(line);
if (data.type === 'status') {
statusEl.textContent = data.message;
} else if (data.type === 'log') {
logsEl.textContent += data.message + '\\n';
logsEl.scrollTop = logsEl.scrollHeight;
} else if (data.type === 'response') {
responseEl.textContent = JSON.stringify(data.message, null, 2);
logsStatus.style.display = 'none';
}
} catch (parseError) {
console.error('JSON 解析错误:', parseError, '内容:', line);
statusEl.textContent = '错误: JSON 解析失败';
logsStatus.style.display = 'none';
}
}
});
}
}
if (buffer.trim() !== '') {
try {
const data = JSON.parse(buffer);
if (data.type === 'status') {
statusEl.textContent = data.message;
} else if (data.type === 'log') {
logsEl.textContent += data.message + '\\n';
logsEl.scrollTop = logsEl.scrollHeight;
} else if (data.type === 'response') {
responseEl.textContent = JSON.stringify(data.message, null, 2);
logsStatus.style.display = 'none';
}
} catch (parseError) {
console.error('JSON 解析错误:', parseError, '内容:', buffer);
statusEl.textContent = '错误: JSON 解析失败';
logsStatus.style.display = 'none';
}
}
} catch (error) {
statusEl.textContent = '错误: ' + error.message;
logsStatus.style.display = 'none';
}
});
</script>
</body>
</html>
`;
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
// 存储请求信息到 D1
async function storeRequest(env, traceId, request) {
console.log('Storing request:', { traceId, ip: request.headers.get('cf-connecting-ip') });
try {
await env.DB.prepare(`
INSERT INTO requests (trace_id, ip, asn, as_org, city, country, colo, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).bind(
traceId,
request.headers.get('cf-connecting-ip'),
request.cf.asn,
request.cf.asOrganization,
request.cf.city,
request.cf.country,
request.cf.colo,
Date.now()
).run();
console.log('Request stored successfully');
} catch (error) {
console.error('Error storing request:', error);
throw error; // 重新抛出错误以便追踪
}
}
// 获取特定 traceId 的所有请求
async function getRequests(env, traceId) {
console.log('Getting requests for traceId:', traceId);
try {
// 添加时间窗口条件,获取最近10秒内的请求
const result = await env.DB.prepare(`
SELECT DISTINCT ip, asn, as_org, city, country, colo
FROM requests
WHERE trace_id = ?
AND timestamp >= ?
ORDER BY timestamp ASC
`).bind(
traceId,
Date.now() - 10000 // 10秒时间窗口
).all();
// 添加调试日志
console.log('SQL result:', result);
console.log('Found records:', result.results?.length || 0);
if (!result.results || result.results.length === 0) {
console.log('No records found for traceId:', traceId);
return [];
}
return result.results;
} catch (error) {
console.error('Error getting requests:', error);
return [];
}
}
async function handleFakeImage(request, env) {
console.log('Handling fake image request');
const url = new URL(request.url);
const traceId = url.searchParams.get('traceId');
if (!traceId) {
return new Response('Missing traceId parameter', { status: 400 });
}
// 存储请求信息
await storeRequest(env, traceId, request);
// 返回随机图片
try {
const imageResponse = await fetch('https://picsum.photos/200/300', {
headers: { 'Cache-Control': 'no-cache' }
});
if (!imageResponse.ok) {
throw new Error('Failed to fetch image');
}
const imageBuffer = await imageResponse.arrayBuffer();
return new Response(imageBuffer, {
headers: {
'Content-Type': imageResponse.headers.get('content-type') || 'image/jpeg',
'Cache-Control': 'no-store'
}
});
} catch (e) {
console.error('Error in handleFakeImage:', e);
throw e;
}
}
// 设置超时时间为5秒
const TIMEOUT = 10000;
// 超时处理函数
function timeoutPromise(ms) {
return new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), ms));
}
async function handleOpenAIRequest(request, env) {
console.log('Handling OpenAI request');
try {
const { url, key, model = 'gpt-4o' } = await request.json();
if (!url || !key) {
throw new Error('Missing url or key parameter');
}
const traceId = Date.now().toString();
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
function sendMessage(type, message) {
const msg = JSON.stringify({ type, message }) + '\n';
controller.enqueue(encoder.encode(msg));
console.log('Sent message:', msg);
}
// 发送初始状态
sendMessage('status', '请求已发送,TraceId: ' + traceId);
sendMessage('log', '开始请求 - 模型: ' + model);
// 构造图片 URL
const imageUrl = `https://${CONFIG.WORKER_DOMAIN}/static/img?traceId=${traceId}`;
// 使用 Promise.race 实现超时控制
const fetchPromise = fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${key}`
},
body: JSON.stringify({
model,
messages: [{
role: "user",
content: [
{ type: "image_url", image_url: { url: imageUrl } },
{ type: "text", text: "What is this?" }
]
}],
max_tokens: 30,
stream: false
})
});
// 设置超时机制
const response = await Promise.race([fetchPromise, timeoutPromise(TIMEOUT)]);
const responseText = await response.text();
let responseData;
try {
responseData = JSON.parse(responseText);
sendMessage('log', '请求成功');
} catch (e) {
sendMessage('error', 'Invalid JSON response: ' + responseText);
return;
}
// API 请求完成后,等待一小段时间让数据写入完成
await new Promise(resolve => setTimeout(resolve, 1000));
// 现在查询中转节点信息
sendMessage('log', '正在检测中转节点...');
const requests = await getRequests(env, traceId);
console.log('Found requests:', requests);
if (requests && requests.length > 0) {
for (const req of requests) {
sendMessage('log',
`检测到中转节点: ${req.as_org || 'Unknown'} ` +
`(${req.ip}) - 位置: ${req.city || 'Unknown'}, ` +
`${req.country} [DC: ${req.colo}]`
);
// 添加小延迟确保消息正确发送
await new Promise(resolve => setTimeout(resolve, 100));
}
} else {
sendMessage('log', '未检测到中转节点');
}
// 最后发送 API 响应结果
sendMessage('response', responseData);
} catch (error) {
console.error('Error in stream:', error);
sendMessage('error', error.message);
} finally {
controller.close();
}
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/plain',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
} catch (error) {
console.error('Error in handleOpenAIRequest:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
async function handleRequest(request, env) {
console.log('Request received:', request.url);
const url = new URL(request.url);
const pathname = url.pathname;
try {
if (pathname === '/v1/serve' && request.method === 'POST') {
return await handleOpenAIRequest(request, env);
} else if (pathname === '/static/img' && request.method === 'GET') {
return await handleFakeImage(request, env);
} else if (pathname === '/' || pathname === '/index.html') {
return new Response(HTML_CONTENT, {
headers: { 'Content-Type': 'text/html;charset=UTF-8' }
});
}
return new Response('Not Found', { status: 404 });
} catch (error) {
console.error('Error in handleRequest:', error);
throw error;
}
}
export default {
async fetch(request, env, ctx) {
try {
return await handleRequest(request, env);
} catch (error) {
console.error('Fatal error:', error);
return new Response(JSON.stringify({
error: 'Internal Server Error',
message: error.message
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
};