第17篇 · Linux部署实战:让项目真正跑在云端

代码写完了,接口调通了,接下来要做的是把项目部署到服务器上,让其他人也能访问。

如果你只在本地跑过 Spring Boot 项目,那你可能对部署这件事还有一些陌生------打包命令是什么?服务器上怎么装 Java?怎么让项目在后台一直运行,关掉终端也不会停?

这一篇我们用一篇完整的实战指南,把这些问题全部解决。从 Linux 基础命令到云服务器选购,从环境安装到项目部署,完整走一遍。

学习目标

  • 掌握 Linux 常用命令(文件操作、进程管理、网络查看)
  • 掌握在 Ubuntu 上安装 JDK 和 MySQL
  • 掌握 Spring Boot 项目的打包(mvn package)与部署(java -jar
  • 掌握多环境配置application-dev.yml / application-prod.yml
  • 掌握 nohup 后台运行和 kill 停止进程

正文

一、Linux 基础命令速通

在开始部署之前,先熟悉一组最常用的 Linux 命令。这些命令覆盖了 90% 的日常操作场景。

文件操作类

命令 用法 说明
ls ls -la 列出当前目录下的所有文件(含隐藏文件),显示详细信息
cd cd /opt 切换目录
pwd pwd 显示当前所在目录的完整路径
mkdir mkdir -p app/logs 创建目录,-p 可以递归创建父目录
rm rm -rf app/ 删除目录或文件,-r 递归删除,-f 强制不确认
cp cp source.txt target/ 复制文件
mv mv old.txt new.txt 移动文件或重命名
tail tail -f app.log 查看文件末尾,-f 实时跟踪文件变化,最常用于查看日志

进程管理类

命令 用法 说明
ps `ps -ef grep java`
kill kill -9 12345 终止进程,-9 强制终止,-15 优雅终止
netstat netstat -tlnp 查看端口占用情况,-tlnp 显示 TCP 端口和对应的进程

grep 是文本搜索工具,常与其他命令配合使用。| 是管道操作符,把前一个命令的输出传给后一个命令作为输入。 这两个符号是 Linux 命令行的基本组合方式,几乎所有排查场景都会用到。

二、云服务器选购与连接

如果你是第一次买云服务器,不用纠结配置。

规格推荐

  • 入门级(个人项目):1 核 2G 内存 + 40G 硬盘,约 50-80 元/月
  • 标准级(小型应用):2 核 4G 内存 + 60G 硬盘,约 150-250 元/月
  • 生产级(线上正式服务):4 核 8G 内存 + 100G 硬盘,约 400-600 元/月

操作系统 :推荐 Ubuntu 22.04 LTS。长期支持版本,软件源稳定,国内云厂商均有镜像。

连接方式

  1. 在云厂商控制台重置 root 密码
  2. 使用 SSH 客户端连接(命令行或 XShell / FinalShell / Termius)
  3. 命令格式:ssh root@你的公网IP -p 22

安全组配置

这是最容易忽略的步骤。服务器默认只开放 22 端口(SSH),你需要手动开放:

端口 用途
22 SSH 远程连接
80 HTTP 服务(Nginx)
443 HTTPS 服务
8080 Spring Boot 应用直接访问
3306 MySQL(建议仅内网开放)

在云厂商控制台的"安全组"或"防火墙"中添加入站规则,允许对应端口的访问。

三、Java 环境安装

Ubuntu 22.04 可以直接用 apt 安装 OpenJDK 17:

bash 复制代码
# 更新软件包索引
sudo apt update

# 安装 OpenJDK 17
sudo apt install -y openjdk-17-jdk

# 验证安装
java -version

输出类似:

复制代码
openjdk version "17.0.9" 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+9-Ubuntu-122.04)
OpenJDK 64-Bit Server VM (build 17.0.9+9-Ubuntu-122.04, mixed mode, sharing)

四、MySQL 安装与配置

bash 复制代码
# 安装 MySQL 8.0
sudo apt install -y mysql-server

# 启动 MySQL 服务
sudo systemctl start mysql
sudo systemctl enable mysql   # 设置开机自启

# 查看 MySQL 状态
sudo systemctl status mysql

安全配置

MySQL 8.0 默认 root 用户使用 auth_socket 认证插件,需要用 sudo mysql 才能登录。为了方便外部连接,需要改为密码认证:

bash 复制代码
# 用 root 登录 MySQL(无密码)
sudo mysql

# 切换到 mysql 数据库
USE mysql;

# 修改 root 密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的新密码';

# 允许外部 IP 连接(生产环境建议仅限内网)
CREATE USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '你的新密码';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;

# 刷新权限
FLUSH PRIVILEGES;

# 退出
EXIT;

测试连接

bash 复制代码
# 用新密码登录测试
mysql -u root -p

如果连接成功,说明配置完成。

五、多环境配置

在生产环境中,开发环境的配置(如本地 MySQL、调试日志)和生产环境完全不同。Spring Boot 通过 Profile 来隔离多环境配置。

配置结构

复制代码
src/main/resources/
├── application.yml          # 公共配置
├── application-dev.yml      # 开发环境
└── application-prod.yml     # 生产环境

公共配置(application.yml

yaml 复制代码
spring:
  profiles:
    active: dev  # 默认使用 dev 环境
  jackson:
    time-zone: Asia/Shanghai
    date-format: yyyy-MM-dd HH:mm:ss

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true

server:
  port: 8080

开发环境(application-dev.yml

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/blog_db?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

logging:
  level:
    com.example.blog: DEBUG

生产环境(application-prod.yml

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/blog_db?useSSL=true&serverTimezone=Asia/Shanghai
    username: blog_user
    password: 生产环境强密码

logging:
  level:
    com.example.blog: INFO

server:
  port: 8080  # 生产环境通常用 Nginx 转发,这里保持 8080

切换环境

在打包或启动时通过 spring.profiles.active 指定环境:

bash 复制代码
# 方式一:启动时指定
java -jar blog.jar --spring.profiles.active=prod

# 方式二:通过环境变量
export SPRING_PROFILES_ACTIVE=prod
java -jar blog.jar

# 方式三:在配置文件中设置(不推荐)
# application.yml 中写 spring.profiles.active: prod

六、项目打包与上传

打包

bash 复制代码
# 在项目根目录执行(跳过测试,加快打包速度)
mvn clean package -DskipTests

# 打包完成后,target 目录下会生成 blog-backend-1.0.0.jar

上传到服务器

使用 scp 命令上传:

bash 复制代码
# 从本地上传到服务器
scp target/blog-backend-1.0.0.jar root@你的服务器IP:/opt/blog/

如果服务器没有 /opt/blog/ 目录,先用 mkdir -p /opt/blog 创建。

在服务器上验证

bash 复制代码
# 切换到部署目录
cd /opt/blog

# 查看文件
ls -lh
# 输出:-rw-r--r-- 1 root root 45M Jan 15 10:30 blog-backend-1.0.0.jar

七、后台运行与日志查看

前台运行(仅用于测试,关掉终端就停了)

bash 复制代码
java -jar blog-backend-1.0.0.jar --spring.profiles.active=prod

后台运行(生产环境推荐)

bash 复制代码
nohup java -jar blog-backend-1.0.0.jar \
  --spring.profiles.active=prod \
  > /opt/blog/logs/app.log 2>&1 &

这个命令拆开看:

  • nohup:忽略 SIGHUP 信号,终端关闭后进程继续运行
  • >:重定向标准输出(stdout)到日志文件
  • 2>&1:把标准错误(stderr)也合并到标准输出,统一写入日志
  • &:在后台运行,不占用终端

查看日志

bash 复制代码
# 实时跟踪日志(最常用)
tail -f /opt/blog/logs/app.log

# 查看最近 100 行
tail -n 100 /opt/blog/logs/app.log

# 查看全部日志
cat /opt/blog/logs/app.log

停止进程

bash 复制代码
# 查找 Java 进程的 PID
ps -ef | grep blog-backend-1.0.0.jar

# 输出示例:
# root  12345  1  0 10:30 ?  00:00:30 java -jar blog-backend-1.0.0.jar

# 优雅停止
kill 12345

# 如果优雅停止无效,强制终止
kill -9 12345

查看进程是否还在运行

bash 复制代码
ps -ef | grep java | grep blog

一键启动和停止脚本

把以下脚本保存为 /opt/blog/start.sh

bash 复制代码
#!/bin/bash
JAR_NAME="blog-backend-1.0.0.jar"
LOG_DIR="/opt/blog/logs"
LOG_FILE="$LOG_DIR/app.log"

# 创建日志目录
mkdir -p $LOG_DIR

# 检查进程是否已经在运行
PID=$(ps -ef | grep $JAR_NAME | grep -v grep | awk '{print $2}')
if [ -n "$PID" ]; then
    echo "应用已在运行,PID: $PID"
    exit 1
fi

# 启动应用
nohup java -jar $JAR_NAME --spring.profiles.active=prod > $LOG_FILE 2>&1 &
echo "应用启动成功,日志文件: $LOG_FILE"

停止脚本 /opt/blog/stop.sh

bash 复制代码
#!/bin/bash
JAR_NAME="blog-backend-1.0.0.jar"

PID=$(ps -ef | grep $JAR_NAME | grep -v grep | awk '{print $2}')
if [ -z "$PID" ]; then
    echo "应用未运行"
    exit 1
fi

kill $PID
echo "应用已停止,PID: $PID"

代码示例

示例一:多环境配置切换

第一步:确认配置文件存在

复制代码
src/main/resources/
├── application.yml
├── application-dev.yml
└── application-prod.yml

第二步:在 application.yml 中指定默认环境

yaml 复制代码
spring:
  profiles:
    active: dev  # 开发时默认用 dev

第三步:打包时指定环境

bash 复制代码
# 开发环境打包
mvn clean package -DskipTests

# 启动时覆盖环境
java -jar target/blog.jar --spring.profiles.active=prod

第四步:验证环境是否生效

启动后查看日志,找到类似以下输出:

复制代码
... Initializing Spring Boot Application (prod)
... The following 1 profile is active: "prod"

示例二:项目部署完整流程(操作清单)

这是一份完整的部署操作清单,你可以按顺序执行:

服务器端

bash 复制代码
# 1. 安装 Java
sudo apt update && sudo apt install -y openjdk-17-jdk
java -version

# 2. 安装 MySQL
sudo apt install -y mysql-server
sudo systemctl start mysql
sudo systemctl enable mysql

# 3. 创建数据库
mysql -u root -p
CREATE DATABASE blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
EXIT;

# 4. 导入数据(如果有 SQL 文件)
mysql -u root -p blog_db < /opt/blog/blog.sql

# 5. 创建部署目录
mkdir -p /opt/blog/logs

# 6. 上传 JAR 包(在本地执行)
scp target/blog-backend-1.0.0.jar root@你的IP:/opt/blog/

# 7. 修改生产环境配置
vi /opt/blog/application-prod.yml
# 修改数据库密码,改为生产环境的强密码

# 8. 启动项目
cd /opt/blog
nohup java -jar blog-backend-1.0.0.jar \
  --spring.profiles.active=prod \
  > logs/app.log 2>&1 &

# 9. 查看启动日志
tail -f logs/app.log

# 10. 测试接口
curl http://localhost:8080/api/posts

前端联调测试

项目启动后,使用 Postman 或 curl 测试接口:

bash 复制代码
# 健康检查(如果写了 /health 接口)
curl http://你的IP:8080/api/health

# 获取文章列表
curl http://你的IP:8080/api/posts

如果访问失败,检查安全组是否开放了 8080 端口。

新手错误 vs 正确姿势

错误表象 根本原因 正确姿势
外网无法访问项目,浏览器一直转圈 云服务器防火墙/安全组未开放端口 在云厂商控制台 → 安全组 → 添加入站规则,开放 8080 端口(或其他应用端口)
项目启动失败,报 Communications link failure 数据库连接配置错误,或 MySQL 未启动 检查 application-prod.yml 中的数据库 URL、用户名、密码;用 systemctl status mysql 确认 MySQL 正在运行
nohup 启动后项目又停了,没有任何日志 nohup.out 或指定日志文件权限不足,或 JAR 包路径错误 确保日志目录有写入权限:chmod 755 /opt/blog/logs
关闭终端后项目停止运行 使用了 java -jar 而非 nohup ... & nohup + & 启动,或用 screen / tmux 替代
`ps -ef grep java` 看到多个 Java 进程,不知道停哪个 服务器上运行了多个 Java 应用

疑难深度追问

Q1:为什么 nohup 启动的程序关闭终端后仍在运行?

nohup 命令让进程忽略 SIGHUP 信号 (终端关闭时发送的信号),因此终端关闭后进程不会退出。& 让进程在后台运行,不占用终端。两者结合就实现了"关掉终端,进程继续跑"的效果。

Q2:如果部署后发现代码有 bug,如何快速更新?

流程是:本地修改代码 → mvn clean package -DskipTestsscp 上传新的 JAR 包 → ps -ef | grep blog.jar 找到旧进程的 PID → kill 停止旧进程 → nohup 启动新进程。如果使用蓝绿部署,可以先启动新版本(用不同的端口),验证通过后再切换流量。

Q3:服务器重启后,Java 进程会自动启动吗?

不会。 nohup 启动的进程不会自动重启。要实现开机自启,有以下几种方案:

方案 复杂度 适用场景
systemd 服务 生产环境推荐
crontab @reboot 简单场景
手动重启 最低 临时部署、验证环境

systemd 方案(生产环境推荐)

创建 /etc/systemd/system/blog.service

复制代码
[Unit]
Description=Blog Backend Service
After=network.target mysql.service

[Service]
Type=simple
User=root
WorkingDirectory=/opt/blog
ExecStart=/usr/bin/java -jar /opt/blog/blog-backend-1.0.0.jar --spring.profiles.active=prod
StandardOutput=append:/opt/blog/logs/app.log
StandardError=append:/opt/blog/logs/app-error.log
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

启用服务:

bash 复制代码
sudo systemctl daemon-reload
sudo systemctl enable blog
sudo systemctl start blog
sudo systemctl status blog

这样服务器重启后,服务会自动启动。

思考与延伸

  1. 动手验证 :按照完整操作清单,在自己的云服务器上部署一遍博客系统。从 apt installnohup java -jar 完整走一遍流程。

  2. 思考题 :如果部署后发现数据库连接报错,但 application-prod.yml 的配置看起来没问题,你会怎么排查?(提示:检查 MySQL 是否允许外部连接、防火墙是否放行 3306 端口、MySQL 服务是否在运行、密码是否包含特殊字符导致 YAML 解析错误)

  3. 延伸阅读 :systemd 的官方文档对 ExecStartRestart 等参数有详细的说明。另外,crontab -e 配合 @reboot 可以实现简单的开机自启。

参考与延伸阅读

  • Ubuntu. OpenJDK Installation Guide. Ubuntu Documentation
  • MySQL. MySQL 8.0 Installation Guide. MySQL Official Documentation
  • Spring Boot. Deploying Spring Boot Applications. Spring Boot Documentation
  • Baeldung. Deploy a Spring Boot Application on Linux. Baeldung, 2024
  • 阿里云. Spring Boot 项目部署到云服务器完整流程. 2024-03
  • 腾讯云. 使用 systemd 管理 Spring Boot 应用. 2024-05