☰
前后台分离项目打包部署实战:Vue+Spring Boot+Nginx+Docker 完整指南
2026/10/2 22:42:09 网站建设 项目流程

前后台分离项目,代码写得再漂亮,最后一步打包部署要是掉了链子,前面所有功夫全白费。尤其是前后台分离这套架构,前端、后端各自独立构建、独立部署,中间还夹着一个反向代理层,任何一个环节配置对不上,线上就是一片白屏加一串 502。这篇文章我把整个“前后台分离打包部署项目”从思路到落地完整拆一遍,覆盖前端 Vue 打包、后端 Spring Boot 构建、Nginx 路由与缓存配置、Docker 容器化编排这些核心环节,也会把我实际操作中踩过的坑一次说清楚。适合正在做前后台分离项目、准备把项目从本地环境迁到测试服务器或生产环境的人参考,后端是 Java Spring Boot、前端是 Vue 系的场景可以直接照搬思路,其他技术栈也能借方法论。

这里先说明白“前后台分离”在部署层面到底意味着什么。开发环境下,前端跑在 Node 热更新服务器上,后端跑在 Tomcat 内嵌端口上,两者通过代理各干各的,很爽。但生产环境根本没有 Node 进程,也没有 IDE,所有代码都必须被打成静态文件和可执行程序,再由 Web 服务器和进程管理器把它们拉起来。这个从“开发态”到“生产态”的转换,就是打包和部署的核心价值。所以与其说这是一个部署教程,不如说是一次完整的架构落地梳理。

1. 前后台分离部署的整体思路与方案选型

1.1 前后台分离架构下部署的真正难点

前后台分离部署,难的不是“打包”这个动作,而是“打包完之后怎么让它们协作”。开发时前端代理到后端的地址是 localhost:8080,后端连的数据库是本地 root 密码,CORS 全放开无所谓。但到了服务器上,前端静态文件要有人托管,后端进程要常驻且崩溃自动拉起,接口域名不能写死,数据库密码不能裸奔,跨域要么关掉走代理、要么精确配置。这背后是一整套部署规范的建立。

另外,前后台分离还带来了资源组织方式的变化。前端构建产物是一堆带 hash 的 JS/CSS 文件和 index.html,后端产物则是一个可执行 jar 包或 war 包。两者生命周期不同、更新频率不同、回滚策略不同,必须分开管理。很多人第一次部署时图省事,把前端打包好的 dist 直接塞进后端项目的 resources 目录一起发布,这种做法在小 demo 里能跑,但到了真实项目里,前端改一行文案后端就得重新构建发版,非常痛苦。所以从一开始就要明确:前端产物归前端,后端产物归后端,中间用 Nginx 或 CDN 衔接。

1.2 一套能落地的部署架构长什么样

一套经得起生产环境考验的前后台分离部署架构,至少包含这几个角色:前端静态资源服务器(Nginx)、后端应用服务(Spring Boot 内嵌 Tomcat)、反向代理层(同样是 Nginx,可以跟前端静态资源服务器合并)、数据库与中间件(MySQL、Redis)、以及可选的容器化编排层(Docker Compose 或 K8s)。

我实际项目中常用的是“Nginx 双职责”方案:同一个 Nginx 实例既托管前端静态文件,又把 /api 和 /prod-api 这类前缀的请求反向代理到后端服务。这样浏览器永远只跟同一个域名打交道,不存在跨域问题,Cookie 也能顺畅携带,同时运维只需要维护一个入口。如果你有独立域名和 HTTPS 证书,这个方案尤其舒服,证书只需要配一层。

架构确定之后,打包部署就变成了三个标准化步骤:前端构建、后端构建、配置 Nginx 并启动。下面我从环境准备讲起,逐步把每一步的操作细节和参数选择逻辑讲透。

2. 环境准备与项目结构调整

2.1 服务器端要准备哪些基础环境

不管项目多简单,服务器上这几样东西必须齐:JDK(版本与后端项目一致,比如 Java 8 或 Java 17)、Nginx、Git(用于拉取代码)、Docker 与 Docker Compose(如果走容器化)。数据库和 Redis 可以装在宿主机,也可以用 Docker 起,生产环境我建议独立安装或用云数据库,避免容器重启导致数据丢失。

环境版本这里有个常见坑:服务器上 JDK 版本与本地开发版本不一致,明明本地打包运行都正常,放到服务器上 jar 包启动直接报 UnsupportedClassVersionError。这是 JDK 主版本号不匹配导致的,比如本地用 JDK 17 编译的 class 文件,扔到 JDK 8 的运行时里必然跑不起来。所以条件允许时,尽量让本地构建机的 JDK 与服务器运行时 JDK 保持同一大版本。

Nginx 编译安装和 yum/apt 安装都行,但注意 Nginx 版本不要太老,至少 1.18 以上,对 HTTP/2、gzip 静态压缩的支持更完善。安装完先用 nginx -v 确认版本,再检查配置文件语法 nginx -t,避免后面写配置时基础环境出问题。

2.2 前端项目的打包前配置调整

前端项目里,最影响打包结果的就是publicPath、路由模式、接口请求地址这几个配置。Vue CLI 项目在vue.config.js里通过publicPath控制静态资源的引用基础路径,Vite 项目则是base配置。很多人的前端打包后白屏,就是publicPath写错了。如果你的站点部署在域名根路径下,publicPath可以配成'/';如果要部署在http://域名/xxx/这样的子路径下,就得设成'/xxx/'。否则页面加载时 index.html 引用 JS 和 CSS 的路径全部对不上,自然白屏。

路由模式这块,如果用了 Vite 或 Vue Router 的 HTML5 History 模式(createWebHistory),打包部署后访问非首页路径刷新会 404,因为服务器上根本不存在那个路径对应的物理文件。这个问题必须通过 Nginx 的 try_files 配置做回退,后面我会给出完整的 Nginx 配置片段。如果你不想处理这个问题,可以把路由模式改成 Hash 模式(createWebHashHistory),URL 会多一个 # 号,但部署上省心很多。

接口地址的配置要单独拎出来说。我见过太多项目把接口请求地址写死在代码里,比如axios.defaults.baseURL = 'http://localhost:8080/api',本地开发没问题,一打包到服务器,所有请求全打到本地回环地址上,直接废掉。正确处理方式是区分环境,生产环境走相对路径/api,由 Nginx 把/api反向代理到后端真实地址。这样前端产物里不包含任何服务器 IP 或域名,迁移环境时不用重新打包。

2.3 后端项目的打包前配置调整

后端项目打包前,最需要关注的是配置文件里的环境相关项。Spring Boot 项目通常有application.yml、application-dev.yml、application-prod.yml,打包前要确认spring.profiles.active设置成了生产环境。如果你有多套环境,强烈建议用 Maven profile 结合 Spring Boot profile 做联动,打包时通过-Pprod参数指定。

数据源信息、Redis 地址这些敏感配置,生产环境不要硬编码在源码里。常见做法是把配置外置,比如 jar 包同级的config/目录下放置application-prod.yml,或者通过启动参数--spring.config.location=file:/path/to/config/指定外部配置。Spring Boot 的应用优先级中,外部配置文件优先级高于 jar 包内部的配置文件,所以你不用重新打包就能修改生产环境配置,这非常实用。我的习惯是 jar 包里只放开发环境的默认配置,生产环境配置全部外置,这样打包一次可以部署到多台服务器,每台只需改自己的配置即可。

3. 前端打包实操:从 Vue 项目到 Nginx 可托管的静态资源

3.1 生产构建与开发构建的差异

开发时npm run dev启动的是开发服务器,带热更新、带源码映射,构建速度慢但调试方便。生产环境要的是npm run build,这个命令会做代码压缩、tree shaking、资源指纹(hash)等优化,输出到一个 dist 目录。Vue CLI 默认输出到项目根目录的dist文件夹,Vite 默认输出到项目根目录的dist文件夹。

构建命令跑完先别急着上传,先做三件事:检查 dist 目录结构是否完整(index.html、static 或 assets 目录、favicon 等)、打开本地预览确认页面渲染正常(可以用npx serve dist起一个本地静态服务)、检查控制台有没有资源加载 404 或接口请求报错。很多人这一步跳过,直接把 dist 丢到服务器上,结果白屏了也分不清是前端资源路径问题还是后端接口问题。

3.2 前端构建命令的常见选择与参数

如果你用的是 npm,那么生产构建命令一般是npm run build,但二次封装时可以加参数。Vite 项目构建时可以指定--outDir控制输出目录,也可以通过环境变量VITE_APP_ENV=production vite build让项目代码读取不同的环境变量文件。

构建阶段还需要留意依赖安装的问题。npm install与npm ci是有区别的:npm install会根据 package.json 的语义化版本范围安装依赖,可能装到新版本;npm ci严格按照 package-lock.json 锁定文件安装,保证构建环境的一致性。生产环境建议使用npm ci,并且把node_modules目录彻底删掉再装,避免本地残留的依赖影响构建结果。如果项目里有原生模块或者依赖了特定平台的二进制文件,构建机与服务器的操作系统不一致也可能引发问题,尽量保证构建环境纯净。

3.3 打包后布局异常之类的问题怎么排查

热词里“vue 打包后布局异常”是个高频问题。这类问题通常集中在几个原因:第一,CSS 文件没有被正确加载,页面样式完全丢失,这时打开控制台大概率是 CSS 文件 404,基本都是publicPath或base配置错了;第二,引入的第三方 UI 库样式顺序变了,打包后 CSS 被压缩合并,某些覆盖样式失效,解决方法是把自定义样式放到入口文件最后引入,或者开启 CSS 模块化避免全局污染;第三,字体文件和图片路径不对,构建后资源文件没有正确复制到 dist 目录,需要检查打包配置里的 asset 处理规则。

另外,如果前端项目用到了 Web Worker 或者动态引入(dynamic import)的模块,打包后这些异步加载的 chunk 也容易出现路径问题。排查思路很简单:F12 打开开发者工具,看 Network 面板里哪些请求 404 了,然后顺着 URL 反推构建配置里对应的路径设置,百分之八九十的问题都能通过 publicPath 修正解决。

3.4 Webpack 打包优化配置的小经验

如果项目体积大、首屏加载慢,可以针对 Webpack 做几个常规优化。第一是开启 gzip 压缩,Nginx 端开启gzip on,前端可以在打包时用 compression-webpack-plugin 预生成.gz文件,这样 Nginx 可以直接返回压缩包,减少服务器 CPU 开销。第二是代码分割,Webpack 的splitChunks配置可以把第三方库(vendor)与业务代码分离,让浏览器缓存更持久。第三是生产环境关闭 source map,productionSourceMap: false,否则会大大增加打包体积,也容易暴露源码。

这些优化不是必须的,但对于项目体量较大、用户分布广的场景,收益很明显。具体的配置参数需要根据项目实际情况调整,这里不展开每个插件的完整配置,重点是理解“预压缩 + 代码分割 + 关闭调试信息”这三个方向。

4. 后端打包实操:Spring Boot 项目从 IDEA 到可执行 jar

4.1 Maven 多模块项目的 package 流程

Spring Boot 后端项目如果是单模块,直接执行mvn clean package就好。但真实项目更多的是多模块结构,比如 parent 工程下包含 common、system、admin 等多个子模块。这种项目打包时要特别注意打包顺序:先安装被依赖的基础模块到本地仓库,再打包可执行模块。如果直接在最外层执行mvn clean package,Maven 会根据 reactor 顺序自动处理模块依赖,通常没问题;但如果某个模块依赖的是本地仓库里不存在的快照版本,就要先mvn install基础模块。

另一个常见报错是“intellij+maven项目打包报错”,表现形式五花八门:编译失败、测试失败、依赖找不到。编译失败先看具体的报错信息,很多时候是 Lombok 注解处理器与 JDK 版本不兼容导致的,需要检查 Lombok 版本是否支持当前 JDK。测试失败可以执行mvn clean package -DskipTests跳过测试,但建议先在本地跑一遍关键测试确认代码没大问题。依赖找不到则多半是私服地址没有配置,或者某个依赖没有从远程仓库拉取成功。

4.2 构建可执行 jar 的关键配置

Spring Boot 项目要打成可执行的 fat jar(包含内嵌 Tomcat 和所有依赖),必须在 pom.xml 里配置spring-boot-maven-plugin。这个插件默认在mvn package阶段会重新打包,生成的可执行 jar 和普通 jar 不同。配置里通常要指定mainClass作为启动入口。

另外,多环境配置可以结合 Maven profile 做资源过滤。比如在 pom.xml 里定义 prod 和 dev 两个 profile,每个 profile 对应不同的application-{profile}.yml,构建时用-Pprod指定激活哪个。这样打包出来的 jar 里已经包含了对应环境的默认配置,再配合外置配置覆盖,灵活性就很高。

4.3 IDEA 里直接构建 Docker 镜像

“idea 打包 docker镜像”这种需求,本质上是在构建阶段生成镜像,免去手动把 jar 包拷到服务器再写 Dockerfile 的操作。前提是你本机装了 Docker Desktop,并且在 IDEA 里配置了 Docker 插件连接本地 Docker 守护进程。

操作路径是:在 IDEA 右侧的 Maven 面板里,找到spring-boot:build-image这个 goal(Spring Boot 2.3+ 提供),双击执行,插件会自动根据项目配置生成一个可运行镜像。这套方案用的是 Cloud Native Buildpacks 技术,不需要编写 Dockerfile,但生成的镜像名字和标签需要你提前在 pom.xml 里通过image配置指定。如果你的项目对基础镜像、启动命令有特殊要求,还是建议自己写 Dockerfile 然后用命令行docker build -t 镜像名:标签 .构建。

用 IDEA 构建镜像确实方便,但我个人在实际项目中还是更倾向于在服务器上用 Dockerfile 构建,原因有两个:一是服务器构建出的镜像与运行时完全一致,避免本机与服务器架构差异(比如 Apple Silicon 的 arm64 镜像在 x86 服务器上有些基础镜像会有兼容性问题);二是 CI/CD 流水线拉代码到服务器后直接构建,流程更统一。

5. Nginx 配置与前后台联调

5.1 静态资源托管与 History 路由回退

前端 dist 上传到服务器后,Nginx 需要把它作为静态资源目录暴露出去。最简单配置如下:

server { listen 80; server_name your-domain.com; root /var/www/frontend/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }

这里最关键的就是try_files $uri $uri/ /index.html。它的作用是:当用户访问/about这个路径时,Nginx 先看有没有对应的实体文件$uri,没有再找目录$uri/,都找不到就回退到/index.html。这样 Vue Router 的 History 模式刷新页面就不会 404。如果你的前端项目用了 Hash 路由,这个配置可以省略,但我还是建议配置上,因为不排除有的页面通过 URL 直接访问。

5.2 反向代理解决跨域与接口转发

前端页面能正常打开了,接下来要让页面里的接口请求打到后端服务。假设前端请求统一带/api前缀,经过 Nginx 转发到 Spring Boot 服务的http://127.0.0.1:8080,配置如下:

location /api/ { proxy_pass http://127.0.0.1:8080/; 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_pass末尾的斜杠很关键。http://127.0.0.1:8080/带斜杠时,Nginx 会把请求 URI 中匹配到的/api/前缀去掉再转发,所以前端请求/api/user/list会变成后端/user/list。如果你不想去掉前缀,就把proxy_pass配成http://127.0.0.1:8080(不带斜杠),后端接收到的还是/api/user/list。这个规则容易混淆,每次配完最好用 curl 实测一下转发效果。

5.3 静态资源缓存策略与 gzip 压缩

前端静态资源带 hash 的文件名意味着内容变了文件名就变,非常适合长期缓存。可以在 Nginx 里针对这类文件做强缓存:

location /assets/ { expires 30d; add_header Cache-Control "public, immutable"; }

而 index.html 不能被缓存,否则前端发版后用户浏览器还停留在旧页面。可以单独设置location = /index.html { add_header Cache-Control "no-cache"; }。

gzip 压缩同样在 Nginx 层开启即可:

gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss image/svg+xml; gzip_min_length 1k;

如果前端构建时已经用插件预生成了.gz文件,可以配合gzip_static on直接返回预压缩文件,减少服务器端压缩的 CPU 开销。

5.4 多个不同结构的项目共用 Nginx 的问题

热词里有“nginx部署多个web项目”,这在实际工作中也很常见。如果一台服务器上要跑多个前后台分离项目,每个项目分配不同的 server_name 或不同的 location 前缀即可。用不同域名区分时,写多个 server 块;用同一域名不同路径区分时,在 location 层级做隔离。

后一种情况要注意:多个前端项目共用一个域名时,之前说的 History 路由回退try_files ... /index.html可能互相覆盖。比如项目 A 挂在/a/下,项目 B 挂在/b/下,配置必须写成location ^~ /a/ { alias /var/www/project-a/; try_files $uri $uri/ /a/index.html; }。这里的 alias 和 try_files 回退路径都要带上前缀,非常容易出错。我建议能用子域名区分就用子域名区分,配置简单,项目之间彻底隔离,排查问题也方便。

6. Docker 容器化部署与生产环境落地

6.1 后端 Dockerfile 编写与镜像构建

后端容器化的核心是写一个干净的 Dockerfile。我用得比较多的是多阶段构建,虽然对单模块项目稍显冗余,但能控制最终镜像体积。下面是一个简化的两阶段后端 Dockerfile:

# 构建阶段 FROM maven:3.8-openjdk-11 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 运行阶段 FROM openjdk:11-jre-slim WORKDIR /app COPY --from=builder /build/target/demo.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"]

第一段负责构建,第二段只拷贝构建产物,镜像里不会残留 Maven 和源码,体积小很多。如果你的项目是纯单模块且构建时间不长,也可以把 Dockerfile 简化成只包含运行阶段,手动把 jar 拷进去:

FROM openjdk:11-jre-slim WORKDIR /app COPY demo.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"]

这里有一个生产上必须注意的细节:容器内进程要以非 root 用户运行。可以在 Dockerfile 里创建专用用户,避免容器权限过大。Jenkins 或 GitLab CI 构建时,镜像标签建议用构建时间和 Git commit 组合,比如demo:20250518-3f2a9c1,这样回滚时可以精确定位到代码版本。

6.2 前端 Dockerfile 与 Nginx 基础镜像组合

前端容器化有两种思路:一种是把打包出来的 dist 目录挂载到宿主机 Nginx 上,Nginx 装在宿主机;另一种是前端也容器化,用 nginx 镜像承载静态资源。第二种更干净,迁移和扩展都方便。

前端容器化的典型做法是两个阶段:第一阶段用 node 镜像执行构建,第二阶段把 dist 目录复制到 nginx 镜像中,覆盖默认的/usr/share/nginx/html。同时把配置好的 nginx.conf 也复制进去:

# 构建阶段 FROM node:18-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # 运行阶段 FROM nginx:1.24-alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

这样构建出的前端镜像自带 Nginx 配置,部署时只需要把后端地址通过环境变量传入即可。为了让后端地址不写死在 nginx.conf 里,可以在启动脚本里用 envsubst 替换模板变量,或者通过 docker-compose 的 environment 传递,再用自定义 entrypoint 渲染配置。这个技巧在微服务场景下非常有用,一个镜像可以部署到多个环境,配置由运行时注入。

6.3 docker-compose 一键编排前后台服务

当前台、后台都要容器化之后,用 docker-compose 编排是最有效率的方式。一个简单的docker-compose.yml示例如下:

version: '3.8' services: frontend: image: my-frontend:latest ports: - "80:80" depends_on: - backend restart: always backend: image: my-backend:latest ports: - "8080:8080" environment: SPRING_PROFILES_ACTIVE: prod DB_HOST: mysql REDIS_HOST: redis depends_on: - mysql - redis restart: always mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: demo volumes: - mysql-data:/var/lib/mysql restart: always redis: image: redis:7-alpine volumes: - redis-data:/data restart: always volumes: mysql-data: redis-data:

这个编排文件里,数据库和 Redis 也给容器化了,数据通过 volume 持久化。生产环境如果数据库有备份需求、主从复制需求,建议单独管理数据库服务,不一定要放进 compose 里。compose 部署时先docker compose build构建镜像,再docker compose up -d启动服务,最后用docker compose ps查看所有服务状态。如果服务起不来,用docker compose logs -f查看日志排查。

关于 hot restart 的问题:compose 文件里的restart: always保证了服务器重启后容器自动拉起来,这是生产环境必须具备的。另外,当你更新了某张后端镜像后,执行docker compose up -d --build时,compose 会检测到镜像变化后重建容器,这个流程可以用来做简单版本的滚动发布。

6.4 Redis、MySQL 等依赖服务的容器化部署要点

Redis 用 Docker 部署非常简单,一个命令就能跑起来。但生产环境用 Docker 起 Redis 要注意:一定要挂载数据卷,否则容器删除后数据全部丢失;Redis 默认没有密码认证,如果端口暴露在外网,一定会被扫描爆破。建议通过--requirepass设置强密码,或者在 compose 文件里通过command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes配置。

MySQL 用 Docker 部署要特别注意字符集和时区问题。MySQL 8.0 默认字符集是 utf8mb4,一般够用,但如果业务上有特殊字符需求,需要在启动参数或配置文件里显式指定。时区问题建议在连接 URL 里显式指定serverTimezone=Asia/Shanghai,避免后端和数据库时区不一致导致时间数据错乱。数据卷挂载是必须的,同时定期做逻辑备份,不能只依赖 Docker volume,防止存储节点故障导致数据彻底丢失。

6.5 容器化部署的监控与日志

部署上去不代表就完事了。生产环境一定要有基本的监控和日志收集。最简单的方式是看 Docker 日志:docker compose logs -f --tail=100 backend。但这只适合登录服务器排查问题。如果项目规模再大一点,可以引入 Prometheus 监控和 Grafana 展示,把 Node Exporter、cAdvisor 等指标采集起来。这个方向我在另一个项目里专门整理过完整的监控方案,核心是用 Prometheus 抓取指标、Grafana 做可视化看板、Alertmanager 做告警通知。

日志方面,容器日志默认输出到 stdout,然后由 Docker 接管。如果要更精细地做日志分析,可以把日志挂载出来或用日志采集组件。对中小项目,我建议先把 Nginx access log 和 Spring Boot 日志目录挂载到宿主机固定目录,方便后续对接采集工具。

7. 常见问题与排查技巧实录

7.1 高频问题速查表

我把实战中遇到的高频问题整理成一张表,方便大家对照排查:

现象可能原因排查与解决
前端页面白屏,控制台 JS/CSS 404publicPath或base配置错误打开 Network 看失败资源 URL,根据部署路径修正构建配置
页面刷新后 404Vue Router History 模式没有配置回退Nginx 加try_files $uri $uri/ /index.html;
接口请求失败,报跨域错误前端请求了不同源的后端地址统一改用相对路径/api,由 Nginx 反向代理
后端启动报端口被占用宿主机端口或容器端口冲突用lsof -i:8080排查占用进程,修改端口映射
容器内连不上宿主机数据库数据库地址写成了 localhost容器内 localhost 是容器自己,宿主机数据库要用宿主机局域网 IP 或 host 网络模式
jar 包启动报 class 版本错误构建 JDK 与运行 JDK 版本不一致统一 JDK 大版本,重新打包
Docker 构建时 Maven 下载依赖太慢国内网络访问 Maven 中央仓库慢配置阿里云等镜像加速,或在构建阶段挂载本机 maven 仓库缓存
前后端都启动后,前端仍显示 502Nginx 代理的后端地址不通在服务器上 curl 后端接口地址,确认后端进程和端口正常

7.2 一套好用的排查思路

排查前后台分离部署问题,我习惯按照“链路排查法”从浏览器一步步往后端推进。第一步看浏览器 Network 面板,确认页面资源和接口请求的状态码。如果 HTML 都能加载、CSS/JS 200,但页面空白,优先怀疑 JS 运行时错误,看 Console 面板。如果接口 404,说明 Nginx 代理路径或后端接口路径不匹配;如果 502,说明 Nginx 无法连接到后端服务;如果 504,说明后端服务响应超时。确定问题出在 Nginx 或后端之后,直接上服务器 curl 验证。

比如后端接口 502,我先执行curl -v http://127.0.0.1:8080/api/test看后端是否正常返回。如果不通,看后端日志;如果通,再看 Nginx 转发配置。这套方法看起来基础,但在紧张的上线时间窗里,能最快把问题定位到具体环节。

还有个很容易被忽略的地方:前后台分离项目部署后,前端请求的接口必须与 Nginx 反向代理规则一一对应。如果前后端约定接口前缀是/prod-api,但前端代码里的 baseURL 写的是/api,即使 Nginx 只代理了/prod-api,请求也会全部 404。前端接口前缀、后端 context-path、Nginx location 三者必须构成同一条链路,这是部署前的前置检查项。

7.3 部署时环境变量与敏感信息的管理

这一节专门聊聊部署里的环境变量与敏感信息管理。很多人在生产环境踩的坑,都源于配置写死。比如数据库密码写死在application.yml里,项目开源或代码仓库被多人访问时,密码直接泄露。比较实用的做法是:本地开发用本地配置,生产环境通过环境变量注入敏感信息。Spring Boot 原生支持环境变量覆盖配置项,比如SPRING_DATASOURCE_PASSWORD这种环境变量名会自动映射到spring.datasource.password配置项。

Docker Compose 部署时,可以使用.env文件配合${VARIABLE}语法:

environment: SPRING_DATASOURCE_URL: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8 SPRING_DATASOURCE_USERNAME: ${DB_USER} SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}

.env文件不要提交到版本控制,并在服务器上严格控制权限。如果项目对安全的敏感度高,也可以集成更专业的机密管理工具,但对绝大多数前后台分离项目而言,环境变量方案已经够用且容易落地。

再补充一个线上环境常见的细节:后端服务健康检查。Spring Boot 项目可以引入 Actuator,暴露/actuator/health端点,配合 docker-compose 的 healthcheck 配置。这样编排工具可以感知到后端是否就绪,避免前端服务已经起来了,后端还在启动中,导致依赖它的请求全部失败。前端容器也没有必要因为后端还没有就绪就反复重启,配好 depends_on 的条件即可按需等待。

7.4 常用的网络连通性排查工具

服务器上排查问题时,curl 和 telnet 这两个工具要熟练。curl可以测试接口是否通、返回什么状态码、响应头是否正确。telnet则用来检测端口连通性,比如telnet 192.168.1.10 3306检查 MySQL 端口是否可达。如果端口不通,检查防火墙、安全组规则、容器端口映射。

还有一个很实用的小技巧:Nginx 配置修改后,一定先执行nginx -t验证语法,再执行nginx -s reload热加载。千万不要直接重启 Nginx,否则配置里有语法错误,服务直接挂掉,影响所有正在运行的站点。热加载相比重启的优势在于平滑,不会中断现有连接。

