This commit is contained in:
2026-05-18 22:32:08 +08:00
parent 6ab44ea187
commit bd4caa0f09
22 changed files with 1178 additions and 297 deletions

View File

@@ -1,149 +1,155 @@
server {
# 文件名前缀是 000-,会比 ai.sggai.site.conf 更早加载。
# 这样可以在不修改原配置文件的情况下,让这里的 mock 逻辑优先生效。
listen 80;
# http2 on;
listen 443 ssl;
server_name ai.sggai.site;
ssl_certificate /etc/letsencrypt/live/ai.sggai.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai.sggai.site/privkey.pem;
location ^~ /.well-known/acme-challenge/ {
root /var/www;
default_type text/plain;
try_files $uri =404;
}
# 关键:允许 Session_id 这种带下划线的请求头
# underscores_in_headers on;
# ignore_invalid_headers off;
client_max_body_size 200m;
gzip off;
gunzip off;
location / {
proxy_pass http://10.1.0.1:3001;
proxy_http_version 1.1;
# 保持你原来“模拟 IP 直连”的行为
proxy_set_header Host $host;
# 基础请求头
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Type $http_content_type;
proxy_set_header Accept $http_accept;
proxy_set_header User-Agent $http_user_agent;
# 关键Codex / 上游 prompt cache 相关头
proxy_set_header Originator $http_originator;
proxy_set_header Session_id $http_session_id;
proxy_set_header X-Codex-Beta-Features $http_x_codex_beta_features;
proxy_set_header X-Codex-Turn-Metadata $http_x_codex_turn_metadata;
# Claude CLI 相关头,保留无害
proxy_set_header X-Stainless-Arch $http_x_stainless_arch;
proxy_set_header X-Stainless-Lang $http_x_stainless_lang;
proxy_set_header X-Stainless-Os $http_x_stainless_os;
proxy_set_header X-Stainless-Package-Version $http_x_stainless_package_version;
proxy_set_header X-Stainless-Retry-Count $http_x_stainless_retry_count;
proxy_set_header X-Stainless-Runtime $http_x_stainless_runtime;
proxy_set_header X-Stainless-Runtime-Version $http_x_stainless_runtime_version;
proxy_set_header X-Stainless-Timeout $http_x_stainless_timeout;
proxy_set_header X-App $http_x_app;
proxy_set_header Anthropic-Beta $http_anthropic_beta;
proxy_set_header Anthropic-Dangerous-Direct-Browser-Access $http_anthropic_dangerous_direct_browser_access;
proxy_set_header Anthropic-Version $http_anthropic_version;
# 禁用压缩干扰
proxy_set_header Accept-Encoding "";
# 继续模拟直连,不暴露外层代理链
proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Port "";
proxy_set_header Connection "";
# SSE / 流式响应
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_bypass 1;
proxy_connect_timeout 600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
server {
listen 443 ssl;
# http2 on;
server_name ai.sggai.site;
ssl_certificate /etc/letsencrypt/live/ai.sggai.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai.sggai.site/privkey.pem;
# 关键:允许 Session_id 这种带下划线的请求头 http2 on 和下面这2个加上就容易出 status_code=400, Invalid 'prompt_cache_key': string too long. Expected a string with maximum length 64, but got a string with length 74 instead.
# underscores_in_headers on;
# ignore_invalid_headers off;
client_max_body_size 200m;
gzip off;
gunzip off;
# 公共反代配置放在 server 级别,主后端 proxy_pass location 会继承这些配置。
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Type $http_content_type;
proxy_set_header Accept $http_accept;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header Originator $http_originator;
proxy_set_header Session_id $http_session_id;
proxy_set_header X-Codex-Beta-Features $http_x_codex_beta_features;
proxy_set_header X-Codex-Turn-Metadata $http_x_codex_turn_metadata;
proxy_set_header X-Stainless-Arch $http_x_stainless_arch;
proxy_set_header X-Stainless-Lang $http_x_stainless_lang;
proxy_set_header X-Stainless-Os $http_x_stainless_os;
proxy_set_header X-Stainless-Package-Version $http_x_stainless_package_version;
proxy_set_header X-Stainless-Retry-Count $http_x_stainless_retry_count;
proxy_set_header X-Stainless-Runtime $http_x_stainless_runtime;
proxy_set_header X-Stainless-Runtime-Version $http_x_stainless_runtime_version;
proxy_set_header X-Stainless-Timeout $http_x_stainless_timeout;
proxy_set_header X-App $http_x_app;
proxy_set_header Anthropic-Beta $http_anthropic_beta;
proxy_set_header Anthropic-Dangerous-Direct-Browser-Access $http_anthropic_dangerous_direct_browser_access;
proxy_set_header Anthropic-Version $http_anthropic_version;
proxy_set_header Accept-Encoding "";
proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Port "";
proxy_set_header Connection "";
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_bypass 1;
proxy_connect_timeout 600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# 只拦截很小的非流式 Chat Completions 请求。
# 不满足条件的请求会内部跳转到正常后端,不直接返回 mock。
location = /v1/chat/completions {
client_body_buffer_size 16k;
content_by_lua_block {
-- 不满足 mock 条件时,统一内部跳转到这个 named location。
local backend = "@ai_sggai_site_backend"
-- 请求体总大小上限:只处理非常小的探测类请求。
local max_body_bytes = 1024
-- 只处理 POST其它方法按普通请求转发到真实后端。
if ngx.req.get_method() ~= "POST" then
return ngx.exec(backend)
end
-- 先看 Content-Length超过阈值就不读取 body直接放行。
local content_length = tonumber(ngx.var.http_content_length)
if not content_length then
-- 没有 Content-Length 的请求可能是 chunked不为了判断 mock 去读取未知大小的 body。
return ngx.exec(backend)
end
-- 请求体太大,说明不是目标小请求,直接走真实后端。
if content_length > max_body_bytes then
return ngx.exec(backend)
end
-- 到这里说明 body 足够小,可以安全读取并解析 JSON。
ngx.req.read_body()
local body = ngx.req.get_body_data()
if not body or #body > max_body_bytes then
-- body 不在内存里或实际大小仍然超限时,不 mock转发到真实后端。
return ngx.exec(backend)
end
-- JSON 解析失败,或 stream 不是 false都不 mock。
local cjson = require "cjson.safe"
local payload = cjson.decode(body)
if type(payload) ~= "table" or payload.stream ~= false then
return ngx.exec(backend)
end
-- 必须有 messages 数组;没有就不 mock。
if type(payload.messages) ~= "table" then
return ngx.exec(backend)
end
local has_hi_content = false
-- 遍历所有 message只匹配精确的 "content": "hi"。
for _, message in ipairs(payload.messages) do
if type(message) == "table" and message.content == "hi" then
has_hi_content = true
break
end
end
-- 没有精确命中 "content": "hi",交给真实后端处理。
if not has_hi_content then
return ngx.exec(backend)
end
-- 命中小请求 + stream=false + "content": "hi" 条件,直接返回 mock 响应。
ngx.status = ngx.HTTP_OK
ngx.header["Content-Type"] = "application/json; charset=utf-8"
ngx.say([[{
"id": "chatcmpl-mock",
"object": "chat.completion",
"created": 1716030000,
"model": "xxx",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "ok" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2 }
}]])
return ngx.exit(ngx.HTTP_OK)
}
}
# 仅供上面的 Lua 逻辑通过 ngx.exec 内部跳转使用,外部 URL 不能直接访问这个 location。
location @ai_sggai_site_backend {
proxy_pass http://10.1.0.1:3001;
}
# 普通流量保持原来的上游转发行为。
location / {
proxy_pass http://10.1.0.1:3001;
proxy_http_version 1.1;
# 保持你原来“模拟 IP 直连”的行为
proxy_set_header Host $host;
# 基础请求头
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Type $http_content_type;
proxy_set_header Accept $http_accept;
proxy_set_header User-Agent $http_user_agent;
# 关键Codex / 上游 prompt cache 相关头
proxy_set_header Originator $http_originator;
proxy_set_header Session_id $http_session_id;
proxy_set_header X-Codex-Beta-Features $http_x_codex_beta_features;
proxy_set_header X-Codex-Turn-Metadata $http_x_codex_turn_metadata;
# Claude CLI 相关头,保留无害
proxy_set_header X-Stainless-Arch $http_x_stainless_arch;
proxy_set_header X-Stainless-Lang $http_x_stainless_lang;
proxy_set_header X-Stainless-Os $http_x_stainless_os;
proxy_set_header X-Stainless-Package-Version $http_x_stainless_package_version;
proxy_set_header X-Stainless-Retry-Count $http_x_stainless_retry_count;
proxy_set_header X-Stainless-Runtime $http_x_stainless_runtime;
proxy_set_header X-Stainless-Runtime-Version $http_x_stainless_runtime_version;
proxy_set_header X-Stainless-Timeout $http_x_stainless_timeout;
proxy_set_header X-App $http_x_app;
proxy_set_header Anthropic-Beta $http_anthropic_beta;
proxy_set_header Anthropic-Dangerous-Direct-Browser-Access $http_anthropic_dangerous_direct_browser_access;
proxy_set_header Anthropic-Version $http_anthropic_version;
# 禁用压缩干扰
proxy_set_header Accept-Encoding "";
# 继续模拟直连,不暴露外层代理链
proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Port "";
proxy_set_header Connection "";
# SSE / 流式响应
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_bypass 1;
proxy_connect_timeout 600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

View File

@@ -0,0 +1,149 @@
server {
listen 80;
# http2 on;
server_name ai.sggai.site;
location ^~ /.well-known/acme-challenge/ {
root /var/www;
default_type text/plain;
try_files $uri =404;
}
# 关键:允许 Session_id 这种带下划线的请求头
# underscores_in_headers on;
# ignore_invalid_headers off;
client_max_body_size 200m;
gzip off;
gunzip off;
location / {
proxy_pass http://10.1.0.1:3001;
proxy_http_version 1.1;
# 保持你原来“模拟 IP 直连”的行为
proxy_set_header Host $host;
# 基础请求头
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Type $http_content_type;
proxy_set_header Accept $http_accept;
proxy_set_header User-Agent $http_user_agent;
# 关键Codex / 上游 prompt cache 相关头
proxy_set_header Originator $http_originator;
proxy_set_header Session_id $http_session_id;
proxy_set_header X-Codex-Beta-Features $http_x_codex_beta_features;
proxy_set_header X-Codex-Turn-Metadata $http_x_codex_turn_metadata;
# Claude CLI 相关头,保留无害
proxy_set_header X-Stainless-Arch $http_x_stainless_arch;
proxy_set_header X-Stainless-Lang $http_x_stainless_lang;
proxy_set_header X-Stainless-Os $http_x_stainless_os;
proxy_set_header X-Stainless-Package-Version $http_x_stainless_package_version;
proxy_set_header X-Stainless-Retry-Count $http_x_stainless_retry_count;
proxy_set_header X-Stainless-Runtime $http_x_stainless_runtime;
proxy_set_header X-Stainless-Runtime-Version $http_x_stainless_runtime_version;
proxy_set_header X-Stainless-Timeout $http_x_stainless_timeout;
proxy_set_header X-App $http_x_app;
proxy_set_header Anthropic-Beta $http_anthropic_beta;
proxy_set_header Anthropic-Dangerous-Direct-Browser-Access $http_anthropic_dangerous_direct_browser_access;
proxy_set_header Anthropic-Version $http_anthropic_version;
# 禁用压缩干扰
proxy_set_header Accept-Encoding "";
# 继续模拟直连,不暴露外层代理链
proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Port "";
proxy_set_header Connection "";
# SSE / 流式响应
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_bypass 1;
proxy_connect_timeout 600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
server {
listen 443 ssl;
# http2 on;
server_name ai.sggai.site;
ssl_certificate /etc/letsencrypt/live/ai.sggai.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai.sggai.site/privkey.pem;
# 关键:允许 Session_id 这种带下划线的请求头 http2 on 和下面这2个加上就容易出 status_code=400, Invalid 'prompt_cache_key': string too long. Expected a string with maximum length 64, but got a string with length 74 instead.
# underscores_in_headers on;
# ignore_invalid_headers off;
client_max_body_size 200m;
gzip off;
gunzip off;
location / {
proxy_pass http://10.1.0.1:3001;
proxy_http_version 1.1;
# 保持你原来“模拟 IP 直连”的行为
proxy_set_header Host $host;
# 基础请求头
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Type $http_content_type;
proxy_set_header Accept $http_accept;
proxy_set_header User-Agent $http_user_agent;
# 关键Codex / 上游 prompt cache 相关头
proxy_set_header Originator $http_originator;
proxy_set_header Session_id $http_session_id;
proxy_set_header X-Codex-Beta-Features $http_x_codex_beta_features;
proxy_set_header X-Codex-Turn-Metadata $http_x_codex_turn_metadata;
# Claude CLI 相关头,保留无害
proxy_set_header X-Stainless-Arch $http_x_stainless_arch;
proxy_set_header X-Stainless-Lang $http_x_stainless_lang;
proxy_set_header X-Stainless-Os $http_x_stainless_os;
proxy_set_header X-Stainless-Package-Version $http_x_stainless_package_version;
proxy_set_header X-Stainless-Retry-Count $http_x_stainless_retry_count;
proxy_set_header X-Stainless-Runtime $http_x_stainless_runtime;
proxy_set_header X-Stainless-Runtime-Version $http_x_stainless_runtime_version;
proxy_set_header X-Stainless-Timeout $http_x_stainless_timeout;
proxy_set_header X-App $http_x_app;
proxy_set_header Anthropic-Beta $http_anthropic_beta;
proxy_set_header Anthropic-Dangerous-Direct-Browser-Access $http_anthropic_dangerous_direct_browser_access;
proxy_set_header Anthropic-Version $http_anthropic_version;
# 禁用压缩干扰
proxy_set_header Accept-Encoding "";
# 继续模拟直连,不暴露外层代理链
proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Port "";
proxy_set_header Connection "";
# SSE / 流式响应
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_bypass 1;
proxy_connect_timeout 600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

View File

@@ -1,78 +1,44 @@
server {
listen 80;
server_name dms.sggai.site;
location ^~ /.well-known/acme-challenge/ {
root /var/www;
default_type text/plain;
try_files $uri =404;
}
# 静态网站案例
# location / {
# root /var/www/dms.sggai.site;
# index index.html;
# try_files $uri $uri/ /index.html;
# }
# 反向代理案例
# location / {
# proxy_pass http://10.1.0.1:3001;
#
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#
# proxy_read_timeout 3600s;
# proxy_send_timeout 3600s;
# }
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name dms.sggai.site;
ssl_certificate /etc/letsencrypt/live/dms.sggai.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dms.sggai.site/privkey.pem;
root /var/www/dms.sggai.site;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www;
default_type text/plain;
try_files $uri =404;
}
location / {
root /var/www/dms.sggai.site;
index index.html;
if ($scheme = http) {
return 301 https://$host$request_uri;
}
try_files $uri $uri/ /index.html;
}
}
server {
# 对外暴露的 HTTPS 端口
# 用户访问https://dms.sggai.site:18083/
listen 18083 ssl;
# 只匹配这个域名
server {
listen 18083 ssl;
server_name dms.sggai.site;
ssl_certificate /etc/letsencrypt/live/dms.sggai.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dms.sggai.site/privkey.pem;
location / {
# 转发到局域网后端设备
# 这里是 http表示 nginx 到 10.1.0.1 使用明文 HTTP
proxy_pass http://10.1.0.1:18083;
# 传递原始访问域名
# 如果后端需要带端口,建议用 $http_host
proxy_set_header Host $http_host;
# 传递客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 传递完整代理链 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 告诉后端:用户外部访问协议是 HTTPS
proxy_set_header X-Forwarded-Proto https;
# 长连接/慢请求超时时间
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}

View File

@@ -1,81 +1,39 @@
map $server_port $lsbd2_prod_api_backend {
default 10.1.0.64:8080;
8000 10.1.0.100:8080;
}
server {
listen 80;
listen 443 ssl;
listen 8000 ssl;
server_name lsbd2.loveteemo.com;
ssl_certificate /etc/letsencrypt/live/lsbd2.loveteemo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lsbd2.loveteemo.com/privkey.pem;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
location ^~ /.well-known/acme-challenge/ {
root /var/www;
default_type text/plain;
try_files $uri =404;
}
# 静态网站案例
# location / {
# root /usr/share/nginx/html/lsbd2.loveteemo.com;
# index index.html;
# try_files $uri $uri/ /index.html;
# }
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name lsbd2.loveteemo.com;
ssl_certificate /etc/letsencrypt/live/lsbd2.loveteemo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lsbd2.loveteemo.com/privkey.pem;
location / {
proxy_pass http://10.1.0.64:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location /prod-api/ {
proxy_pass http://10.1.0.64:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
server {
listen 8000 ssl;
server_name lsbd2.loveteemo.com;
ssl_certificate /etc/letsencrypt/live/lsbd2.loveteemo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lsbd2.loveteemo.com/privkey.pem;
location /{
proxy_pass http://10.1.0.64:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location /prod-api/ {
proxy_pass http://10.1.0.100:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_pass http://$lsbd2_prod_api_backend;
}
}
server {
listen 8001 ssl;
server_name lsbd2.loveteemo.com;
@@ -83,14 +41,13 @@ server {
ssl_certificate /etc/letsencrypt/live/lsbd2.loveteemo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lsbd2.loveteemo.com/privkey.pem;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
location / {
proxy_pass http://36.111.46.77:31777/prod-api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}