前后端分离项目云上部署实战:腾讯云CVM + Nginx反向代理 + SpringBoot自动化发布完整代码
一、开篇:为什么选择腾讯云CVM部署前后端分离项目
前后端分离架构已经成为现代Web应用开发的主流模式——后端提供RESTful API,前端独立构建和部署。这种架构带来的开发效率和维护便利性毋庸置疑,但到了部署环节,很多开发者却卡在了"最后一公里":后端jar包不知道放哪里、前端dist文件不会配Nginx、端口不通、跨域报错、页面空白……折腾一整天可能连一个hello world都跑不起来。
本文的目标就是解决这个问题。我们将从一个典型的SpringBoot + Vue前后端分离项目出发,在腾讯云CVM上完整走完从0到上线的全过程。所有操作基于真实的腾讯云控制台界面和Linux命令行,不跳步、不省略、不假设你懂Linux。文章会包含完整的配置文件代码和自动化部署脚本,你可以直接复制使用。
需要先登录腾讯云控制台,点击:腾讯云控制台,还没有账号,点击:注册后再关联,已有账号点击:登录后再关联
二、项目架构与准备
2.1 项目结构
本文以典型的SpringBoot + Vue前后端分离项目为例:
- 后端:Spring Boot 2.7.x(或3.x),Maven打包生成app.jar,内置Tomcat,默认端口8080
- 前端:Vue 3 + Vue Router + Axios,npm run build生成dist/静态资源目录
- 通信方式:前端所有API请求以/api/开头,由Nginx反向代理转发至后端服务
- 运行环境:腾讯云CVM,Ubuntu 22.04或CentOS 7.9
2.2 服务器选购建议
很多新手买服务器时容易踩坑——要么配置选太小跑不动,要么选错地域导致访问延迟高。对于SpringBoot + Vue项目,建议配置如下:
- CPU & 内存:2核4GB起步——够跑后端服务 + Nginx + MySQL,留有余量避免OOM
- 系统镜像:Ubuntu 22.04 或 CentOS 7.9——生态稳定,教程最多
- 公网带宽:3Mbps起——够日常访问和小流量接口调用,后续可随时升配
- 地域:选离目标用户最近的数据中心,全国用户可选北京或上海
地域选择有一个重要原则:如果你有多台服务器需要内网通信(如Web服务器+数据库服务器),必须选同一个地域——不同地域的服务器之间内网不互通。
三、安全组配置:开放必要端口
买了服务器之后,新手最容易犯的错误就是忘记配置安全组——服务器配好了、网站部署了,但浏览器就是打不开,排查了半天发现是端口没开。安全组是CVM的"虚拟防火墙",具备状态检测和数据包过滤功能,不开放对应端口,外部流量就进不来。
以下是必须开放的端口清单:
| 端口 | 协议 | 用途 | 是否必须 | 安全建议 |
|---|---|---|---|---|
| 22 | TCP | SSH远程登录 | ✅ 必须 | 建议限制来源IP |
| 80 | TCP | HTTP网站访问 | ✅ 必须 | 对所有IP开放 |
| 443 | TCP | HTTPS加密访问 | ✅ 必须 | 对所有IP开放 |
| 8080 | TCP | SpringBoot后端 | 按需 | 建议仅内网或通过Nginx代理 |
| 3306 | TCP | MySQL数据库 | 按需 | 仅内网开放,严禁对公网 |
安全组配置实操步骤:
- 登录腾讯云控制台,在左侧导航找到"云服务器CVM" → 选择你的实例
- 点击"安全组"选项卡 → 点击"添加规则"
- 类型选"自定义",来源填0.0.0.0/0(允许所有IP)或指定IP段
- 协议端口填TCP:80,443(多端口用逗号分隔),策略选"允许"
- 保存后立即生效
安全组配置的几个常见错误:忘记开80/443端口、数据库端口对公网全开放(3306/6379等仅对内网IP开放)、图省事把所有端口全开放、SSH端口不限制来源IP、配置了安全组但忘记绑定到实例。
四、服务器环境搭建
4.1 连接服务器
购买完成后,在腾讯云控制台获取公网IP。Windows用户可以用MobaXterm或Xshell,Mac和Linux直接用终端:
ssh root@你的公网IP首次登录需重置密码,按提示设一个含大小写字母+数字+符号的强密码。
4.2 安装JDK
Spring Boot 2.7.x推荐JDK 11,Spring Boot 3.x需要JDK 17。以Ubuntu为例:
# Ubuntu/Debian
sudo apt update
sudo apt install -y openjdk-11-jdk
# CentOS/RHEL
yum install -y java-11-openjdk-devel
# 验证安装
java -version4.3 安装Nginx
Nginx用于托管前端静态资源和反向代理后端API请求:
# Ubuntu/Debian
sudo apt install -y nginx
# CentOS/RHEL
yum install -y nginx
# 启动并设置开机自启
sudo systemctl start nginx
sudo systemctl enable nginx启动后在浏览器访问 http://你的公网IP,看到"Welcome to nginx!"说明安装成功。
五、部署SpringBoot后端
5.1 本地打包
在项目根目录执行Maven打包命令:
mvn clean package -Dmaven.test.skip=true参数说明:clean清理之前的构建产物,package执行编译、测试、打包,-Dmaven.test.skip=true跳过测试加速打包并避免测试失败影响构建。打包成功后,jar包位于target/目录下,例如myapp-0.0.1-SNAPSHOT.jar。
5.2 上传jar包到服务器
使用scp命令将jar包上传到服务器:
scp target/myapp-0.0.1-SNAPSHOT.jar root@你的公网IP:/opt/app/也可以使用SFTP工具(如MobaXterm自带的文件浏览器)直接拖拽上传。
5.3 启动后端服务
使用nohup命令让程序在关闭SSH连接后继续运行:
cd /opt/app
nohup java -jar myapp-0.0.1-SNAPSHOT.jar --server.port=8080 > app.log 2>&1 &命令解析:nohup让进程不受终端关闭影响,--server.port=8080指定运行端口,> app.log 2>&1将标准输出和错误都重定向到app.log文件,&放入后台运行。
验证服务是否启动成功:
# 查看进程
ps aux | grep myapp
# 测试接口
curl http://localhost:8080/api/health如果返回正确的JSON数据,说明后端服务已经在服务器上正常工作。
六、部署Vue前端与Nginx配置
6.1 前端打包
在Vue项目根目录执行构建命令:
npm run build构建完成后生成dist/目录,包含所有静态资源文件(HTML、CSS、JS)。
6.2 上传前端静态文件
将dist/目录下的所有文件上传到服务器的Nginx静态目录。Ubuntu默认的Nginx静态目录是/var/www/html/,CentOS是/usr/share/nginx/html/。
# 创建项目前端目录
sudo mkdir -p /var/www/myapp
# 上传dist文件(在本地执行)
scp -r dist/* root@你的公网IP:/var/www/myapp/6.3 配置Nginx反向代理
Nginx的核心配置位于/etc/nginx/nginx.conf,但更规范的做法是在/etc/nginx/conf.d/或/etc/nginx/sites-enabled/下创建独立的配置文件。
创建配置文件 /etc/nginx/conf.d/myapp.conf:
server {
listen 80;
server_name 你的域名或公网IP;
# 前端静态资源
location / {
root /var/www/myapp;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# API反向代理到SpringBoot
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}配置要点解析:
- location /:托管前端静态文件,try_files确保Vue的SPA路由能正确回退到index.html
- location /api/:拦截所有以/api/开头的请求,转发到后端SpringBoot服务
- proxy_pass末尾的/:至关重要——它会剥离匹配的路径前缀(/api/),再转发请求,避免后端收到重复路径
- proxy_set_header:确保后端能正确识别原始请求的客户端IP、Host和协议
配置完成后测试并重启Nginx:
# 测试配置语法
sudo nginx -t
# 重新加载配置(不中断服务)
sudo systemctl reload nginx6.4 常见Nginx配置陷阱
根据实际项目经验,Nginx反向代理配置中最容易踩的坑是代理路径错误。例如,前端请求 /api/user/login,但Nginx转发后后端实际收到 /user/login,导致JWT白名单不匹配直接返回401。解决方案是确保proxy_pass配置正确——如果proxy_pass末尾带/,Nginx会移除location匹配的路径前缀。
另一个常见问题是后端实际端口与预期不符。排查时先用curl直接测试后端端口:
curl -i -X POST http://127.0.0.1:8080/api/user/login如果返回Connection refused,说明端口不对,需要查看jar包内的application.properties确认server.port配置。
七、自动化发布脚本
手动部署的痛点在于每次更新都需要重复执行打包、上传、重启等一系列操作。编写Shell脚本可以将这些步骤自动化。
7.1 服务器端部署脚本
在服务器上创建 /opt/deploy/deploy.sh:
#!/bin/bash
# ================= 配置区 =================
APP_NAME="myapp-0.0.1-SNAPSHOT.jar"
APP_PORT="8080"
WORK_DIR="/opt/app"
BACKUP_DIR="/opt/app/backup"
JAVA_OPTS="-Xms512m -Xmx512m -Dspring.profiles.active=prod"
LOG_FILE="${WORK_DIR}/app.log"
# ===========================================
cd ${WORK_DIR} || exit 1
# 1. 停止旧进程(优雅停止)
echo "[$(date)] Stopping old process..."
PID=$(ps -ef | grep ${APP_NAME} | grep -v grep | awk '{print $2}')
if [ -n "$PID" ]; then
kill $PID
# 等待端口释放,最多30秒
for i in {1..30}; do
if ! netstat -tlnp 2>/dev/null | grep :${APP_PORT} > /dev/null 2>&1; then
break
fi
sleep 1
done
# 如果端口仍占用,强制杀死
if netstat -tlnp 2>/dev/null | grep :${APP_PORT} > /dev/null 2>&1; then
PID=$(ps -ef | grep ${APP_NAME} | grep -v grep | awk '{print $2}')
[ -n "$PID" ] && kill -9 $PID
sleep 2
fi
echo "[$(date)] Old process stopped."
fi
# 2. 备份旧版本
if [ -f "${WORK_DIR}/${APP_NAME}" ]; then
mkdir -p ${BACKUP_DIR}
cp ${APP_NAME} ${BACKUP_DIR}/${APP_NAME}.$(date +%Y%m%d%H%M%S)
echo "[$(date)] Backup completed: ${BACKUP_DIR}/${APP_NAME}.$(date +%Y%m%d%H%M%S)"
fi
# 3. 启动新服务
echo "[$(date)] Starting new process..."
nohup java ${JAVA_OPTS} -jar ${APP_NAME} --server.port=${APP_PORT} > ${LOG_FILE} 2>&1 &
# 4. 健康检查(等待最多60秒)
echo "[$(date)] Checking health..."
HEALTH_URL="http://localhost:${APP_PORT}/actuator/health"
for i in {1..60}; do
sleep 1
if curl -s ${HEALTH_URL} 2>/dev/null | grep -q "UP"; then
echo "[$(date)] Deployment successful!"
exit 0
fi
done
# 如果健康检查失败,回滚
if [ -f "${BACKUP_DIR}/$(ls -t ${BACKUP_DIR} | head -1)" ]; then
echo "[$(date)] Health check failed! Rolling back..."
LATEST_BACKUP=$(ls -t ${BACKUP_DIR} | head -1)
cp ${BACKUP_DIR}/${LATEST_BACKUP} ${WORK_DIR}/${APP_NAME}
# 重新启动旧版本
nohup java ${JAVA_OPTS} -jar ${APP_NAME} --server.port=${APP_PORT} > ${LOG_FILE} 2>&1 &
echo "[$(date)] Rollback completed. Please check logs."
fi
echo "[$(date)] Deployment failed! Check ${LOG_FILE}"
exit 1脚本核心逻辑:
- 停止旧进程:先优雅停止(kill),等待端口释放,如果超时则强制杀死(kill -9)
- 备份旧版本:将当前运行的jar备份到backup目录,带时间戳
- 启动新服务:使用nohup后台启动新jar包
- 健康检查:通过/actuator/health端点验证服务是否正常启动
- 自动回滚:如果健康检查失败,自动恢复最近备份的版本
7.2 本地一键发布脚本
在本地项目根目录创建 publish.sh:
#!/bin/bash
# ================= 配置区 =================
SERVER_IP="你的公网IP"
SERVER_USER="root"
SERVER_DIR="/opt/app/"
LOCAL_JAR="./target/myapp-0.0.1-SNAPSHOT.jar"
# ===========================================
# 1. 本地打包
echo "[$(date)] Building project..."
mvn clean package -Dmaven.test.skip=true
if [ $? -ne 0 ]; then
echo "Build failed!"
exit 1
fi
# 2. 上传jar包
echo "[$(date)] Uploading jar to server..."
scp ${LOCAL_JAR} ${SERVER_USER}@${SERVER_IP}:${SERVER_DIR}
if [ $? -ne 0 ]; then
echo "Upload failed!"
exit 1
fi
# 3. 远程执行部署脚本
echo "[$(date)] Executing deploy script on server..."
ssh ${SERVER_USER}@${SERVER_IP} "bash /opt/deploy/deploy.sh"
echo "[$(date)] Publish completed!"使用方法:
chmod +x publish.sh deploy.sh
./publish.sh脚本执行后,会自动完成打包→上传→停止旧服务→备份→启动新服务→健康检查的完整流程。
八、生产环境增强建议
8.1 使用Systemd管理服务
纯nohup启动缺乏守护能力,生产环境建议使用systemd管理。创建 /etc/systemd/system/myapp.service:
[Unit]
Description=MyApp Spring Boot Service
After=network.target
[Service]
User=root
WorkingDirectory=/opt/app
ExecStart=/usr/bin/java -Xms512m -Xmx512m -jar /opt/app/myapp-0.0.1-SNAPSHOT.jar --server.port=8080
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target启用服务:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp8.2 日志切割
使用logrotate防止app.log无限增长。创建 /etc/logrotate.d/myapp:
/opt/app/app.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 644 root root
postrotate
systemctl reload myapp > /dev/null 2>&1 || true
endscript
}8.3 配置HTTPS
生产环境建议启用HTTPS。使用腾讯云SSL证书或Let's Encrypt免费证书,在Nginx配置中添加443端口监听:
server {
listen 443 ssl http2;
server_name 你的域名;
ssl_certificate /etc/nginx/ssl/your-domain.crt;
ssl_certificate_key /etc/nginx/ssl/your-domain.key;
# ... 其他配置同80端口
}
server {
listen 80;
server_name 你的域名;
return 301 https://$server_name$request_uri;
}8.4 安全加固
几个关键的安全措施:
- 为每个应用分配独立的系统用户运行,防止文件权限冲突
- 数据库端口(3306、6379)仅对内网开放,严禁暴露到公网
- SSH端口限制来源IP,或改用密钥登录
- Nginx配置中隐藏后端服务版本信息
九、总结
本文完整梳理了前后端分离项目在腾讯云CVM上的部署全流程,从服务器选购、安全组配置、环境搭建、后端部署、前端托管、Nginx反向代理配置,到自动化发布脚本的编写。核心要点可以概括为:
- 安全组是第一步——80、443等端口必须开放,数据库端口严禁对公网
- Nginx反向代理是核心——统一入口、静态托管、API转发三位一体,注意proxy_pass末尾的/决定路径拼接方式
- 自动化脚本是效率提升的关键——包含停止、备份、启动、健康检查、回滚的完整流程
- 生产环境需要systemd + 日志切割 + HTTPS——提升服务的稳定性和安全性
通过本文的方案,你可以将部署时间从手动操作的30分钟缩短到脚本一键执行的2分钟,同时大幅降低人为失误的风险。
常见问题与解答
问1:部署后访问页面空白,但Nginx欢迎页能打开,是什么原因?
答:通常是前端静态文件路径不对。检查Nginx配置中root指向的目录是否包含了正确的dist文件,以及try_files配置是否正确。另外检查浏览器控制台是否有404错误,确认静态资源(JS、CSS)是否加载成功。
问2:API请求返回404,但后端服务已经在跑,怎么排查?
答:首先用curl直接测试后端端口:curl http://localhost:8080/api/xxx,确认后端能正常响应。如果后端正常但通过Nginx访问404,问题在Nginx代理配置——检查location和proxy_pass的路径拼接是否正确,特别是proxy_pass末尾是否有/。
问3:部署脚本中的健康检查一直失败怎么办?
答:健康检查依赖Spring Boot Actuator的/actuator/health端点。如果项目没有引入spring-boot-starter-actuator依赖,或者management.endpoints.web.exposure.include没有包含health,这个端点会返回404。解决方案:在pom.xml中添加actuator依赖,或在application.properties中配置management.endpoints.web.exposure.include=health。
问4:为什么我的Nginx配置修改后不生效?
答:可能是配置语法错误导致Nginx重载失败。先用nginx -t测试语法,如果有错误会提示具体位置。另外确认修改的是正确的配置文件——Ubuntu下/etc/nginx/sites-enabled/中的配置会覆盖/etc/nginx/nginx.conf中的默认配置。
问5:自动化脚本中scp上传失败,提示Permission denied怎么办?
答:检查服务器是否开启了密码登录(默认开启),或者改用密钥认证。如果使用root用户,确认sshd_config中PermitRootLogin设置为yes。另外检查服务器磁盘空间是否充足。
问6:前后端分离项目部署后出现跨域问题,但本地开发时没有,怎么解决?
答:本地开发时前端devServer通常会配置proxy代理,但生产环境通过Nginx反向代理解决跨域是最佳实践。只要Nginx将/api/请求正确转发到后端同一域名/IP的不同端口,浏览器就不会有跨域限制。如果仍有跨域问题,检查Nginx配置中proxy_set_header是否正确传递了Host和Origin。



