资讯详情

资讯详情

SpringBoot项目部署实战:从Jar包到宝塔面板+Nginx全流程

部署这事儿说简单也简单说复杂也复杂。简单到一句java -jar app.jar就能起服务复杂到能让你折腾一晚上还找不到问题在哪。很多刚接触 SpringBoot 的同学都有个直觉本地 IDE 里跑得好好的点击运行就完事儿为什么部署到服务器上就这么费劲宝塔面板又是干什么的我不用行不行这篇文章就是想一次性把这事说透。我尽量用大白话拆解 SpringBoot 项目部署背后的原理再结合宝塔面板讲一遍完整的实操流程让你看完既能明白“部署到底在干什么”也能直接跟着做把项目跑起来。1. 部署的本质本地运行和服务器运行的差别在哪先说一个扎心的事实你本地能跑不代表服务器上能跑。这不是玄学是两套环境之间的差异造成的。部署的本质其实就是消除这种差异。1.1 开发环境和服务器环境到底差在哪你本机上的开发环境通常包括代码编辑器、Maven 仓库、JDK、MySQL 客户端、各种本地安装的中间件。IDE比如某款常见的 Java IDE帮你做了很多事自动编译、自动识别依赖、内置的运行时配置、甚至帮你搞定了类路径 Classpath。你在 IDE 里点一下运行背后是 IDE 把项目编译成了 target 目录下的 class 文件然后启动一个 JVM 进程加载这些 class。但服务器上没有 IDE。服务器上只有一个干净的操作系统什么 Java 环境、MySQL、Nginx统统都没有。你需要把项目变成一个可独立运行的东西传上去再把这个东西交给服务器上的 Java 运行时去启动。具体差异集中在四块依赖差异本地可能下载了各种依赖包但服务器上没有 Maven 仓库它不会自动帮你下载。配置差异你在本地连的数据库可能是localhost:3306密码是123456服务器上的数据库可能是另一套密码、另一个端口。运行方式差异本地用 IDE 跑服务器要用命令跑而且服务器上跑的程序不能依赖你开着电脑、开着终端进程。资源差异本地可能 16G 内存随便造服务器可能只有 2GJava 启动参数没调好直接内存溢出。提示部署不是简单把代码“搬”过去而是把“可运行制品 运行环境 配置”这三样东西组合起来在服务器上复现出一个能稳定对外提供服务的进程。1.2 SpringBoot 的可执行 Jar 包是怎么工作的SpringBoot 项目执行mvn package之后会在target目录下生成一个 jar 包这个 jar 包和普通 jar 包不一样它叫可执行 jarfat jar。名字叫 fat是因为它把项目编译出来的 class、resources 里所有配置、以及所有第三方依赖的 class 文件全部打进了这一个 jar 里。你甚至可以打开这个 jar 看看里面有个BOOT-INF/lib目录里面躺着你项目的所有依赖 jar。还有一个BOOT-INF/classes目录里面是你自己的代码。org/springframework/boot/loader里是 SpringBoot 的启动器它负责特殊类加载逻辑——把嵌套在 fat jar 里的依赖 jar 加载到 JVM 里。所以它能做到java -jar app.jar直接启动本质上是 SpringBoot 自己做了一个类加载器绕过 JDK 默认的 jar 嵌套限制。这也是 SpringBoot 部署最简单的核心原因——以前 SSM 项目要打包成 war 丢进 Tomcat 的 webapps 目录现在内嵌 Tomcat一个 jar 全搞定。1.3 部署路线的演化部署方式这几年变化很大理解这条演化路线你就知道为什么现在推荐用宝塔或者容器化方式了。原始时代大家把 war 包扔到 Tomcat webapps 下手动启动 Tomcat改代码就得重启。再后来有了 Jenkins配合 Git 钩子自动拉代码、自动打包、自动重启。再后来 Docker 出现整个应用连同 JDK 环境一起封装成镜像一举解决环境不一致问题。再到 Kubernetes 做编排扩缩容成了家常便饭。对于个人项目、小公司内部系统、外包项目、毕业设计这套容器化链路太重了。一台云服务器加宝塔面板反而是性价比最高的路。宝塔把环境安装、站点配置、数据库管理、进程守护这些常见操作都可视化让一个后端开发同学能在一小时内搞定整个上线流程。2. 打包把代码变成能跑的 Jar 包部署的第一步不是买服务器而是确保你能在本地打出一个干净、可用的 jar 包。很多人上来就是mvn package打完传上去发现启动报错再回头看发现连打包都打错了。2.1 Maven 打包的几个关键配置SpringBoot 项目打开pom.xml有几个东西值得检查一下。packaging标签确定打包方式SpringBoot 一般用jarpackagingjar/packagingspring-boot-maven-plugin是核心它负责在打包时把项目依赖全部打进去并生成启动信息。没有这个插件打出来的 jar 会缺很多东西build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build还有一个很隐蔽的问题如果你用了SpringBootApplication这个入口类官方推荐把主类放在某个包路径下确保它能被自动扫描到。如果你把主类放错了位置打包能过但启动必报Unable to find main class。2.2 打包时忽略测试和清理旧产物很多项目的测试用例不是拿来跑服务器用的打包时如果执意跑测试往往因为测试环境连不上数据库直接失败。所以打包前有两个建议mvn clean package -DskipTestsclean是把之前的 target 删掉避免旧 class 残留导致莫名其妙的问题。-DskipTests是跳过测试代码的编译执行。注意这里省略了运行测试但测试代码还是会编译。如果连测试代码编译都不想碰有些测试代码写得不规范编译都过不了用mvn clean package -Dmaven.test.skiptrue两个命令的区别在于前者跳过“执行”测试后者跳过“编译执行”测试。实战中我用第二种更多省事。2.3 配置文件外部化打包之前还有一件事要想清楚配置怎么管理。比如数据库密码、第三方密钥这类敏感信息如果直接写在application.yml里打进 jar 包等于把密码拱手送人。而且不同环境开发、测试、生产配置还不一样。推荐的做法是打包时只留一份通用配置生产环境的配置放到 jar 包外面用 SpringBoot 的外部化配置机制覆盖内部配置。启动时指定外置配置java -jar app.jar --spring.config.additional-location/www/wwwroot/app/config/application.yml这样 jar 包是同一个不同环境通过外挂配置文件切换上线换配置不用重新打包。这是部署中非常实用的一个小技巧。3. 服务器环境准备宝塔面板帮你省掉哪些事服务器到手时是裸的需要装各种软件环境。装环境这个事恰恰是新手最容易绝望的地方——每装一个软件都要对着教程一步步操作还会遇到各种依赖冲突、版本兼容问题。宝塔面板的价值就在这里把这些事全部图形化。3.1 安装宝塔面板宝塔面板是一个服务器管理面板官方提供一键安装脚本。对新手来说只要两步拿到服务器、执行安装命令。安装完成后面板会输出一个带端口的管理地址和管理账号密码浏览器访问就能进入图形化管理界面。进入面板后第一个要做的事是安装环境套件。在面板首页的“软件商店”里可以一键安装 Nginx、MySQL、PHP、phpMyAdmin 等。对 SpringBoot 项目来说一般需要的是 Nginx 和 MySQLJDK 不一定有现成一键包如果软件商店里有 Java 相关环境工具可以用没有的话手动装也不难。3.2 JDK 安装的两种方式SpringBoot 3.x 要求 JDK 17 及以上2.x 用 JDK 8 就能跑。服务器上装 JDK 有两种主流方式。第一种是命令行直接装 OpenJDKsudo apt update sudo apt install openjdk-17-jdk -yCentOS 系列类似只是包管理器换成了 yum包名可能是java-17-openjdk。装完可以用java -version验证。第二种是手动解压 JDK 压缩包配置环境变量。这种方式的好处是版本可控可以同时装多个 JDK用软链接切换版本。装完后注意一件事确认java -version显示的版本和你项目要求的一致。很多人的坑就出在这里——项目是 JDK 17 写的服务器默认装了 JDK 8启动直接UnsupportedClassVersionError。3.3 MySQL 初始化与数据库创建宝塔装完 MySQL 后初始密码一般写在面板首页的数据库信息区域。首次登录建议手动改掉密码并且只允许本地访问生产环境的数据库千万不要开外网访问——除非你想被扫库勒索。然后在宝塔的“数据库”页面新建一个数据库记下数据库名、用户名、密码。这些信息后面配application.yml时要一一对应。注意宝塔创建的数据库用户密码如果包含#、、$这类特殊字符写在 yml 配置里记得加双引号或单引号不然后面解析配置时会出诡异问题。4. 首次部署全流程实录从上传 Jar 到成功访问环境准备完毕进入实战环节。这里我用一个模拟项目 X 走一遍完整流程目标是让 jar 包在服务器上跑起来并通过 IP 加端口访问到页面。4.1 准备项目产物和依赖文件本地执行mvn clean package -Dmaven.test.skiptrue成功后target目录下会生成一个X.jar。我习惯把它改名成更简洁的名字比如app.jar方便后面写脚本。同时准备好外部的application-prod.yml里面配置了生产环境的数据源、端口等。注意数据库的数据库名和配置对得上。常见错误是配置里写jdbc:mysql://localhost:3306/app_db但实际建的数据库叫别的名字启动时就会报Unknown database。4.2 上传 jar 包到服务器宝塔的文件管理器可以直接拖拽上传。我一般会在根目录建一个专门的部署目录比如/www/wwwroot/app把所有跟这个项目有关的东西都放里面jar 包、配置文件、启动脚本、日志文件。目录结构清爽后面找东西不迷路。4.3 手动启动与测试进入目录直接启动cd /www/wwwroot/app java -jar app.jar --spring.profiles.activeprod注意这个--spring.profiles.activeprod是把 Spring 的 profile 切换成 prodSpringBoot 会自动读取application-prod.yml。如果配置文件名不叫这个就得用--spring.config.additional-location指向外部配置。第一次启动建议在前台跑这样能直接看日志输出方便发现问题。看到类似Started Application in 8.32 seconds的日志就说明启动成功了。随后可以开个新终端窗口验证curl http://127.0.0.1:8080/api/health能返回结果说明应用本身没问题。但注意这个 8080 端口如果你在云服务器控制台的安全组没有放行外部访问会直接超时。4.4 用 nohup 让应用在后台常驻上面那种直接启动的方式有个致命问题一旦终端断开进程就会被杀死服务就挂了。真正的部署必须让进程脱离终端会话独立运行。标准做法是 nohupnohup java -jar app.jar --spring.profiles.activeprod app.log 21 拆开看这个命令nohup表示忽略挂断信号即使终端关闭也不退出。 app.log把标准输出重定向到日志文件。21把错误输出也重定向到同一日志文件。最后的让命令在后台执行。启动后立即查看日志tail -f app.log看到成功启动的标志后再检查进程ps -ef | grep java提示进程管理千万别用java -jar app.jar 这种简陋方式因为一旦父进程挂了子进程很容易变孤儿进程服务没人管。4.5 停止与重启的正确姿势停止 SpringBoot 应用不能直接 kill -9 了事那样容易丢失未写入的数据。先找 PIDps -ef | grep app.jar | grep -v grep然后优雅停止kill -15 PIDSpringBoot 内置的优雅停机机制会处理剩余请求再释放资源。如果等了几秒还没退再考虑kill -9强杀。重启就是停掉旧进程再执行 nohup 启动命令。这一步经常被打包进脚本里下面第 6 节会专门讲。4.6 云服务器安全组端口放行这里有个极易被忽略的坑。很多云服务商在服务器之外还有一层安全组或防火墙策略不管你服务器内部怎么开放端口安全组没放行就是连不上。以常见的云控制台为例需要去“安全组”或“防火墙”里添加入站规则放行 TCP 8080 端口。宝塔面板安全页面也要确认该端口已放行。服务器内部如果开了 firewalld 或 ufw还得单独放行sudo firewall-cmd --zonepublic --add-port8080/tcp --permanent sudo firewall-cmd --reload或者sudo ufw allow 8080/tcp这么多层漏一层就连不上。排查端口不通问题时我一般按这个顺序进程是否在监听ss -lntp宝塔是否放行服务器防火墙是否放行云安全组是否放行。5. 用 Nginx 做反向代理为什么要这么做直接用http://服务器IP:8080访问其实已经能用了但实际部署基本都会再装一层 Nginx 反向代理。这一步既不是多余也不是装样子它解决几个特别实际的问题。5.1 反向代理解决了什么问题第一个问题端口不体面。8080 端口既不好记也不好看用户访问还得带着端口号。Nginx 监听标准的 80 端口HTTP和 443 端口HTTPS对外是干干净净的域名内部再把请求转发给 8080 端口的 SpringBoot 进程。第二个问题HTTPS 证书管理。自己没有写 SSL 证书代码的能力Nginx 可以轻松完成证书配置和续期。第三个问题日志和访问控制。Nginx 的访问日志能记录所有 HTTP 请求还能做 IP 黑名单、限制请求速率这些功能用 Java 代码实现既费劲又不安全。第四个问题负载均衡。以后项目规模大了一个 Tomcat 扛不住Nginx 可以把请求分发到多个后端实例几乎是零成本升级。用生活化类比Nginx 是前台接待SpringBoot 是办公室里干活的员工。访客不直接找员工而是先到前台由前台判断谁有空、谁能处理然后带进去。5.2 宝塔中配置站点在宝塔面板里安装 Nginx 后进入“网站”页面添加站点。只需要填域名没有域名可以先填服务器 IP其他都能默认。然后进入该站点的配置文件把监听端口换成 80添加关键的两段location / { 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它把 Nginx 收到的所有请求转发到本机 8080 端口。后面那几行proxy_set_header是为了让 SpringBoot 拿到真实客户端 IP 和原始请求头。如果不设置X-Forwarded-For后端拿到的全是 127.0.0.1做日志分析和风控功能时就会很尴尬。改完配置记得保存并重载 Nginxnginx -t nginx -s reloadnginx -t是必须的一步配置文件有语法错误的话直接 reload 会失败服务直接挂掉。先测试再重载是运维的铁律。5.3 绑定域名与 SSL 证书有域名的话去域名服务商加一条 A 记录指向服务器 IP。等解析生效后浏览器访问域名就能看到应用了。如果要用 HTTPS 访问宝塔有免费的 Lets Encrypt 证书申请功能在站点设置里找到“SSL”选项卡一键申请并部署。部署完后可以在 Nginx 配置里加一段 HTTP 自动跳转 HTTPSserver { listen 80; server_name your-domain.com; return 301 https://$host$request_uri; }注意做 HTTP 跳转 HTTPS 时301 是永久跳转浏览器和搜索引擎都会缓存这个跳转。如果后续要改回来记得先清浏览器缓存否则会很煎熬。6. 运维日常三大件日志查看、版本升级、数据备份部署成功只是万里长征第一步。项目上线后的日常运维主要就三件事看日志、发版本、备份数据。6.1 日志文件的正确查看方式前面 nohup 启动时把日志写到了app.log。查日志的黄金姿势# 实时跟踪日志输出 tail -f app.log # 查看最后 200 行 tail -n 200 app.log # 按关键字搜索最近的内容 grep -n ERROR app.log | tail -n 50 # 按时间范围筛选需要日志里有时间戳 grep 2025-06-01 14: app.log日志文件会越来越大时间久了把磁盘塞满。两个思路一是直接在 SpringBoot 里配置 logback 的滚动策略二是写个定时任务定期清理。我更推荐前者——在application-prod.yml里配置日志路径和滚动策略按天滚动并保留 7 天或 15 天比你写 shell 脚本处理日志靠谱得多。SpringBoot 标配的 logback 配置片段示例logging: file: name: /www/wwwroot/app/logs/app.log logback: rollingpolicy: max-history: 15 max-file-size: 100MB6.2 升级版本的流程化操作每次发新版本如果没有一套顺手的流程很容易在反复停启中搞乱。我经历过几次上线事故后总结了一个稳妥的顺序备份当前 jar 包cp app.jar app.jar.bak-$(date %Y%m%d%H%M%S)用ps -ef找到旧进程 PIDkill -15优雅停止上传新 jar 包覆盖旧包nohup java -jar app.jar --spring.profiles.activeprod app.log 21 启动tail -f app.log观察启动日志确认成功用curl测试接口然后才让用户访问这套流程看着简单但“先备份再替换”这条被我刻进了骨子里。因为总有那么一次你回滚时发现旧包已经被覆盖了只能从 git 历史重新编译那是真的绝望。6.3 数据库备份与恢复数据库是系统的钱袋子这个钱袋子不能裸奔。宝塔面板本身有“计划任务”功能可以创建定时任务做数据库备份。我习惯每天早上 3 点自动备份一次备份文件保留 7 天。宝塔的数据库备份可以手动备份也可以设置计划任务自动备份备份完成后会有文件生成这样数据库出问题能恢复到前一天。顺便说一句服务器做了数据库备份之外我还会额外建议你定期把备份文件下载到本地。因为服务器本身可能出现故障只备份在同一台机器上是假安全感。7. 部署时的常见坑我踩过的一线实录这些年部署踩过的坑写出来能成一本书。挑几个大家最容易遇到的集中讲一下排查思路。7.1 端口被占用启动日志报Port 8080 was already in use。这种情况十有八九是上一次启动的进程没杀掉或者别的程序占了端口。排查ss -lntp | grep 8080会显示占用进程的 PID然后kill -15 PID。如果 kill 不掉查看一下是不是僵尸进程或者用kill -9强杀。7.2 数据库连接失败报错通常是Communications link failure或Access denied for user。先检查配置文件的数据库地址对不对、密码对不对再看 MySQL 服务有没有启动systemctl status mysqld最后看 MySQL 是否只允许本地连接——如果权限表里用户 host 不是 localhost你的应用也别想连上。这里有个很常见的坑SpringBoot 配置文件里数据库密码写错了不报语法错误程序照样启动但等到第一个数据库请求过来才会抛异常。所以启动成功后别急着高兴先调一个查询接口验证一下。7.3 内存不足启动被杀云服务器如果是 2G 内存跑个 MySQL 再跑个 SpringBoot 就很吃紧。解决办法有几种思路调整 JVM 参数限制堆内存比如-Xms256m -Xmx512m用轻量级 MySQL 替代品或减小缓存配置升级服务器配置调整堆内存的启动命令nohup java -Xms256m -Xmx512m -jar app.jar --spring.profiles.activeprod app.log 21 注意-Xms和-Xmx不要设得一样大除非你确定内存需求稳定。设一样大有触发 JVM 扩容问题的可能在极小众场景下也存在但更关键的是设太小会有 GC 问题。我个人的经验是 512M 起步先观察一段时间的 GC 日志和内存占用再调。7.4 中文乱码控制台日志中文乱码一般是字符集不一致。JVM 默认字符集跟随系统可以显式指定nohup java -Dfile.encodingUTF-8 -jar app.jar app.log 21 如果接口返回的中文在数据库存下来也是乱码那大概率是数据库表的字符集不是 utf8mb4。建库时统一指定CREATE DATABASE app_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;7.5 Nginx 502 Bad Gateway页面报 502意思是 Nginx 等不到后端的正常响应。最常见原因是后端进程挂了其次是后端启动慢导致 Nginx 超时。碰到 502 千万别慌先看后端进程在不在ps -ef | grep java再看后端日志tail -n 100 app.log。进程挂了就看日志报什么错进程没挂但是响应超时的话可能是连接数不够或慢 SQL 导致线程池打满。8. 两条提升效率的进阶技巧下面这两个技巧不算必须但能显著提升我个人这几个项目的运维体验分享出来供参考。8.1 用启动脚本替代手动敲命令刚部署那会儿我每次上线都是手动敲命令敲多了想吐。后来随手写了个 shell 脚本一键完成停止、备份、启动#!/bin/bash APP_NAMEapp.jar APP_PATH/www/wwwroot/app cd $APP_PATH PID$(ps -ef | grep $APP_NAME | grep -v grep | awk {print $2}) if [ -n $PID ]; then echo Stopping old process: $PID kill -15 $PID sleep 3 fi cp $APP_NAME $APP_NAME.bak-$(date %Y%m%d%H%M%S) nohup java -Xms256m -Xmx512m -jar $APP_NAME --spring.profiles.activeprod app.log 21 echo Started. Log: app.log这样上线就变成了“上传 jar 包、执行脚本”两步操作。脚本本身还能做成一个“重启”入口日常发版省了不少事。8.2 用 JFianl 的失败重跑思路应对进程守护对 Java 进程来说如果因为内存溢出或未被捕获的异常直接退出没有人去拉它它就彻底没了。生产环境最好有个守护机制。简单的方案是把启动命令写进宝塔的计划任务每隔几分钟检查一次进程是否存活如果挂了就重新拉起。这算是一个不折腾人的兜底方案。如果要更专业的进程守护可以用 systemd配置一个 service 文件让系统帮你管进程生命周期。但写了 systemd 之后就不太依赖宝塔窗口了适合真正跑稳定了的项目再上。写在最后的一点个人经验从第一次部署手忙脚乱到后来形成一套固定的流程我最大的体会是部署这件事80% 的坑都出在环境差异上而不是代码本身。你花在理解“每一步到底在干什么”上的时间绝对比到处复制粘贴命令要值得。另一个体会是运维流程要尽可能固化。哪怕只是几行启动脚本、一个备份计划都能让你在半夜被叫起来修 bug 时少掉一半头发。如果你完全是个新手建议第一次部署时从头到尾手动操作一遍别急着上脚本——手动过程中你才会真正理解进程、端口、防火墙、数据库连接这几样东西是怎么协作的。这套流程跑通一次之后后面的路就很顺了。等到你对 jar 包、Nginx 反代、日志排查都有感觉了再去接触容器化也不迟那时候你会比别人更容易理解容器到底解决了什么问题。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →