fix: address code review — CORS, input validation, timeout alignment, logging

- CORS: default whitelist restricted to project domains (not wildcard)
- Input validation: clamp temperature [0,2] and max_tokens [1,8192]
- Timeout: increase to 60s, align Nginx config (90s read / 60s send)
- Logging: remove client IP and message count from logs
- Frontend: move proxy option lower in dropdown, fix duplicate system prompt
- Shorten proxy mode placeholder text

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-07 07:34:59 +00:00
parent 723f6b16bc
commit 466f344ef8
3 changed files with 23 additions and 18 deletions

View File

@ -40,7 +40,7 @@ const { URL } = require('url');
// 配置
// ═══════════════════════════════════════════════════════
const PORT = parseInt(process.env.PROXY_PORT || '3721', 10);
const ALLOWED_ORIGINS = (process.env.ALLOWED_ORIGINS || '*').split(',').map(s => s.trim());
const ALLOWED_ORIGINS = (process.env.ALLOWED_ORIGINS || 'https://qinfendebingshuo.github.io,https://guanghulab.com,http://localhost:8765').split(',').map(s => s.trim());
const RATE_LIMIT_RPM = parseInt(process.env.RATE_LIMIT_RPM || '10', 10);
// 模型端点映射
@ -243,8 +243,8 @@ function proxyStream(upstreamUrl, apiKey, requestBody, res) {
reject(err);
});
// Set timeout (30 seconds for connection, but streaming can be longer)
upstream.setTimeout(30000, () => {
// Connection timeout only — once response headers arrive, streaming can run longer
upstream.setTimeout(60000, () => {
upstream.destroy(new Error('连接超时'));
});
@ -312,6 +312,10 @@ async function handleChat(req, res) {
return;
}
// Validate numeric parameters
const temp = typeof temperature === 'number' ? Math.max(0, Math.min(2, temperature)) : 0.8;
const maxTok = typeof max_tokens === 'number' ? Math.max(1, Math.min(8192, Math.floor(max_tokens))) : 2000;
// Determine provider
const prov = provider || 'deepseek';
const config = MODEL_CONFIG[prov];
@ -341,8 +345,8 @@ async function handleChat(req, res) {
model: mdl,
messages: messages,
stream: stream !== false, // Default to streaming
temperature: temperature ?? 0.8,
max_tokens: max_tokens ?? 2000
temperature: temp,
max_tokens: maxTok
};
// If system prompt provided, prepend as system message
@ -352,7 +356,7 @@ async function handleChat(req, res) {
const upstreamUrl = config.base + '/chat/completions';
console.log(`[代理] ${clientId}${prov}/${mdl} (${messages.length}条消息)`);
console.log(`[代理] ${prov}/${mdl}`);
try {
await proxyStream(upstreamUrl, apiKey, upstreamBody, res);

View File

@ -20,7 +20,7 @@ location /api/ {
proxy_cache off;
chunked_transfer_encoding on;
# 长连接超时(AI 生成可能需要较长时间
proxy_read_timeout 120s;
proxy_send_timeout 120s;
# 长连接超时(与 Node.js api-proxy 的 60s 连接超时对齐,流式响应可更长
proxy_read_timeout 90s;
proxy_send_timeout 60s;
}

View File

@ -323,13 +323,13 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
<div class="fg">
<label>选择 AI 提供商</label>
<select id="sp" onchange="onProv(this.value,'s')">
<option value="proxy">🔌 后端代理(国内直连·推荐)</option>
<option value="yunwu">☁️ 云雾 AI团队推荐</option>
<option value="openai">OpenAI (ChatGPT)</option>
<option value="gemini">🔮 Google Gemini</option>
<option value="deepseek">DeepSeek深度求索·国内直连</option>
<option value="proxy">🔌 后端代理guanghulab.com</option>
<option value="moonshot">MoonshotKimi·国内直连</option>
<option value="zhipu">智谱 AIGLM·国内直连</option>
<option value="openai">OpenAI (ChatGPT)</option>
<option value="gemini">🔮 Google Gemini</option>
<option value="custom">自定义 / 其他兼容接口</option>
</select>
</div>
@ -515,13 +515,13 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
<div class="sfg">
<label>AI 提供商</label>
<select id="cp" onchange="onProv(this.value,'c')">
<option value="proxy">🔌 后端代理(国内直连·推荐)</option>
<option value="yunwu">☁️ 云雾 AI团队推荐</option>
<option value="openai">OpenAI (ChatGPT)</option>
<option value="gemini">🔮 Google Gemini</option>
<option value="deepseek">DeepSeek深度求索·国内直连</option>
<option value="proxy">🔌 后端代理guanghulab.com</option>
<option value="moonshot">MoonshotKimi·国内直连</option>
<option value="zhipu">智谱 AIGLM·国内直连</option>
<option value="openai">OpenAI (ChatGPT)</option>
<option value="gemini">🔮 Google Gemini</option>
<option value="custom">自定义</option>
</select>
</div>
@ -1188,7 +1188,7 @@ function onProv(pv,ctx){
}
// Proxy mode: API key is optional (managed server-side)
const ki = document.getElementById(keyInp);
if(ki) ki.placeholder = isProxy ? '后端代理模式 — 无需填写 API 密钥(服务器端统一管理' : (ctx==='s'?'粘贴任意 API 密钥,点击右侧按钮自动识别提供商和模型':'留空 = 保持当前密钥不变;粘贴新密钥即替换');
if(ki) ki.placeholder = isProxy ? '后端代理模式(无需密钥' : (ctx==='s'?'粘贴任意 API 密钥,点击右侧按钮自动识别提供商和模型':'留空 = 保持当前密钥不变;粘贴新密钥即替换');
}
function fillModels(selId, pv, cur){
@ -1584,9 +1584,10 @@ async function streamReply(txt){
const url = isProxy ? (A.base+'/chat') : (A.base+'/chat/completions');
const headers = {'Content-Type':'application/json'};
if(!isProxy && A.key) headers['Authorization'] = 'Bearer '+A.key;
const allMsgs = [{role:'system',content:sysPrompt()},...A.msgs];
const reqBody = isProxy
? {provider:A.prov==='proxy'?undefined:A.prov, model:A.mdl, messages:[{role:'system',content:sysPrompt()},...A.msgs], stream:true, temperature:0.8, max_tokens:2000, system:sysPrompt()}
: {model:A.mdl, messages:[{role:'system',content:sysPrompt()},...A.msgs], stream:true, temperature:0.8, max_tokens:2000};
? {model:A.mdl, messages:allMsgs, stream:true, temperature:0.8, max_tokens:2000}
: {model:A.mdl, messages:allMsgs, stream:true, temperature:0.8, max_tokens:2000};
const res=await fetch(url,{
method:'POST',
headers:headers,