刘光辉
昨天 bb638871a7fb692d80f1b7a758f991dc0879002c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# cx-web:SPA 静态托管 + /api、/websocket 反代 cx-gateway(compose 服务名直连)
# 本文件被 compose 挂载到容器 /etc/nginx/conf.d/default.conf(镜像内亦有同份 COPY 兜底);
# 现场改反代/开 TLS 直接编辑后 docker compose restart cx-web,无需重构建镜像。
 
server {
    listen 80;
    server_name _;
 
    root  /usr/share/nginx/html;
    index index.html;
 
    # 上传经 /api 反代(分片上传单片默认几 MB,放宽到 512m 兜底整文件直传场景)
    client_max_body_size 512m;
 
    gzip on;
    gzip_min_length 1k;
    gzip_types text/plain text/css application/javascript application/json
               application/xml image/svg+xml font/woff2;
 
    # 变量 proxy_pass + docker 内嵌 DNS 惰性解析:
    #   ① gateway 容器未起时 nginx 也能正常启动(静态 proxy_pass 会在启动期解析失败直接退出)
    #   ② gateway 重建换 IP 后按 valid=30s 重新解析,不会缓存旧 IP 一直 502
    resolver 127.0.0.11 valid=30s ipv6=off;
    set $gw http://cx-gateway:30000;
 
    # API:完整路径透传(proxy_pass 为变量且不带 URI => 原样转发,/api 前缀保留,网关按前缀路由)
    # ^~:命中此前缀后不再参与正则匹配——否则 /api/file/Image/xxx.jpg 会被下方
    # 扩展名长缓存正则劫走当本地静态文件找而 404(图片预览经 80 反代的场景,2026-07-18 现场实证)
    location ^~ /api/ {
        proxy_pass $gw;
        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;
        # 🔴 2026-08-11 补:本 location 原先没设读超时,走 nginx 默认 60s
        #(那个 3600s 只写在下方 /websocket/ 里,对 /api/ 不生效)。
        # 后果:任何 /api/** 超过 60 秒必然 504,且 nginx 只回一个自造的错误页,
        # 前端看不到后端的任何信息。实测撞上的是
        # POST /api/workflow/operator/CandidateUser/0(选审批人)首次调用,
        # nginx error log 原文 "upstream timed out ... while reading response header"。
        # 取 300s 与 Feign 的 client.config.default.readTimeout: 300000 对齐
        #(config/shared/frame-config.yaml),使超时判定发生在后端而非 nginx——
        # 后端超时至少能落日志、能回结构化错误。
        # ⚠️ 这只是不让它 504,**没有解决"接口慢"本身**:
        # 流程相关接口偏慢是既有问题,定位与优化见 tasks/todo.md 的待优化章节。
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
 
    # 报表设计(Univer 版):前端 VITE_GLOB_REPORT_API_URL 生产构建为空,报表请求落到本域
    # /api/Report,而网关 router.yaml 无此路由(该服务是独立发行包、不注册 Nacos)→ 404
    # "No static resource api/Report."。此处按最长前缀优先(比下方 ^~ /api/ 更长,与顺序无关)
    # 单独截走,直连服务。
    # ⚠️ 临时联调态:现指向宿主 java -jar 起的 32000;容器化(cx-report)后改成 $gw 同款
    #    变量形式 http://cx-report:32000。host.docker.internal 走容器 /etc/hosts,
    #    故此处用静态 proxy_pass(变量形式会绕过 hosts 直查 DNS 解析不到)。
    location ^~ /api/Report {
        proxy_pass http://host.docker.internal:32000;
        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;
    }
 
    # WebSocket:前端生产环境连 ${location.origin}/websocket/<token>,
    # 网关实际路由是 /api/message/websocket/**(2026-07-17 curl 实证:前者 200、后者 101),
    # 故重写补前缀再反代(rewrite 的 URI 会替换 proxy_pass 转发路径)
    location ^~ /websocket/ {
        rewrite ^/websocket/(.*)$ /api/message/websocket/$1 break;
        proxy_pass $gw;
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host       $host;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
 
    # 运行时配置注入点(VITE_GLOB_* 三地址):改此文件免重构建生效,绝不能被长缓存
    location = /_app.config.js {
        add_header Cache-Control "no-cache";
    }
 
    # vite 产物带内容 hash(分布在 assets/ js/ css/ cdn/ 等目录),按扩展名长缓存;
    # html 不在列(index.html 走下方 no-cache);_app.config.js 由上方精确匹配优先兜住
    location ~* \.(js|mjs|css|woff2?|ttf|eot|png|jpe?g|gif|svg|ico|mp3|mp4)$ {
        add_header Cache-Control "public, max-age=2592000, immutable";
    }
 
    # history 路由:内页刷新/直达一律回落 index.html;index 本身不缓存(发版即生效)
    location / {
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "no-cache";
    }
}
 
# ── TLS(本期不启用,现场有证书后三步开启,详见交付包 INSTALL.md §5)──────────
# ① 证书放到仓库/交付包根 docker/frontend/certs/{server.crt,server.key}
# ② docker-compose.yml 的 cx-web 解开 "443:443" 端口与 certs 挂载两行注释
# ③ 解开下方 server 块注释后 docker compose restart cx-web
#
# server {
#     listen 443 ssl;
#     server_name _;
#     ssl_certificate     /etc/nginx/certs/server.crt;
#     ssl_certificate_key /etc/nginx/certs/server.key;
#     ssl_protocols TLSv1.2 TLSv1.3;
#
#     # 与 80 端口 server 块保持一致(root/反代/缓存各 location 原样复制到此处)
# }