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:
parent
723f6b16bc
commit
466f344ef8
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">Moonshot(Kimi·国内直连)</option>
|
||||
<option value="zhipu">智谱 AI(GLM·国内直连)</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">Moonshot(Kimi·国内直连)</option>
|
||||
<option value="zhipu">智谱 AI(GLM·国内直连)</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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue