Spring Boot + Vue前后端分离项目从本地联调到服务器部署全指南
发布时间:2026/9/19 16:16:45 作者:尧图编辑部 阅读量:1,286

1. 本地跑通前后端项目两个端口和一个代理的联调基本功我一直觉得前后端分离项目最折腾人的不是写代码而是把它从“我电脑上能跑”变成“别人也能访问”。本地上有Vite热更新后端有Spring Boot一键启动一切都很美好真到部署那天你会发现能跑的代码在服务器上不一定能跑能跑的程序公网不一定能访问。这篇文章就用一个典型的Spring Boot Vue前后端分离项目打样把从本地联调、打包构建、服务器部署到公网访问的整条链路拆开讲一遍。适合刚接触部署的新手也适合已经部署过一两次但总在某些环节卡壳的人。1.1 本地开发环境的“最小可用配置”前后端分离项目在本地跑通看起来是小事其实是后面所有部署动作的基准线。你连本地都跑不通服务器上更别想一次成功。先说后端。Spring Boot项目本地启动基本没什么坑JDK版本对上、Maven依赖拉下来mvn spring-boot:run或者IDE里直接跑main方法就行。但有一类项目需要注意——如果你拿的是若依、mall这类开源脚手架启动前务必检查application-druid.yml里的数据库连接以及Redis地址。很多人启动报错不是代码问题是本地MySQL没起、Redis没起或者密码不对。这类项目往往还依赖一个初始化SQL脚本你导入数据库了吗没导的话启动时表不存在照样白屏。再说前端。Vue项目clone下来之后npm install是第一步。这里有个小建议别直接npm install先看package.json里用的包管理器是npm还是yarn还是pnpm。若依老版本用npm新版本用pnpm混用容易产生锁文件冲突。装完依赖后npm run dev默认端口一般会落在5173或80。注意看控制台输出的实际地址因为如果80被占用Vite会往后顺延一个端口这时候你访问的就不是你以为的那个地址。前后端都启动后验证联调是否正常的标准动作是打开浏览器F12切到Network找一个登录接口或者列表接口看请求是否成功返回JSON。如果返回401、404、500按下面的顺序查后端控制台有没有打印异常、接口路径是否匹配、有没有走代理转发。1.2 联调阶段最容易翻车的“跨域”问题前后端分离之后前端跑在5173端口后端跑在8080端口两个端口不同浏览器就会拦截前端的AJAX请求——这就是跨域。解决跨域有三种主流姿势我建议按优先级排序前端开代理最常用在Vite或webpack的配置里加一个proxy把/api开头的请求转发到http://localhost:8080。这样浏览器看到的请求是同源的根本不触发跨域。后端开启CORSSpring Boot里写一个WebMvcConfigurer配置允许跨域的域名、请求头和请求方法。适合前端无法改代理的场景比如别人在局域网里直接访问你的前端地址。浏览器关跨域校验仅限临时调试千万别在正经项目里干这事。实际使用中我推荐前端代理方案因为它的转发逻辑跟生产环境的Nginx反向代理天然一致。你在本地配的/api - 8080到了生产环境配的就是/api - 服务器后端端口一个思路用到底不需要来回切换思维。Vite的配置长这样// vite.config.js server: { host: 0.0.0.0, port: 80, proxy: { /prod-api: { target: http://localhost:8080, changeOrigin: true } } }注意这里有个细节changeOrigin一定要设成true。不设的话后端可能会拿到前端的Host头某些框架对Host头有校验比如Spring Security在某些配置下会直接拒绝请求。这个坑我见过不止一次。2. 打包构建阶段最容易被忽略的三件事环境配置、产物目录与资源路径本地跑通只是万里长征第一步。接下来要做的是把前后端源码变成可以在服务器上执行的产物。这一步看似简单实际是整个部署过程中翻车率最高的区域之一。我总结了三件最容易忽略的事。2.1 后端Maven打包前必须确认的配置项Spring Boot项目的后端打包命令很简单mvn clean package -DskipTests命令行敲下去等一两分钟target目录下就会出现一个xxx.jar。但很多人的问题恰恰出在“太简单”上——包打出来一放到服务器上就起不来或者起来了但接口报错。先检查打包用了哪套配置。Spring Boot项目一般有application.yml、application-dev.yml、application-prod.yml这种按环境拆分的配置。启动时通过--spring.profiles.activeprod来指定。很多人开发时一直用dev配置数据库连的是本地Redis连的是本地打包时也没改传到服务器上之后代码起来了但连不上数据库查半天才发现配置不对。所以打包前你要确认三样东西生产数据库地址、Redis地址、文件上传路径。这三样至少先按服务器实际情况改好或者用环境变量占位。比如在application-prod.yml里写spring: datasource: url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:3306/yourdb?useUnicodetruecharacterEncodingutf8 username: ${MYSQL_USER:root} password: ${MYSQL_PASSWORD:root}这样在服务器上启动时通过环境变量注入就不用每次重新打包了。还有一个容易被忽略的点mvn clean package时会执行测试类如果某个测试用例失败打包会中断。加上-DskipTests跳过测试没问题但要提醒自己这不是让你永远不跑测试只是部署场景下的取舍。顺带说一下IDEA部署项目时添加Deployment没有Artifact这个老问题。如果你还在用SSM那套、需要打war包扔进外部Tomcat的老古董方案IDEA里找不到Artifact多半是因为你的项目没有创建Web Facet或者没有执行构建。现在的Spring Boot项目直接打jar包、用内嵌Tomcat根本没这个烦恼。如果你确实要打war包请在pom.xml里把packaging改成war然后在启动类上继承SpringBootServletInitializer并重写configure方法。2.2 前端构建产物的路径陷阱前端打包命令通常是npm run build产物在dist目录下里面有index.html、static目录、各种js/css文件。看起来一切正常但传到服务器上用Nginx一配完蛋了——首页能打开但点登录按钮之后接口全部404或者刷新页面变成404。这两个问题的根子都在“路径”上。第一个问题接口404。打开dist/js下某个js文件搜索http://如果发现代码里写死了http://localhost:8080/api那基本可以断定你忘记改环境变量了。前端项目里一般有个.env.development和.env.production文件生产构建时要用/prod-api这种相对路径让请求走Nginx的代理而不是写死IP。一旦写死了localhost浏览器就是拿用户自己的电脑去访问8080端口能通才怪。第二个问题刷新404。这是因为你的前端路由用了history模式刷新页面时浏览器会向服务器请求当前路径比如http://你的域名/sys/user但服务器上根本没有/sys/user这个物理文件Nginx就返回404了。解决办法是让Nginx把所有非静态文件的请求都重写到index.html由前端路由自己接管。这个配置我会在下一章给出。2.3 前端构建还有一个不起眼的坑publicPath与资源加载如果你的项目部署在域名根路径下publicPath用默认的/就行。但如果你想让前端和后端同时跑在一个IP的不同端口或者放在Nginx的某个子路径下比如http://IP/xxx/那就必须设置publicPath为对应子路径否则js/css全部加载不出来因为浏览器会去根路径找资源。这事我在部署多个Web项目到同一台Nginx时吃过亏。两个前端项目一个放根路径一个放在/mall路径下第二个项目的publicPath如果不设为/mall/页面就是纯白。Vite里改base配置Vue CLI里改publicPath。3. 服务器部署布局Java进程负责APINginx负责静态资源与反向代理本地跑通了包也打好了接下来就是真正的部署环节。一台干净的云服务器拿到手需要装JDK、装Nginx、传文件、起服务、配代理。这一章节我把整条链路走一遍。3.1 后端Jar包的常规启动姿势与日志处理假设你有一台Linux服务器IP是1.2.3.4从你的云服务商控制台可以查到公网IP。先把jar包传到服务器上传送方式很多scp命令、宝塔面板、rz命令都行。我个人习惯用scp一条命令搞定scp target/your-app.jar root1.2.3.4:/opt/app/然后在服务器上确认Java环境。Spring Boot项目建议用JDK 8或11具体版本看你的pom.xml里java.version。java -version看一下没有就装有但版本不对也得调整。启动Java进程的标准命令是nohup java -jar /opt/app/your-app.jar --spring.profiles.activeprod /opt/app/logs/app.log 21 这个命令的意思是后台启动jar包使用prod环境配置日志输出到/opt/app/logs/app.log。加nohup是为了防止你关闭SSH终端时进程被一起带走。是让程序在后台运行。21是把错误输出也重定向到同一个日志文件这样排查问题只看一个文件就行。等几秒之后怎么确认启动成功tail -f /opt/app/logs/app.log看日志看到Started Application in xx seconds就是成功了。这时候你可以先在本机服务器上验证一下接口是否通curl http://127.0.0.1:8080/api/test如果curl有返回说明后端程序本身没问题。如果卡住或者返回空先别急着查外部90%的情况是数据库连不上、Redis连不上、或者端口被防火墙挡了。这里要提一个关于日志的细节。Spring Boot默认用Logback打日志如果你不额外配置日志文件会无限增长或者按天滚动但保留所有历史。热搜词里有一条“Spring Boot项目部署后日志只保留三个月”就是典型的运维需求。在logback-spring.xml里配maxHistory就行rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern/opt/app/logs/app.%d{yyyy-MM-dd}.log/fileNamePattern maxHistory90/maxHistory /rollingPolicy配上之后日志自动保留90天超过的自动删除磁盘不会爆。3.2 Nginx配置前端静态目录与后端接口代理的完整模板后端起来了接下来处理前端。把dist目录传到服务器上比如放到/opt/nginx/html/mall然后编辑Nginx的配置文件。下面是我经常用的一套模板直接抄就能用server { listen 80; server_name your-domain.com; # 前端静态资源 location / { root /opt/nginx/html/mall; index index.html; try_files $uri $uri/ /index.html; # 解决history路由刷新404 } # 后端API反向代理 location /prod-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; } # 静态资源缓存可选 location /static/ { root /opt/nginx/html/mall; expires 7d; } }配置完执行nginx -t检查语法然后nginx -s reload让配置生效。这里有两个点值得单独拿出来讲。第一try_files $uri $uri/ /index.html;这行是解决history模式路由刷新404的关键。它的含义是如果请求的URI找不到对应文件就返回index.html让前端路由去解析。没有这行你访问/sys/user刷新就404。第二proxy_pass http://127.0.0.1:8080/;最后这个斜杠有讲究。如果proxy_pass带了路径比如http://127.0.0.1:8080/那么Nginx会把location匹配到的部分替换掉。如果请求/prod-api/login实际转发到后端是/login因为/prod-api被吃掉了。如果你的后端接口本身也有/prod-api前缀比如后端Controller的RequestMapping里带着这段那proxy_pass就不能带斜杠写成http://127.0.0.1:8080这样请求会原样转给后端。这一正一反错一个就404。3.3 若依这类脚手架项目的特殊部署姿势拿若依前后端分离框架举例。若依前端的请求路径默认带/prod-api后端接口的上下文是/prod-api。按上面那套配置Nginx的location /prod-api/和proxy_pass http://127.0.0.1:8080/正好匹配——前端发的/prod-api/loginNginx去掉了/prod-api转给后端/login后端返回数据前端完美拿到。这是若依官方推荐的部署方式。但是热词里还有一条“若依前后端分离框架部署”很多人用的是另外一套方式前端和后端分开跑在服务器上前端直接改nginx配置把/prod-api指到服务器公网IP:8080。这种做法也能通但有个前提——后端必须开启CORS否则跨域问题会让浏览器把请求拦截掉。我的建议是统一走Nginx反向代理让浏览器始终只访问同一个域名不要在dist里写死后端地址也不要在前端代码里配http://服务器IP:8080。一个入口解决所有问题出问题也只需查一层代理。3.4 Tomcat部署Web项目的遗留场景war包与外置Tomcat现在主流是Spring Boot内嵌Tomcat但还是有不少老项目、课程项目走的是“外部Tomcat部署war包”的路线热搜词里“tomcat部署web项目”就是这个场景。如果你拿到的是war包部署方式是丢到Tomcat的webapps目录下启动Tomcat后自动解压。JDK版本要和Tomcat兼容Tomcat 8.5以上建议JDK8。部署后访问路径要带项目名比如http://IP:8080/your-project-name/如果想去掉项目名可以把war包改名为ROOT.war它会自动落到根路径。IDEA里跑Tomcat部署时出现的“添加Deployment没有Artifact”多半是你没有先执行一次构建或者Web Facet没配置好。先Build一下项目确保target下有war包再打开Deployment面板Artifact才会出现。4. 从IP到域名的公网访问链路解析、端口、安全组和备案缺一不可现在你的后端Java进程在服务器上跑着Nginx也配好了理论上浏览器已经可以通过http://服务器公网IP访问到你的前端页面。但如果实际测试发现打不开大概率是卡在下面这一条链路上公网IP、域名解析、安全组放行、服务器内部防火墙。4.1 服务器有公网IP但外网访问不了先查三层我在排查“知道了服务器的公网怎么访问”这类问题时永远按三层来查第一层云服务商控制台的安全组。这是最容易被忽略的。大多数云厂商默认只在安全组里放行了22端口SSH而80、443端口没有对外开放。你在服务器上用curl http://127.0.0.1:80能通但外网就是访问不了原因就在这。需要登录云服务商控制台找到你的实例进入安全组规则添加放行规则协议TCP端口80来源0.0.0.0/0。443要不要放取决于你是否配了HTTPS证书。第二层服务器内部防火墙。检查firewalld或iptables的状态。CentOS系统常见的是firewalldfirewall-cmd --state firewall-cmd --permanent --add-port80/tcp firewall-cmd --reload如果装了宝塔面板宝塔的安全入口也要放行端口不然端口在面板里被拦截照样访问不了。第三层Nginx有没有监听正确端口。ss -lntp看一下确认Nginx确实在监听80端口。有时候Nginx没启动或者启动失败配置语法错误外网自然访问不了。这三层检查完http://公网IP应该能打开你的前端页面了。4.2 域名绑定后面的几个细节用IP访问没问题接下来很多人会去注册一个域名然后做A记录解析把域名指向服务器IP。域名解析的操作很直观——在云厂商的DNS控制台添加一条A记录主机记录填www或者记录值填你的服务器公网IP。但域名解析生效需要一点时间而且不同网络环境生效速度不一样。本地刷新一下DNS缓存ipconfig/flushdns然后用ping或者在线工具解析一下域名确认指向是否已经生效。如果域名解析配置了但一直不生效看看服务器备案状态。按照国内平台的合规要求域名如果绑定到大陆区域的服务器并通过80/443端口提供Web服务是需要在接入服务商的平台完成备案流程的否则域名会被阻断访问。如果你不想等这个流程临时绕过的方式是用IP直连或者把服务部署在非80端口比如8080、8888但这只适合测试环境正经上线还是得走完合规流程。这一点务必重视很多人域名解析了一天都访问不了最后发现是备案没完成。4.3 HTTPS从“能用”到“好用”的升级HTTP能访问之后我强烈建议顺手把HTTPS配上。原因很实际如果你的项目涉及登录、支付这类敏感操作HTTP明文传输等于把用户的账号密码裸奔在网络上而且现在主流浏览器对HTTP站点会有“不安全”提示非常掉档次。配HTTPS的流程是获取SSL证书有免费的一年期DV证书然后把证书文件放到服务器上修改Nginx配置server { listen 443 ssl; server_name your-domain.com; ssl_certificate /usr/local/nginx/cert/your-domain.pem; ssl_certificate_key /usr/local/nginx/cert/your-domain.key; # 与HTTP配置相同的 location 规则 location / { root /opt/nginx/html/mall; index index.html; try_files $uri $uri/ /index.html; } location /prod-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; } }配好后最好把HTTP的80端口也保留做一个301跳转到HTTPS避免用户访问旧地址时404server { listen 80; server_name your-domain.com; return 301 https://$host$request_uri; }我经历过一个线上事故前端用了HTTPS后端接口也走了Nginx代理但因为后端代码里获取请求协议时用错了方式导致某些回调地址还是HTTP登录回调时无限循环。后来统一在Nginx层加了X-Forwarded-Proto头并在Spring Boot里配置server.forward-headers-strategy问题才解决。5. 手动部署之外的更好选择用Docker把部署变成流水线手动传到服务器上、nohup起Java进程、手动改Nginx配置这套流程跑通一次之后你会开始觉得烦——每次发版都要重复一遍还容易漏步骤。这时候就该上Docker了。Docker的价值不仅在于部署更在于让“本地能跑”和“服务器能跑”之间的差距趋近于零。5.1 后端容器化从Jar包到镜像一个Spring Boot项目的Dockerfile非常简洁FROM openjdk:8-jre-alpine WORKDIR /app COPY your-app.jar /app/app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, /app/app.jar, --spring.profiles.activeprod]然后构建镜像、启动容器docker build -t your-app:1.0 . docker run -d --name your-app -p 8080:8080 \ -e MYSQL_HOST127.0.0.1 \ -e MYSQL_PASSWORDyourpass \ -v /opt/app/logs:/app/logs \ your-app:1.0这里用-e传入环境变量对应我们在配置文件里留的占位符。-v把容器内的日志目录挂载到宿主机不然日志写在容器里容器一删就没了。用Docker跑项目有个隐性好处宿主机上不用装JDK了。以前换一台服务器要装JDK、配环境变量环境不对项目起不来现在镜像里自带JDK到哪都能跑。这点对很多被“JDK版本不一致”坑过的人来说简直是救星。5.2 前端容器化用Nginx镜像跑静态资源前端的Dockerfile也简单基于Nginx镜像把构建好的dist目录拷贝进去FROM nginx:alpine COPY dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80启动docker run -d --name web -p 80:80 your-web:1.0注意前端容器需要和后端容器通信最简单的做法是用docker-compose编排把前端、后端、数据库、Redis一起管起来。之前有一段时间流行“Docker部署若依项目”本质就是写一个docker-compose.yml把若依的后端、前端UI、MySQL、Redis全部定义好一条docker-compose up -d全部拉起来。这也是目前最推荐的方式复现成本极低。5.3 Docker部署需要留意的日志与数据卷问题用Docker部署后日志和数据的持久化是必踩的坑。容器本身是无状态的一删重建里面的数据就没了。数据库容器一定得挂数据卷否则跑了几天的数据一次docker-compose down直接清空哭都来不及。日志同理尽量挂到宿主机目录方便用tail、grep排查问题。此外如果直接用--restartalways让容器自动重启要确认你的应用启动逻辑是否幂等——启动时是否重复执行初始化操作、是否依赖某个前置服务完全就绪。我见过一个项目后端容器和数据库容器同时启动后端启动脚本里做了建表操作结果每次重启都报“数据库还没准备好”最后加了depends_on和健康检查才稳定。6. 部署翻车之后我总结出的排查清单最后一章说说踩坑。设计到这里的你大概率已经遇到了某种“奇怪的问题”——本地一切正常服务器上五花八门。我在多次部署中总结了一套排查顺序每一步都对应一类高频故障拿来即用。第一步先分前后端。浏览器打开页面F12看Network。如果HTML都能加载只是接口报错那问题在后端链路如果页面都打不开那问题在Nginx或静态资源。第二步后端链路用curl自测。在服务器本机执行curl http://127.0.0.1:8080/接口地址如果通了说明后端程序没问题再检查Nginx代理配置。如果本机都不通去看Java日志的堆栈信息多半是数据库、Redis或者配置问题。第三步外部访问不通时按“安全组→防火墙→端口监听”顺序排。安全组在云服务商控制台看防火墙在服务器上systemctl status firewalld端口监听用ss -lntp。第四步排查URL路径。确认前端发出的请求路径和Nginx匹配的location是否一致确认proxy_pass带不带斜杠这和后端接口实际路径是否匹配。路径问题占了部署问题的一半以上。第五步查配置项差异。把本地和生产环境的所有配置项列出来逐一对比数据库地址、Redis地址、文件上传路径、日志级别、域名配置。其中任何一个不一致都可能导致不可预期的问题。第六步实在查不出就抓包。在服务器上tcpdump或者用curl -v看请求和响应详情。很多时候你以为“Nginx转发没问题”实际上HTTP头和请求体早就不对了。我自己养成的一个习惯是每部署一次就把这轮踩的坑记录到一个部署文档里。第二次部署相同类型的项目时先翻一遍文档10分钟之内就能把大部分坑规避掉。这个文档不光是给自己看的团队新同事接手部署任务时直接给文档比自己踩一遍效率高得多。前后端项目部署这件事本质上就是在“本地环境”和“生产环境”之间搭一座桥。你不需要背下所有配置只需要搞清楚每一层组件负责什么、请求是怎么一层层被转发的。把这条主线捋顺换成任何技术栈、任何项目你都能快速上手。最后分享一个小技巧服务器上配置完Nginx永远先跑一遍nginx -t再reload这个习惯能帮你省掉一半的“改完配置Nginx挂了”的麻烦。