刘光辉
3 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 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,**没有解决"接口慢"本身**:
        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;
    }
 
    # OnlyOffice 插件(2026-09-23):make web-dist 把 registry.yaml 的插件构建进
    # dist/onlyoffice-plugins/<release>/<code>/;后端 cx-biz-common 按
    # ONLYOFFICE_PLUGIN_PUBLIC_BASE_URL=http(s)://<本站>/onlyoffice-plugins 签发 pluginsData。
    # (本地开发走前端仓库 pnpm dev:onlyoffice 的 4173 开发网关,不经这里。)
    # ^~:不让下方扩展名长缓存正则接管——release 目录名固定 0.1.0、非每版不可变,必须 no-cache。
    # CORS:config.json 由编辑器页面(DS :20000,跨源)拉取。
    location ^~ /onlyoffice-plugins/ {
        add_header Cache-Control "no-cache";
        add_header Access-Control-Allow-Origin "*";
 
        # 插件 index.html 引用 ../v1/plugins.{js,css}=ONLYOFFICE 插件 SDK,由 DS 的
        # /sdkjs-plugins/v1/ 提供(开发网关同款转发)。变量 proxy_pass 惰性解析:DS 未起不影响 nginx 启动。
        location ~ ^/onlyoffice-plugins/[^/]+/v1/(.+)$ {
            set $oo cx-onlyoffice;
            proxy_pass http://$oo/sdkjs-plugins/v1/$1;
            add_header Cache-Control "no-cache";
            add_header Access-Control-Allow-Origin "*";
        }
    }
 
    # 运行时配置注入点(VITE_GLOB_* 三地址):改此文件免重构建生效,绝不能被长缓存
    location = /_app.config.js {
        add_header Cache-Control "no-cache";
    }
 
    location ~* \.mjs$ {
        default_type application/javascript;
        add_header Cache-Control "public, max-age=2592000, immutable";
    }
 
    # 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 原样复制到此处)
# }