ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ShipDesk前端Nginx独立发布教程

ShipDesk前端Nginx独立发布教程 我是如何让 ShipDesk 一键发布前端的从手工上传到 Nginx 自动回滚以前发布前端通常是这样一套流程npm run build 打包 dist 登录服务器 备份旧文件 上传新文件 nginx -t 重载 Nginx 祈祷页面能打开偶尔做一次还可以。如果前端每天都在更新或者项目交给其他人维护问题就会越来越明显上传容易漏文件改配置容易出错发布失败后还要手动恢复旧版本。这篇文章记录我把前端发布集成到 ShipDesk 的过程。最终效果是后端继续按照原来的方式发布 Java JAR前端则单独构建、上传和切换互相不影响。最后我们得到了什么在 ShipDesk 中选择前端项目点击发布后它会自动完成本地构建前端 ↓ 检查 dist 是否完整 ↓ 打包并上传 ↓ 校验文件是否传完整 ↓ 检查 Nginx 配置 ↓ 切换到新版本 ↓ 重载 Nginx ↓ 检查 HTTPS 是否正常如果中途出错ShipDesk 会尽量恢复到上一个可用版本。整个过程中不会停止后端 Java 服务。为什么前端和后端要分开发布前端和后端虽然一起提供一个网站但发布方式完全不同。后端发布的是正在运行的 Java 进程。发布新 JAR 时通常需要停止旧进程、替换文件、启动新进程再检查服务是否正常。前端发布的是一组静态文件。Vue、React 或其他 Vite 项目执行构建后最终就是index.html、JavaScript、CSS 和图片等文件Nginx 直接读取即可。如果把两者绑在一次发布中前端改一行文字也可能导致后端重启。更合理的方式是后端项目构建 JAR → 停止 Java → 发布 JAR → 启动 Java 前端项目构建 dist → 上传静态文件 → 切换 Nginx 目录这样前端发布不会打断后端服务后端发布也不会重复上传前端资源两边还可以分别回滚。先把服务器目录设计好假设前端部署在服务器的/opt/example-frontendNginx 在 Docker 容器中运行并且只把宿主机的html目录挂载进去宿主机/opt/example-frontend/html 容器内/usr/share/nginx/html最终目录应该这样设计/opt/example-frontend/ └── html/ ├── current - releases/release-002/html ├── previous - releases/release-001/html └── releases/ ├── release-001/html/index.html └── release-002/html/index.htmlNginx 永远只指向root /usr/share/nginx/html/current;发布新版本时不覆盖正在使用的文件只切换current。一个很容易踩到的 Docker 软链接问题最初很容易设计成/opt/example-frontend/ ├── html/ └── releases/ html/current - ../releases/release-001/html在宿主机上看这个链接是正常的。但进入 Docker 容器后容器只能看到挂载进来的html看不到旁边的releases。于是宿主机上链接正常容器里却找不到页面文件。正确做法是把版本目录放进html挂载范围内并使用容器能理解的相对链接current - releases/release-002/html不要使用这种宿主机绝对路径current - /opt/example-frontend/html/releases/release-002/html这是本次实现中最重要的架构约束之一。第一次接入时先保护线上文件如果服务器上已经有正在运行的前端第一次接入版本化发布时不能直接删除原来的html目录。先复制一份线上内容作为初始版本BASE/opt/example-frontendmkdir-p$BASE/html/releases/bootstrap/htmlcp-a$BASE/html/.$BASE/html/releases/bootstrap/html/if[-L$BASE/html/current];thenmv$BASE/html/current$BASE/html/current.legacyfiln-sreleases/bootstrap/html$BASE/html/current然后从容器内检查dockerexecfrontend-nginxtest-f/usr/share/nginx/html/current/index.html如果current不存在ShipDesk 应该停止并提示初始化而不是自行移动线上文件。Nginx 配置怎么写Nginx 主要做三件事提供前端静态文件、转发后端接口、处理 HTTPS。核心配置可以简化成下面这样server { listen 443 ssl; server_name example.com; root /usr/share/nginx/html/current; index index.html; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; location /healthz { access_log off; default_type text/plain; return 200 ok\n; } location ~ ^/(api|ws)(/|$) { proxy_pass http://host.docker.internal:8091; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_buffering off; } location /assets/ { try_files $uri 404; expires 1y; } location / { try_files $uri $uri/ /index.html; } }证书和私钥继续由服务器上的 Certbot 管理。ShipDesk 只上传 Nginx 配置不上传证书也不把证书私钥放进项目仓库。ShipDesk 里新增一个前端项目ShipDesk 原本已经支持后端 JAR 发布所以这次没有另起一套工具而是在项目配置中增加了模板静态文件 · Nginx前端项目需要填写配置示例项目名称knowledge-base-frontend本地工作目录/path/to/knowledge-base-frontend构建命令npm run build产物目录dist远程根目录/opt/example-frontend本地 Nginx 配置/path/to/frontend/deploy/nginx.conf远程 Nginx 配置/opt/example-frontend/nginx.confNginx 校验命令docker exec frontend-nginx nginx -tNginx reload 命令docker exec frontend-nginx nginx -s reload健康检查命令curl -kfsS https://example.com/healthz后端项目仍然使用原来的 JAR 模板。切换项目时ShipDesk 根据template决定走哪条发布流程。静态模板不应该要求填写 Java 停止命令、启动命令或 JVM 参数否则前端发布时容易误调用后端脚本。我们先把发布流程想清楚实现之前先把一次发布拆成几个动作在本地构建前端检查dist/index.html和dist/assets打包并计算 SHA-256获取远程发布锁上传到新的版本目录在服务器上再次校验文件上传 Nginx 候选配置执行nginx -t切换currentreload Nginx检查 HTTPS失败时恢复previous。这里最重要的是顺序Nginx 配置校验要在切换页面之前健康检查要在 reload 之后。ShipDesk 的一次发布过程第一步本地构建发布开始后ShipDesk 进入前端工作目录执行npmrun build构建失败时后面的上传和 Nginx 操作都不会执行。第二步检查产物不能只判断dist目录存在还要检查dist/index.html 必须存在 dist/assets/ 必须存在且非空这样可以避免构建命令虽然退出码为 0但产物目录为空最后把一个空站点发布到线上。第三步打包并校验ShipDesk 把整个dist目录打成临时 tar 包并计算 SHA-256。远程收到文件后再次计算两边不一致就停止发布。版本包会解压到/opt/example-frontend/html/releases/releaseId/html每次发布都有独立目录旧版本不会被新版本覆盖。第四步先检查 Nginx 配置候选配置先上传到临时文件然后备份旧配置安装候选配置执行dockerexecfrontend-nginx nginx-t只有命令成功才允许切换current。如果配置语法错误ShipDesk 恢复旧配置并清理未激活的新版本。第五步原子切换 current切换时不直接复制文件而是使用临时链接rm-fhtml/previousmvhtml/current html/previousln-sreleases/releaseId/html html/current.nextmv-Thtml/current.next html/current这样 Nginx 要么看到旧版本要么看到新版本不会看到一半旧文件、一半新文件。第六步reload 和健康检查dockerexecfrontend-nginx nginx-sreloadcurl-kfsShttps://example.com/healthz只有这两步都成功发布才算完成。发布失败时怎么回滚如果失败发生在切换前例如上传失败、SHA-256 不一致或nginx -t失败线上current还没有变化。这时恢复旧配置、删除新版本目录并释放远程锁即可。如果失败发生在切换后例如 reload 失败、健康检查失败或者容器内无法读取新页面则需要把current切回previous恢复旧 Nginx 配置再次 reload再次执行健康检查把回滚结果写入日志。回滚不能只打印一句“回滚成功”程序应该根据每个恢复动作的真实结果设置状态。我遇到的第一个问题sshpass 退出码 1部署时曾经看到bash: warning: setlocale: LC_ALL: cannot change locale (C.UTF-8) /bin/sh: warning: setlocale: LC_ALL: cannot change locale (C.UTF-8) sshpass 退出码 1第一眼很容易认为是 locale 导致 SSH 失败但继续拆开检查后真正失败的是test-L/opt/example-frontend/html/current服务器还没有完成第一次版本目录初始化因此发布引擎主动停止了流程。排查这类错误时建议把命令单独执行test-L/opt/example-frontend/html/currentechosymlink_exit$?dockerexecfrontend-nginxtest-f/usr/share/nginx/html/current/index.htmlechofile_exit$?dockerexecfrontend-nginx nginx-techonginx_test_exit$?C.UTF-8是服务器 locale 配置警告通常不是 SSH 失败的真正原因。它可以后续通过安装和生成对应 locale 来消除但不能代替对实际退出码的排查。我遇到的第二个问题健康检查通过了页面却可能打不开如果/healthz是 Nginx 直接返回的固定文本即使静态目录软链接断了健康检查仍可能返回 200。所以发布验证最好同时检查dockerexecfrontend-nginx nginx-tdockerexecfrontend-nginxtest-f/usr/share/nginx/html/current/index.htmltest$(curl-kfsShttps://example.com/healthz)ok如果条件允许再请求一次首页和一个静态资源验证用户真正访问的内容。这次实现中保留的安全边界发布工具会执行远程命令所以安全边界不能省略密码通过 ElectronsafeStorage保存使用密码时通过SSHPASS环境变量传递不把密码拼接进命令字符串所有远程路径使用统一的 shell quotingSSL 私钥只存在服务器远程发布使用项目级锁失败时清理临时文件但不删除current或previous正在引用的版本。不要把真实服务器密码、私钥、Token 或证书内容写进博客和 Git 仓库。怎么验证这套流程可靠单元测试中使用 fake runner 捕获 SSH、SCP 和远程命令重点验证命令顺序nginx -t发生在 reload 之前配置校验失败时不能切换current健康检查失败时会尝试恢复previous静态发布不会调用 Java stop/start成功和失败都能释放远程锁密码或私钥不会出现在日志中。本地验证npmtestnpmrun build服务器验证dockerexecfrontend-nginxtest-f/usr/share/nginx/html/current/index.htmldockerexecfrontend-nginx nginx-tcurl-kfsShttps://example.com/healthz最后总结把前端发布集成到 ShipDesk真正重要的不是“增加一个上传文件的按钮”而是把发布过程变成一条有顺序、有校验、能回滚的流水线构建 → 检查产物 → 打包上传 → 校验完整性 → 检查 Nginx 配置 → 切换版本 → reload → 验证页面 → 必要时回滚实现时最值得记住的两个细节是前端和后端应该独立发布前端发布不要顺手重启 Java 服务Docker 场景下版本目录必须位于容器挂载范围内软链接必须使用容器可见的相对路径。做好这两点前端发布就能从一次容易出错的手工操作变成一次可重复、可观察、可恢复的工程流程。
返回列表