8. 前后台分离部署的一条龙实战复盘

看完前面的分环节拆解,我再用一个完整小项目的流程做个串联复盘,这样大家对整体节奏会更有体感。假设项目是 Vue 3 + Spring Boot 2.7,代码托管在 Git 仓库,服务器是 Linux,域名暂用 IP。

第一步,在本地把前后端代码分别 build 一遍。前端执行npm run build,确认 dist 目录生成;后端执行mvn clean package -DskipTests,确认 jar 包生成。这一步能提前暴露绝大多数构建问题。

第二步,把前端 dist 目录上传到服务器指定目录,比如/opt/www/frontend/dist。后端 jar 包上传到/opt/app/backend。同时把生产环境外置配置文件也放好,比如/opt/app/backend/config/application-prod.yml。

第三步,配置 Nginx。把前面提到的静态托管、History 回退、/api反代三块配置写到/etc/nginx/conf.d/default.conf。配置好后执行nginx -t验证,然后nginx -s reload。

第四步,启动后端。可以用 nohup 直接启动,也可以用 systemd 管理,生产环境建议 systemd。命令大概是这样:

cd /opt/app/backend nohup java -jar -Dspring.profiles.active=prod demo.jar > app.log 2>&1 &

启动后curl http://127.0.0.1:8080/api/health确认接口正常。

