纠正清智创游官网游戏下载线上路由
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 1m18s
Project CI / AI game creator shell Rust smoke (push) Failing after 1m18s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 1m18s
Project CI / AI game creator shell Rust crates (push) Successful in 1m51s
Project CI / Frontend tests (push) Successful in 4m46s
Project CI / Repository checks (push) Successful in 5m5s
Project CI / AI game creator shell web tests (push) Successful in 2m47s
Project CI / Backend tests (push) Successful in 9m1s
Project CI / Native shell tests (push) Successful in 10m4s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 1m18s
Project CI / AI game creator shell Rust smoke (push) Failing after 1m18s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 1m18s
Project CI / AI game creator shell Rust crates (push) Successful in 1m51s
Project CI / Frontend tests (push) Successful in 4m46s
Project CI / Repository checks (push) Successful in 5m5s
Project CI / AI game creator shell web tests (push) Successful in 2m47s
Project CI / Backend tests (push) Successful in 9m1s
Project CI / Native shell tests (push) Successful in 10m4s
移除错误的独立主站 vhost 配置 补充官网现役 vhost 的精确下载接口 snippet 同步线上排障说明与回归测试
This commit is contained in:
@@ -47,79 +47,6 @@ server {
|
||||
}
|
||||
}
|
||||
|
||||
# 清智创游官网独立静态站点:根页面位于 /home/index.html,API 仍由当前 api-server 提供。
|
||||
# 该 vhost 不依赖主站 SERVER_ALIASES,避免域名落入静态站点后对 /api 返回 Nginx 404。
|
||||
server {
|
||||
listen 80;
|
||||
server_name tsingnovagames.com www.tsingnovagames.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name tsingnovagames.com www.tsingnovagames.com;
|
||||
access_log /var/log/nginx/genarrative.access.log genarrative_upstream;
|
||||
error_log /var/log/nginx/genarrative.error.log warn;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/genarrative.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/genarrative.example.com/privkey.pem;
|
||||
|
||||
root /srv/genarrative/web;
|
||||
index index.html;
|
||||
|
||||
include /etc/nginx/snippets/genarrative-maintenance.conf;
|
||||
|
||||
location ~ ^/api(?:/|$) {
|
||||
default_type application/json;
|
||||
client_max_body_size 210m;
|
||||
limit_conn genarrative_api_conn 64;
|
||||
limit_req zone=genarrative_api_rps burst=64 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
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_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location = / {
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503;
|
||||
}
|
||||
|
||||
try_files /home/index.html =404;
|
||||
}
|
||||
|
||||
location ^~ /home/ {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
error_page 503 /maintenance.html;
|
||||
error_page 404 /404.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503;
|
||||
}
|
||||
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name genarrative.example.com;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# 清智创游官网下载接口:此 snippet 只在现役 tsingnova-games HTTPS server 中 include。
|
||||
# 依赖主 Nginx 配置已声明的 genarrative_api、限流区和维护状态变量。
|
||||
location = /api/client-downloads {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 64;
|
||||
limit_req zone=genarrative_api_rps burst=64 nodelay;
|
||||
limit_conn_status 429;
|
||||
limit_req_status 429;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
proxy_set_header Cookie "";
|
||||
proxy_set_header Authorization "";
|
||||
proxy_connect_timeout 3s;
|
||||
proxy_read_timeout 15s;
|
||||
proxy_send_timeout 15s;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
add_header X-Accel-Buffering no always;
|
||||
}
|
||||
@@ -112,19 +112,6 @@
|
||||
},
|
||||
"docs": ["`/assets/*`"]
|
||||
},
|
||||
{
|
||||
"id": "official_home_assets",
|
||||
"samplePath": "/home/assets/index.js",
|
||||
"expect": {
|
||||
"kind": "static",
|
||||
"root": "web",
|
||||
"mode": "exact"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["location ^~ /home/", "try_files $uri $uri/ =404;"]
|
||||
},
|
||||
"docs": ["/home/"]
|
||||
},
|
||||
{
|
||||
"id": "generic_api_proxy",
|
||||
"samplePath": "/api/assets/history",
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
|
||||
### 清智创游官网独立域名路由
|
||||
|
||||
清智创游官网 `tsingnovagames.com` / `www.tsingnovagames.com` 的首页静态资源位于同一 Web 根目录的 `/home/`,首页“游戏引擎”入口按同源请求 `/api/client-downloads`。生产 Nginx 必须让这两个域名进入包含 `/api` 反代的主站 `server block`,并把该域名根路径重写到 `/home/`;否则页面可以打开,但接口会被静态站点直接返回 HTML 404,前端显示“暂时无法获取下载信息,请稍后重试”。
|
||||
清智创游官网 `tsingnovagames.com` / `www.tsingnovagames.com` 使用独立的 Nginx 配置 `/etc/nginx/conf.d/tsingnova-games.conf`,静态根目录是 `/srv/genarrative/corporate-site-current`;主站模板 `deploy/nginx/genarrative.conf` 不应重复声明这两个域名。官网首页按同源请求 `/api/client-downloads`,因此应在现役官网 HTTPS `server` 中 include `deploy/nginx/snippets/tsingnova-client-downloads.conf`,只代理这个精确路径到 `genarrative_api`,不要把整个 `/api` 交给官网静态站点兜底。
|
||||
|
||||
`Genarrative-Server-Provision` 在 `SERVER_NAME=genarrative.world`(或 `www.genarrative.world`)且使用 `production-https` 时会自动补入这两个官网别名,并渲染 `/home/` 根路径兼容规则。修改后需重新执行一次正式服务器配置发布并 reload Nginx,不要只重启 api-server:
|
||||
线上修复或排障后必须确认的是**生效配置**而不是仓库模板:
|
||||
|
||||
```bash
|
||||
sudo nginx -T | grep -n -A45 -B5 'server_name tsingnovagames.com'
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
curl -fsS -D ~/data/tmp/tsingnova-client-download.headers \
|
||||
https://tsingnovagames.com/api/client-downloads \
|
||||
-o ~/data/tmp/tsingnova-client-download.json
|
||||
@@ -18,7 +21,7 @@ jq . ~/data/tmp/tsingnova-client-download.json
|
||||
grep -i '^cache-control: no-store' ~/data/tmp/tsingnova-client-download.headers
|
||||
```
|
||||
|
||||
验收必须同时满足:接口返回 `200 application/json`,响应包含 `downloads` 与 `unavailablePlatforms`,并带 `Cache-Control: no-store`;`https://tsingnovagames.com/` 仍返回官网首页。若接口仍返回 Nginx 404,优先检查 `nginx -T` 中 `server_name` 是否包含上述两个域名以及 `/api` location 是否指向 `genarrative_api`。
|
||||
验收必须同时满足:接口返回 `200 application/json`,响应包含 `downloads` 与 `unavailablePlatforms`,并带 `Cache-Control: no-store`;`https://tsingnovagames.com/` 仍返回官网首页。若 API 直连 `http://127.0.0.1:8082/api/client-downloads` 是 `200`、官网域名仍是 `404`,说明官网 vhost 没有 include 下载 snippet,不能靠 reload 旧配置解决。
|
||||
|
||||
## 构建回归的隔离与发布文件权限
|
||||
|
||||
|
||||
@@ -3,26 +3,97 @@ import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const productionNginx = readFileSync('deploy/nginx/genarrative.conf', 'utf8');
|
||||
const provisionScript = readFileSync(
|
||||
'scripts/jenkins-server-provision.sh',
|
||||
const clientDownloadsSnippet = readFileSync(
|
||||
'deploy/nginx/snippets/tsingnova-client-downloads.conf',
|
||||
'utf8',
|
||||
);
|
||||
|
||||
function extractServerBlocks(source: string): string[] {
|
||||
const blocks: string[] = [];
|
||||
const serverStart = /\bserver\s*\{/gu;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = serverStart.exec(source)) !== null) {
|
||||
const openBrace = source.indexOf('{', match.index);
|
||||
let depth = 0;
|
||||
let quote: '"' | "'" | null = null;
|
||||
let escaped = false;
|
||||
let inComment = false;
|
||||
|
||||
for (let index = openBrace; index < source.length; index += 1) {
|
||||
const character = source[index];
|
||||
|
||||
if (inComment) {
|
||||
if (character === '\n') {
|
||||
inComment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (quote !== null) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (character === '\\') {
|
||||
escaped = true;
|
||||
} else if (character === quote) {
|
||||
quote = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (character === '#') {
|
||||
inComment = true;
|
||||
} else if (character === '"' || character === "'") {
|
||||
quote = character;
|
||||
} else if (character === '{') {
|
||||
depth += 1;
|
||||
} else if (character === '}') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
blocks.push(source.slice(match.index, index + 1));
|
||||
serverStart.lastIndex = index + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
describe('清智创游官网引擎下载路由', () => {
|
||||
it('生产 Nginx 为官网域名提供独立 vhost 和 API 反代', () => {
|
||||
expect(productionNginx).toContain(
|
||||
'server_name tsingnovagames.com www.tsingnovagames.com;',
|
||||
it('主站模板不重复声明官网独立域名,下载路由由现役官网 vhost 单独维护', () => {
|
||||
expect(productionNginx).not.toContain('tsingnovagames.com');
|
||||
expect(clientDownloadsSnippet).toContain(
|
||||
'location = /api/client-downloads {',
|
||||
);
|
||||
expect(productionNginx).toMatch(
|
||||
/server_name tsingnovagames\.com www\.tsingnovagames\.com;[\s\S]*?location ~ \^\/api\(\?:\/\|\$\)[\s\S]*?proxy_pass http:\/\/genarrative_api;/u,
|
||||
);
|
||||
expect(productionNginx).toContain('try_files /home/index.html =404;');
|
||||
expect(productionNginx).toContain('location ^~ /home/');
|
||||
});
|
||||
|
||||
it('Server-Provision 会把正式证书路径渲染进官网 vhost', () => {
|
||||
expect(provisionScript).toContain(
|
||||
's|/etc/letsencrypt/live/genarrative.example.com/|/etc/letsencrypt/live/${SERVER_NAME}/|g',
|
||||
it('下载 snippet 只声明精确接口并复用主站保护与 upstream', () => {
|
||||
expect(clientDownloadsSnippet).toContain(
|
||||
'location = /api/client-downloads {',
|
||||
);
|
||||
expect(clientDownloadsSnippet).not.toMatch(
|
||||
/location\s+(?:~|\^~)?\s*\/api(?:\/|\s|\{|\$)/u,
|
||||
);
|
||||
for (const directive of [
|
||||
'limit_conn genarrative_api_conn 64;',
|
||||
'limit_req zone=genarrative_api_rps burst=64 nodelay;',
|
||||
'limit_conn_status 429;',
|
||||
'limit_req_status 429;',
|
||||
'if ($genarrative_maintenance) {',
|
||||
'proxy_pass http://genarrative_api;',
|
||||
'proxy_set_header Cookie "";',
|
||||
'proxy_set_header Authorization "";',
|
||||
'proxy_connect_timeout 3s;',
|
||||
'proxy_read_timeout 15s;',
|
||||
'proxy_send_timeout 15s;',
|
||||
'proxy_buffering off;',
|
||||
'proxy_cache off;',
|
||||
'add_header X-Accel-Buffering no always;',
|
||||
]) {
|
||||
expect(clientDownloadsSnippet).toContain(directive);
|
||||
}
|
||||
expect(clientDownloadsSnippet).not.toMatch(
|
||||
/^\s*add_header\s+Cache-Control\b/mu,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user