diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java index 9ed5c33..4b040e4 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java @@ -1097,91 +1097,34 @@ public class SystemUpdateService { private void patchConfigs(Consumer emit) { emit.accept(">>> 检查并修复配置文件..."); - patchNginxFileRoute(emit); - patchNginxUpdateTimeout(emit); - patchNginxWebSocket(emit); + syncNginxConfig(emit); patchDockerComposeFileService(emit); patchDockerComposeUpdateService(emit); } - private void patchNginxUpdateTimeout(Consumer emit) { - Path conf = Paths.get(deployRoot, "config", "nginx", "conf.d", "xuqm.conf"); - if (!Files.exists(conf)) return; - try { - String content = Files.readString(conf); - if (content.contains("location ~ ^/api/system/") || content.contains("location = /api/system/update")) return; - String anchor = " # 核心 API(兜底,在所有具体 /api/xxx/ 之后)\n location /api/ {"; - if (!content.contains(anchor)) { - emit.accept(" [跳过] nginx 更新超时补丁锚点未找到,请手动检查"); - return; - } - String injection = " # 一键更新/重置:操作耗时较长,需要更长超时(精确匹配,优先于 /api/ 前缀)\n" - + " location ~ ^/api/system/(update|reset)$ {\n" - + " set $svc tenant-service;\n" - + " proxy_pass http://$svc:9001;\n" - + " proxy_set_header Host $host;\n" - + " proxy_set_header X-Real-IP $remote_addr;\n" - + " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n" - + " proxy_buffering off;\n" - + " proxy_read_timeout 600s;\n" - + " proxy_send_timeout 600s;\n" - + " }\n\n" - + anchor; - Files.writeString(conf, content.replace(anchor, injection), StandardOpenOption.TRUNCATE_EXISTING); - emit.accept(" [已修复] nginx: 补齐 /api/system/(update|reset) 600s 超时"); - } catch (IOException e) { - emit.accept(" [警告] nginx 更新超时修复失败: " + e.getMessage()); - } - } - /** - * 为 update-service 注入 nginx WebSocket 代理配置。 - * WebSocket 需要 HTTP/1.1 升级头和长时间超时。 + * 将 JAR 包内打包的 nginx 配置同步到宿主机挂载目录。 + * 以 JAR 包版本为准(覆盖写入),保证每次发版后配置始终最新。 + * 新增服务只需修改 src/main/resources/nginx/xuqm.conf 并发版, + * 租户点一键更新即可自动同步并重建 nginx 容器。 */ - private void patchNginxWebSocket(Consumer emit) { - Path conf = Paths.get(deployRoot, "config", "nginx", "conf.d", "xuqm.conf"); - if (!Files.exists(conf)) return; - try { - String content = Files.readString(conf); - if (content.contains("location /ws/")) return; - - String anchor = " # 核心 API(兜底,在所有具体 /api/xxx/ 之后)\n location /api/ {"; - if (!content.contains(anchor)) { - emit.accept(" [跳过] nginx WebSocket 补丁锚点未找到,请手动检查"); + private void syncNginxConfig(Consumer emit) { + Path dest = Paths.get(deployRoot, "config", "nginx", "conf.d", "xuqm.conf"); + try (java.io.InputStream is = getClass().getResourceAsStream("/nginx/xuqm.conf")) { + if (is == null) { + emit.accept(" [跳过] nginx 配置模板未内置,跳过同步"); return; } - String injection = " # WebSocket 实时通知(update-service 版本发布推送)\n" - + " location /ws/ {\n" - + " set $svc update-service;\n" - + " proxy_pass http://$svc:8084;\n" - + " proxy_http_version 1.1;\n" - + " proxy_set_header Upgrade $http_upgrade;\n" - + " proxy_set_header Connection \"upgrade\";\n" - + " proxy_set_header Host $host;\n" - + " proxy_set_header X-Real-IP $remote_addr;\n" - + " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n" - + " proxy_read_timeout 86400s;\n" - + " proxy_send_timeout 86400s;\n" - + " }\n\n" - + anchor; - Files.writeString(conf, content.replace(anchor, injection), StandardOpenOption.TRUNCATE_EXISTING); - emit.accept(" [已修复] nginx: 补齐 /ws/ WebSocket 代理(update-service)"); + byte[] newContent = is.readAllBytes(); + if (Files.exists(dest) && java.util.Arrays.equals(newContent, Files.readAllBytes(dest))) { + emit.accept(" [跳过] nginx 配置无变化"); + return; + } + Files.createDirectories(dest.getParent()); + Files.write(dest, newContent, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + emit.accept(" [已同步] nginx 配置已更新"); } catch (IOException e) { - emit.accept(" [警告] nginx WebSocket 修复失败: " + e.getMessage()); - } - } - - private void patchNginxFileRoute(Consumer emit) { - Path conf = Paths.get(deployRoot, "config", "nginx", "conf.d", "xuqm.conf"); - if (!Files.exists(conf)) return; - try { - String content = Files.readString(conf); - if (!content.contains("location /file/")) return; - Files.writeString(conf, content.replace("location /file/", "location /api/file/"), - StandardOpenOption.TRUNCATE_EXISTING); - emit.accept(" [已修复] nginx: location /file/ → /api/file/"); - } catch (IOException e) { - emit.accept(" [警告] nginx 配置修复失败: " + e.getMessage()); + emit.accept(" [警告] nginx 配置同步失败: " + e.getMessage()); } } diff --git a/tenant-service/src/main/resources/nginx/xuqm.conf b/tenant-service/src/main/resources/nginx/xuqm.conf new file mode 100644 index 0000000..0aab6bc --- /dev/null +++ b/tenant-service/src/main/resources/nginx/xuqm.conf @@ -0,0 +1,157 @@ +server { + listen 80; + server_name _; + + charset utf-8; + client_max_body_size 100m; + + # Docker 内置 DNS resolver + 变量化 proxy_pass: + # nginx 对静态 proxy_pass 在启动时静态解析主机名,容器重建后 IP 变更即失效。 + # 改为变量写法后,resolver 每 10s 重新解析,容器重建无需重启/reload nginx。 + resolver 127.0.0.11 valid=10s ipv6=off; + + # 健康检查(宿主机 nginx 探活用) + location /health { + return 200 "ok\n"; + add_header Content-Type text/plain; + } + + # 版本管理 — 必须在通用 /api/ 之前 + location /api/v1/updates/ { + set $svc update-service; + proxy_pass http://$svc:8084; + 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 60s; + } + + location /api/v1/rn/ { + set $svc update-service; + proxy_pass http://$svc:8084; + 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 120s; + } + + # IM HTTP — 必须在通用 /api/ 之前 + location /api/im/ { + set $svc im-service; + proxy_pass http://$svc:8082; + 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 60s; + } + + # IM WebSocket + location /ws/im { + set $svc im-service; + proxy_pass http://$svc:8082; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 3600s; + } + + # 版本推送 WebSocket(update-service 实时通知) + location /ws/ { + set $svc update-service; + proxy_pass http://$svc:8084; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + 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 86400s; + proxy_send_timeout 86400s; + } + + # Bug 收集 — 必须在通用 /api/ 之前 + # rewrite 剥离 /api 前缀:/api/bugcollect/xxx → /bugcollect/xxx + location /api/bugcollect/ { + set $svc bugcollect-service; + rewrite ^/api(/bugcollect/.*)$ $1 break; + proxy_pass http://$svc:9006; + 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 60s; + } + + # License — 必须在通用 /api/ 之前 + location /api/license/ { + set $svc license-service; + proxy_pass http://$svc:8085; + 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 60s; + } + + # 文件上传下载 — 必须在通用 /api/ 之前 + location /api/file/ { + set $svc file-service; + proxy_pass http://$svc:8086; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + client_max_body_size 500m; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + # 一键更新/重置:操作耗时较长,需要更长超时(精确匹配,优先于 /api/ 前缀) + location ~ ^/api/system/(update|reset)$ { + set $svc tenant-service; + proxy_pass http://$svc:9001; + 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_buffering off; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + } + + # 私有化部署:精确拦截用户自注册(必须在通用 /api/ 之前) + location = /api/auth/register { + add_header Content-Type 'application/json; charset=utf-8' always; + return 403 '{"code":403,"status":"1","data":null,"message":"私有化部署已禁用用户自注册"}'; + } + + # 核心 API(兜底,在所有具体 /api/xxx/ 之后) + location /api/ { + set $svc tenant-service; + proxy_pass http://$svc:9001; + 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 60s; + } + + location /actuator/ { + set $svc tenant-service; + proxy_pass http://$svc:9001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # 控制台前端(兜底路由,必须最后) + # sub_filter 替换前端 JS bundle 中硬编码的公有平台地址为私有部署地址 + location / { + set $svc tenant-web; + proxy_pass http://$svc:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Accept-Encoding ""; + sub_filter 'wss://im.dev.xuqinmin.com/ws/im' 'wss://$host/ws/im'; + sub_filter 'https://im.dev.xuqinmin.com' 'https://$host'; + sub_filter 'https://dev.xuqinmin.com' 'https://$host'; + sub_filter_once off; + sub_filter_types text/html text/javascript application/javascript; + } +}