第五步,打开浏览器访问服务器公网 IP,如果一切正常,页面能打开、接口能通,部署即完成。如果异常,按前面第七节的链路排查法逐步定位。

整个过程如果熟练,半小时内就能完成一次手动部署。但这里面有一个漫长的沟:第一次部署时,前端 publicPath、Nginx 代理路径、后端外置配置这三者可能来回调好几次才能对上。所以我的建议是,先在测试环境完整走一遍这套流程,记录每一步的配置项,形成部署文档。以后每次发版,照着文档操作,效率和稳定性都会好很多。

9. 关于部署方式的几条真实建议

最后聊点我个人的体会。前后台分离项目的部署,方式没有绝对标准,关键看团队规模和发布频率。如果你是自己维护一个小项目,手动打包 + Nginx + nohup 完全够用,不需要为了“上 Docker”而上 Docker。但一旦有两个人以上协作、或者需要频繁发版,容器化几乎是必须的,因为它能保证“本地能跑,服务器就能跑”。

热词里有一句“无法将此项目用于本地聊天”,这让我联想到另一个容易踩的坑:很多看似能把项目快速跑起来的工具或框架,在本地 Demo 阶段表现完美,但要做成真正可部署、可交付的项目时,需要补齐的工程化细节远比想象多。前后台分离项目的部署也是这样:开发环境顺风顺水,真正落到打包部署这步,才是检验项目完整度的时刻。

再说说 CI/CD。前面讲的所有步骤,其实都可以自动化。前端提交代码后自动 build、自动上传服务器,后端提交后自动打包镜像、推送镜像仓库,服务器端通过 webhook 拉取最新版本再重启。这套流程做下来,发版从“手忙脚乱”变成“一键完成”。即使暂时不打算上完整的 CI/CD,我也建议把部署脚本写在仓库里,比如deploy.sh,把构建、上传、重启这些命令固化下来。这样新人接手也能照着跑,降低部署环节的知识门槛。

前后台分离打包部署这件事,本质上没有高深技术,但细节密度非常高。配置项多、链路长、层级多,任何一个环节的疏漏都可能让整个服务跑不起来。但只要把架构理清楚、把每一步的配置逻辑搞明白、把常见坑提前规避掉,这就是一项稳定可靠的日常操作。希望这篇文章能帮你在部署这条路上少踩几个坑,把宝贵的时间留给真正需要解决的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询