全栈手账 订阅 RSS

Java + Vue 项目部署与运维手册

从打包到上线到日常运维的完整实战指南——以 Spring Boot + Vue 3 + MySQL + Redis + Nginx 技术栈为例,覆盖华为云服务器 + 宝塔面板的实际场景。

读了 63 分钟 更新于 8 月 5 日

一、服务器环境准备

1.1 服务器选购建议

配置项 测试环境 生产环境(小型) 生产环境(中型)
CPU 2 核 2-4 核 4-8 核
内存 2G 4G 8-16G
系统盘 40G 50-100G 100G+
带宽 1M 3-5M 5-10M
系统 CentOS 7.9 / Ubuntu 22.04 同左 同左

推荐:华为云 / 阿里云 / 腾讯云,新人优惠力度大。Java 项目至少 4G 内存,否则 JVM + MySQL + Redis 容易 OOM。

1.2 宝塔面板安装

bash
# CentOS 安装命令yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec# Ubuntu 安装命令wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec

安装完成后:

  1. 记录宝塔面板的登录地址、账号、密码
  2. 在华为云安全组放行宝塔面板端口(默认 8888,建议修改)
  3. 登录宝塔面板,绑定宝塔账号

1.3 通过宝塔安装环境

在宝塔面板「软件商店」中安装:

软件 推荐版本 说明
Nginx 1.24+ Web 服务器 / 反向代理
MySQL 8.0(或 5.7) 数据库
Redis 7.x 缓存
PM2 管理器 最新 Node.js 进程管理(可选)
Java 项目管理器 最新 Java 项目部署(推荐)

1.4 JDK 安装

bash
# 方式 1:通过宝塔软件商店安装 JDK# 在宝塔「软件商店」搜索 Java,选择 JDK 17 或 JDK 21(LTS)# 方式 2:手动安装(推荐 JDK 17 LTS)cd /usr/localwget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gztar -zxvf jdk-17_linux-x64_bin.tar.gzmv jdk-17* jdk17# 配置环境变量cat >> /etc/profile << 'EOF'export JAVA_HOME=/usr/local/jdk17export PATH=$JAVA_HOME/bin:$PATHexport CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarEOFsource /etc/profilejava -version  # 验证安装

1.5 安全组配置

在华为云控制台 → 安全组 → 入方向规则,放行以下端口:

端口 用途 是否对外开放
22 SSH ✅(建议限制 IP)
80 HTTP ✅
443 HTTPS ✅
8888 宝塔面板 ✅(建议改端口+限制 IP)
3306 MySQL ❌(仅内网访问)
6379 Redis ❌(仅内网访问)
8080 Spring Boot ❌(通过 Nginx 代理)

原则:数据库、Redis、后端服务端口不要对公网开放,只开放 80/443/SSH/宝塔。


二、后端部署(Spring Boot)

2.1 打包

bash
# 在项目根目录执行mvn clean package -DskipTests# 生成的 jar 包位置ls -la target/*.jar# target/my-project-1.0.0.jar

pom.xml 关键配置:

xml
<packaging>jar</packaging><build>    <finalName>my-project</finalName>  <!-- 打包后的文件名 -->    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>            <configuration>                <mainClass>com.example.Application</mainClass>            </configuration>        </plugin>    </plugins></build>

2.2 上传到服务器

bash
# 方式 1:宝塔面板上传(简单)# 宝塔面板 → 文件 → 上传到 /www/wwwroot/api/# 方式 2:scp 命令上传scp target/my-project.jar root@1.94.33.51:/www/wwwroot/api/# 方式 3:rz 命令(需安装 lrzsz)yum install -y lrzszcd /www/wwwroot/api/rz  # 选择文件上传

推荐目录结构:

文本
/www/wwwroot/api/├── my-project.jar          # 主程序├── application.yml          # 外部配置文件(覆盖 jar 内配置)├── logs/                    # 日志目录└── backup/                  # 旧版本备份

2.3 配置文件外部化

把 application.yml 放在 jar 同级目录,Spring Boot 会优先读取外部配置:

yaml
# /www/wwwroot/api/application.ymlserver:  port: 8080spring:  datasource:    url: jdbc:mysql://localhost:3306/travel_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8    username: travel_user    password: YourStrongPassword123!  redis:    host: 127.0.0.1    port: 6379    password: YourRedisPassword    database: 0mybatis-plus:  configuration:    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 生产环境关闭logging:  file:    name: /www/wwwroot/api/logs/application.log  level:    root: INFO    com.example: DEBUG

好处:修改配置不需要重新打包,只需重启服务。

2.4 启动与停止

bash
# 基础启动方式(不推荐用于生产)java -jar my-project.jar# 推荐方式:后台运行 + 指定配置 + 指定内存nohup java -Xms512m -Xmx512m \  -jar /www/wwwroot/api/my-project.jar \  --spring.config.location=/www/wwwroot/api/application.yml \  > /www/wwwroot/api/logs/startup.log 2>&1 &# 查看是否启动成功ps aux | grep my-projecttail -f /www/wwwroot/api/logs/startup.log# 停止服务# 方式 1:找到进程号后 killps aux | grep my-project | grep -v grepkill -15 <PID>   # 优雅关闭(推荐)kill -9 <PID>    # 强制关闭(紧急情况)# 方式 2:通过端口号找进程lsof -i:8080kill -15 <PID>

2.5 配置 systemd 服务(推荐)

创建系统服务,实现开机自启 + 方便管理:

bash
# 创建服务文件cat > /etc/systemd/system/api.service << 'EOF'[Unit]Description=Travel API ServiceAfter=network.target mysql.service redis.service[Service]Type=simpleUser=rootWorkingDirectory=/www/wwwroot/apiExecStart=/usr/local/jdk17/bin/java -Xms512m -Xmx512m -jar /www/wwwroot/api/my-project.jar --spring.config.location=/www/wwwroot/api/application.ymlExecStop=/bin/kill -15 $MAINPIDRestart=on-failureRestartSec=10StandardOutput=append:/www/wwwroot/api/logs/systemd.logStandardError=append:/www/wwwroot/api/logs/systemd-error.log[Install]WantedBy=multi-user.targetEOF# 启用并启动systemctl daemon-reloadsystemctl enable api.service    # 开机自启systemctl start api.service     # 启动systemctl status api.service    # 查看状态systemctl restart api.service   # 重启systemctl stop api.service      # 停止# 查看实时日志journalctl -u api.service -f

2.6 JVM 参数建议

参数 说明 建议
-Xms 初始堆大小 与 -Xmx 相同,避免动态调整开销
-Xmx 最大堆大小 服务器内存的 50-60%(4G 服务器 → 2G)
-Xmn 新生代大小 堆的 1/3 ~ 1/2
-XX:MetaspaceSize 元空间初始大小 256m
-XX:MaxMetaspaceSize 元空间最大大小 512m
-XX:+HeapDumpOnOutOfMemoryError OOM 时导出堆快照 ✅ 开启
-XX:HeapDumpPath 堆快照路径 指定到 logs 目录
-Dfile.encoding 文件编码 UTF-8

完整示例(4G 服务器):

bash
java -Xms2g -Xmx2g -Xmn768m \  -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \  -XX:+HeapDumpOnOutOfMemoryError \  -XX:HeapDumpPath=/www/wwwroot/api/logs/heapdump.hprof \  -Dfile.encoding=UTF-8 \  -jar my-project.jar

三、前端部署(Vue)

3.1 打包

bash
# 在前端项目根目录npm install        # 安装依赖npm run build      # 打包# 生成的静态文件在 dist/ 目录ls -la dist/

打包前检查 vite.config.js:

javascript
export default defineConfig({  base: '/',           // 部署在根目录用 '/',子目录用 '/app/'  build: {    outDir: 'dist',    assetsDir: 'assets',    sourcemap: false,  // 生产环境关闭 sourcemap  }})

API 地址配置:

javascript
// .env.productionVITE_API_BASE_URL=/api    // 使用相对路径,通过 Nginx 反向代理

关键:生产环境前端 API 地址用相对路径 /api,不要写死 http://1.94.33.51:8080,避免跨域问题。

3.2 上传 dist 到服务器

bash
# 上传到 Nginx 网站目录scp -r dist/* root@1.94.33.51:/www/wwwroot/web/# 或通过宝塔面板上传# 宝塔 → 文件 → /www/wwwroot/web/ → 上传

3.3 宝塔创建网站

  1. 宝塔面板 → 网站 → 添加站点
  2. 域名:www.example.com(或 IP)
  3. 根目录:/www/wwwroot/web
  4. PHP 版本:纯静态
  5. 创建后把 dist 内容上传到根目录

3.4 Nginx 基础配置

nginx
# /www/server/panel/vhost/nginx/www.example.com.confserver {    listen 80;    server_name www.example.com;    root /www/wwwroot/web;    index index.html;    # Vue Router history 模式支持    location / {        try_files $uri $uri/ /index.html;    }    # 静态资源缓存    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {        expires 30d;        add_header Cache-Control "public, immutable";    }    # index.html 不缓存(确保用户拿到最新版本)    location = /index.html {        add_header Cache-Control "no-cache, no-store, must-revalidate";    }}

重点:try_files $uri $uri/ /index.html; 这行是 Vue Router history 模式必须的——否则刷新页面会 404。

3.5 前端更新流程

每次前端更新代码后:

bash
# 1. 本地打包npm run build# 2. 上传 dist 到服务器(覆盖旧文件)scp -r dist/* root@1.94.33.51:/www/wwwroot/web/# 3. 清除 Nginx 缓存(可选)nginx -s reload

四、Nginx 反向代理

4.1 前后端统一域名

最常用的部署方式——前端和后端用同一个域名,通过路径区分:

文本
用户请求  ├── www.example.com/          → 前端静态文件(Vue)  └── www.example.com/api/      → 反向代理到后端 8080

4.2 完整 Nginx 配置

nginx
server {    listen 80;    server_name www.example.com;    # 前端静态资源    root /www/wwwroot/web;    index index.html;    # 上传文件大小限制    client_max_body_size 50m;    # Vue Router history 模式    location / {        try_files $uri $uri/ /index.html;    }    # API 反向代理    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;        # WebSocket 支持(如需要)        proxy_http_version 1.1;        proxy_set_header Upgrade $http_upgrade;        proxy_set_header Connection "upgrade";        # 超时设置        proxy_connect_timeout 60s;        proxy_send_timeout 60s;        proxy_read_timeout 60s;    }    # 上传文件代理(如有独立的文件服务接口)    location /upload/ {        proxy_pass http://127.0.0.1:8080/upload/;        client_max_body_size 50m;    }    # 静态资源缓存    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {        expires 30d;        add_header Cache-Control "public, immutable";    }    # index.html 不缓存    location = /index.html {        add_header Cache-Control "no-cache, no-store, must-revalidate";    }    # 禁止访问隐藏文件    location ~ /\. {        deny all;    }}

proxy_pass 路径规则(容易踩坑):

nginx
# 情况 1:带斜杠 — /api/xxx 转发为 /xxxlocation /api/ {    proxy_pass http://127.0.0.1:8080/;}# 请求 /api/user/list → 后端收到 /user/list# 情况 2:不带斜杠 — /api/xxx 转发为 /api/xxxlocation /api/ {    proxy_pass http://127.0.0.1:8080;}# 请求 /api/user/list → 后端收到 /api/user/list

建议:后端 Controller 路径不含 /api 前缀时,用带斜杠的方式;含 /api 前缀时,用不带斜杠的方式。

4.3 跨域问题解决

如果前端和后端用不同域名(如 web.example.com + api.example.com),需要处理跨域:

方式 1:Nginx 添加 CORS 头(推荐)

nginx
location /api/ {    proxy_pass http://127.0.0.1:8080/;        # 添加 CORS 头    add_header Access-Control-Allow-Origin "https://web.example.com";    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";    add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";    add_header Access-Control-Allow-Credentials "true";        # 处理预检请求    if ($request_method = OPTIONS) {        return 204;    }}

方式 2:Spring Boot 全局跨域配置

java
@Configurationpublic class CorsConfig implements WebMvcConfigurer {    @Override    public void addCorsMappings(CorsRegistry registry) {        registry.addMapping("/**")                .allowedOrigins("https://web.example.com")                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")                .allowedHeaders("*")                .allowCredentials(true)                .maxAge(3600);    }}

推荐:同一域名用反向代理(无跨域问题),不同域名用 Nginx CORS 头。

4.4 Nginx 操作命令

bash
# 测试配置是否正确nginx -t# 重新加载配置(不中断服务)nginx -s reload# 启动 / 停止 / 重启nginx                    # 启动nginx -s stop            # 停止nginx -s reopen          # 重新打开日志# 查看 Nginx 进程ps aux | grep nginx# 查看错误日志tail -f /www/wwwlogs/www.example.com.error.log

五、HTTPS 与域名

5.1 域名解析

  1. 在域名服务商(阿里云/腾讯云/华为云)添加 A 记录:
    • 记录类型:A
    • 主机记录:www(或 @)
    • 记录值:1.94.33.51(服务器 IP)
  2. 等待解析生效(通常几分钟到几小时)
  3. 验证:ping www.example.com

5.2 SSL 证书申请

方式 1:宝塔一键申请 Let's Encrypt(免费,推荐)

  1. 宝塔面板 → 网站 → 设置 → SSL
  2. 选择 Let's Encrypt
  3. 勾选域名 → 申请
  4. 申请成功后开启「强制 HTTPS」

方式 2:手动申请

bash
# 安装 certbotyum install -y certbot python3-certbot-nginx# 申请证书certbot --nginx -d www.example.com -d example.com# 自动续期(Let's Encrypt 证书有效期 90 天)certbot renew --dry-run  # 测试续期crontab -e# 添加定时任务:每月 1 号自动续期0 0 1 * * /usr/bin/certbot renew --quiet

5.3 Nginx HTTPS 配置

nginx
# HTTP 跳转 HTTPSserver {    listen 80;    server_name www.example.com example.com;    return 301 https://www.example.com$request_uri;}# HTTPS 配置server {    listen 443 ssl;    server_name www.example.com;    # SSL 证书路径    ssl_certificate     /www/server/panel/vhost/cert/www.example.com/fullchain.pem;    ssl_certificate_key /www/server/panel/vhost/cert/www.example.com/privkey.pem;    # SSL 优化    ssl_session_timeout 1d;    ssl_session_cache shared:MozSSL:10m;    ssl_session_tickets off;    ssl_protocols TLSv1.2 TLSv1.3;    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;    ssl_prefer_server_ciphers off;    # HSTS(可选,告诉浏览器以后都用 HTTPS)    add_header Strict-Transport-Security "max-age=63072000" always;    # 安全头    add_header X-Frame-Options "SAMEORIGIN";    add_header X-Content-Type-Options "nosniff";    add_header X-XSS-Protection "1; mode=block";    root /www/wwwroot/web;    index index.html;    location / {        try_files $uri $uri/ /index.html;    }    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;    }}

六、数据库运维

6.1 备份策略

bash
# 手动备份mysqldump -u root -p travel_db > /www/backup/mysql/travel_db_$(date +%Y%m%d_%H%M%S).sql# 自动备份脚本cat > /www/backup/mysql_backup.sh << 'EOF'#!/bin/bash# MySQL 自动备份脚本BACKUP_DIR="/www/backup/mysql"DB_USER="root"DB_PASS="YourPassword"DB_NAME="travel_db"DATE=$(date +%Y%m%d)KEEP_DAYS=7# 创建备份目录mkdir -p $BACKUP_DIR# 备份mysqldump -u$DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz# 删除超过 KEEP_DAYS 天的备份find $BACKUP_DIR -name "${DB_NAME}_*.sql.gz" -mtime +$KEEP_DAYS -deleteecho "Backup completed: ${DB_NAME}_${DATE}.sql.gz"EOFchmod +x /www/backup/mysql_backup.sh# 添加定时任务(每天凌晨 3 点备份)crontab -e# 添加:0 3 * * * /www/backup/mysql_backup.sh >> /www/backup/mysql/backup.log 2>&1

宝塔面板方式(更简单):

  1. 宝塔 → 计划任务 → 添加任务
  2. 任务类型:Shell 脚本
  3. 任务名:MySQL 备份
  4. 执行周期:每天 3:00
  5. 脚本内容:上面的备份脚本

6.2 数据恢复

bash
# 解压并恢复gunzip < /www/backup/mysql/travel_db_20240115.sql.gz | mysql -u root -p travel_db# 或分两步gunzip /www/backup/mysql/travel_db_20240115.sql.gzmysql -u root -p travel_db < /www/backup/mysql/travel_db_20240115.sql

6.3 慢查询排查

sql
-- 查看慢查询是否开启SHOW VARIABLES LIKE 'slow_query%';SHOW VARIABLES LIKE 'long_query_time';-- 开启慢查询日志SET GLOBAL slow_query_log = 'ON';SET GLOBAL long_query_time = 2;  -- 超过 2 秒的查询记录
bash
# 查看慢查询日志tail -f /var/lib/mysql/localhost-slow.log# 使用 mysqldumpslow 分析mysqldumpslow -s t -t 10 /var/lib/mysql/localhost-slow.log# -s t 按时间排序# -t 10 显示前 10 条

6.4 常用排查命令

sql
-- 查看当前连接数SHOW STATUS LIKE 'Threads_connected';-- 查看最大连接数SHOW VARIABLES LIKE 'max_connections';-- 查看数据库大小SELECT table_schema AS '数据库',       ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS '大小(MB)'FROM information_schema.tablesWHERE table_schema = 'travel_db'GROUP BY table_schema;-- 查看表大小排序SELECT table_name,       ROUND(data_length / 1024 / 1024, 2) AS '数据(MB)',       ROUND(index_length / 1024 / 1024, 2) AS '索引(MB)',       table_rowsFROM information_schema.tablesWHERE table_schema = 'travel_db'ORDER BY data_length DESC;

七、日志管理

7.1 日志目录规划

code
/www/wwwlogs/├── www.example.com.log          # Nginx 访问日志├── www.example.com.error.log    # Nginx 错误日志└── api/    ├── application.log           # Spring Boot 应用日志    ├── error.log                 # 错误日志    ├── startup.log               # 启动日志    └── gc.log                    # GC 日志(可选)

7.2 Spring Boot 日志配置

application.yml:

yaml
logging:  file:    name: /www/wwwroot/api/logs/application.log  level:    root: INFO    com.example: DEBUG    org.springframework: WARN  logback:    rollingpolicy:      max-file-size: 100MB       # 单个日志文件最大 100MB      max-history: 30             # 保留 30 天      total-size-cap: 3GB         # 总大小上限 3GB

logback-spring.xml(更精细的控制):

xml
<?xml version="1.0" encoding="UTF-8"?><configuration>    <property name="LOG_PATH" value="/www/wwwroot/api/logs"/>        <!-- 控制台输出 -->    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">        <encoder>            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>        </encoder>    </appender>        <!-- 文件输出(按天+大小滚动) -->    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">        <file>${LOG_PATH}/application.log</file>        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">            <fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>            <maxFileSize>100MB</maxFileSize>            <maxHistory>30</maxHistory>            <totalSizeCap>3GB</totalSizeCap>        </rollingPolicy>        <encoder>            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>        </encoder>    </appender>        <!-- 错误日志单独输出 -->    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">        <file>${LOG_PATH}/error.log</file>        <filter class="ch.qos.logback.classic.filter.LevelFilter">            <level>ERROR</level>            <onMatch>ACCEPT</onMatch>            <onMismatch>DENY</onMismatch>        </filter>        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">            <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>            <maxFileSize>50MB</maxFileSize>            <maxHistory>90</maxHistory>        </rollingPolicy>        <encoder>            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>        </encoder>    </appender>        <root level="INFO">        <appender-ref ref="CONSOLE"/>        <appender-ref ref="FILE"/>        <appender-ref ref="ERROR_FILE"/>    </root></configuration>

7.3 Nginx 日志切割

宝塔面板默认已配置日志切割。手动配置方式:

bash
# /www/server/nginx/conf/nginx.conf 中已配置# log_format  main  '$remote_addr - $remote_user [$time_local] '#                    '"$request" $status $body_bytes_sent '#                    '"$http_referer" "$http_user_agent"';# 宝塔自动切割:宝塔 → 计划任务 → 检查是否有 "日志切割" 任务

7.4 日志分析常用命令

bash
# 查看应用日志最后 100 行tail -n 100 /www/wwwroot/api/logs/application.log# 实时查看日志tail -f /www/wwwroot/api/logs/application.log# 搜索错误grep "ERROR" /www/wwwroot/api/logs/application.log# 统计今天的错误数grep "$(date +%Y-%m-%d)" /www/wwwroot/api/logs/application.log | grep "ERROR" | wc -l# Nginx 访问日志:统计访问量前 10 的 IPawk '{print $1}' /www/wwwlogs/www.example.com.log | sort | uniq -c | sort -rn | head -10# Nginx 访问日志:统计 HTTP 状态码分布awk '{print $9}' /www/wwwlogs/www.example.com.log | sort | uniq -c | sort -rn# Nginx 访问日志:统计最常访问的 URLawk '{print $7}' /www/wwwlogs/www.example.com.log | sort | uniq -c | sort -rn | head -20

八、监控与告警

8.1 服务器资源监控

bash
# CPU 使用率top -bn1 | grep "Cpu(s)"# 内存使用free -h# 磁盘使用df -h# 磁盘 IOiostat -x 1 3# 网络流量iftop -n

8.2 宝塔监控

宝塔面板自带监控功能:

  1. 宝塔 → 监控 → 开启监控
  2. 可以查看 CPU、内存、磁盘、网络的实时和历史曲线
  3. 设置告警:宝塔 → 告警系统 → 设置告警阈值

推荐告警阈值:

指标 告警阈值 严重阈值
CPU > 80% > 95%
内存 > 80% > 90%
磁盘 > 80% > 90%
网络带宽 > 80% > 95%

8.3 应用监控

方式 1:Spring Boot Actuator

xml
<!-- pom.xml --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-actuator</artifactId></dependency>
yaml
# application.ymlmanagement:  endpoints:    web:      exposure:        include: health,info,metrics,env  endpoint:    health:      show-details: always
bash
# 检查应用健康状态curl http://127.0.0.1:8080/actuator/health# {"status":"UP"}# 查看 JVM 内存使用curl http://127.0.0.1:8080/actuator/metrics/jvm.memory.used

注意:Actuator 端点不要暴露给公网,只通过内网或 Nginx 代理访问。

8.4 简单告警脚本

bash
#!/bin/bash# /www/backup/alert.sh — 简单资源告警# CPU 使用率CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)CPU_INT=${CPU_USAGE%.*}# 磁盘使用率DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | cut -d'%' -f1)# 内存使用率MEM_USAGE=$(free | awk '/Mem/ {printf "%.0f", $3/$2 * 100}')# 告警函数(可对接企业微信/钉钉机器人)alert() {    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ALERT: $1"    # curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx' \    #   -H 'Content-Type: application/json' \    #   -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$1\"}}"}if [ $CPU_INT -gt 80 ]; then    alert "CPU 使用率过高: ${CPU_USAGE}%"fiif [ $DISK_USAGE -gt 80 ]; then    alert "磁盘使用率过高: ${DISK_USAGE}%"fiif [ $MEM_USAGE -gt 80 ]; then    alert "内存使用率过高: ${MEM_USAGE}%"fi

九、日常运维操作

9.1 更新后端版本

bash
#!/bin/bash# /www/wwwroot/api/deploy.sh — 后端更新脚本APP_NAME="my-project"APP_DIR="/www/wwwroot/api"BACKUP_DIR="${APP_DIR}/backup"DATE=$(date +%Y%m%d_%H%M%S)echo "===== 开始部署 ${APP_NAME} ====="# 1. 停止服务echo "[1/5] 停止服务..."systemctl stop api.service# 2. 备份旧版本echo "[2/5] 备份旧版本..."mkdir -p $BACKUP_DIRif [ -f "${APP_DIR}/${APP_NAME}.jar" ]; then    cp "${APP_DIR}/${APP_NAME}.jar" "${BACKUP_DIR}/${APP_NAME}_${DATE}.jar"    echo "  备份到: ${BACKUP_DIR}/${APP_NAME}_${DATE}.jar"fi# 3. 上传新版本(此处假设新 jar 已上传到 APP_DIR)# 如果用 scp 上传,在上传完成后执行此脚本echo "[3/5] 新版本已就位"# 4. 启动服务echo "[4/5] 启动服务..."systemctl start api.service# 5. 检查健康echo "[5/5] 检查健康状态..."sleep 10if curl -s http://127.0.0.1:8080/actuator/health | grep -q "UP"; then    echo "✅ 部署成功!服务已启动"else    echo "❌ 服务启动失败,请检查日志"    tail -50 ${APP_DIR}/logs/application.logfi# 清理超过 10 个的旧备份cd $BACKUP_DIR && ls -t ${APP_NAME}_*.jar | tail -n +11 | xargs -r rmecho "===== 部署完成 ====="

9.2 回滚

bash
#!/bin/bash# 回滚到上一个版本APP_NAME="my-project"APP_DIR="/www/wwwroot/api"BACKUP_DIR="${APP_DIR}/backup"# 查看可用备份echo "可用备份版本:"ls -lt $BACKUP_DIR/${APP_NAME}_*.jar# 选择要回滚的版本read -p "输入要回滚的版本文件名: " VERSION_FILE# 停止服务systemctl stop api.service# 回滚cp "${BACKUP_DIR}/${VERSION_FILE}" "${APP_DIR}/${APP_NAME}.jar"# 启动服务systemctl start api.serviceecho "已回滚到 ${VERSION_FILE}"

9.3 更新前端版本

bash
#!/bin/bash# 前端更新脚本WEB_DIR="/www/wwwroot/web"BACKUP_DIR="/www/wwwroot/web_backup"DATE=$(date +%Y%m%d_%H%M%S)echo "===== 开始更新前端 ====="# 1. 备份旧文件echo "[1/3] 备份旧文件..."mkdir -p $BACKUP_DIRtar -czf "${BACKUP_DIR}/web_${DATE}.tar.gz" -C $WEB_DIR .# 2. 清理旧文件(保留 .git 等隐藏文件)echo "[2/3] 清理旧文件..."rm -rf ${WEB_DIR}/*# 3. 解压新文件(假设新 dist.tar.gz 已上传)echo "[3/3] 部署新文件..."tar -zxvf /www/wwwroot/dist.tar.gz -C $WEB_DIR# 4. 重载 Nginxnginx -s reloadecho "✅ 前端更新完成"# 清理超过 5 个的备份cd $BACKUP_DIR && ls -t web_*.tar.gz | tail -n +6 | xargs -r rm

9.4 磁盘清理

bash
# 查看磁盘使用情况df -h# 找出大文件(大于 100M)find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null# 清理系统日志journalctl --vacuum-size=500M    # systemd 日志只保留 500M# 清理宝塔日志rm -f /www/wwwlogs/*.log.1rm -f /www/server/nginx/logs/*.log.1# 清理旧备份find /www/backup -name "*.gz" -mtime +30 -delete  # 删除 30 天前的备份# 清理 Docker(如使用 Docker)docker system prune -a -f# 清理 npm/pnpm 缓存npm cache clean --force

十、常见问题排查

10.1 接口返回 502 Bad Gateway

原因:Nginx 找不到后端服务(后端没启动或端口不对)

bash
# 排查步骤:# 1. 检查后端是否在运行systemctl status api.serviceps aux | grep java# 2. 检查端口是否监听netstat -tlnp | grep 8080lsof -i:8080# 3. 检查 Nginx 配置中的 proxy_pass 地址是否正确grep proxy_pass /www/server/panel/vhost/nginx/*.conf# 4. 查看后端启动日志tail -100 /www/wwwroot/api/logs/application.log

10.2 接口返回 504 Gateway Timeout

原因:后端响应太慢,Nginx 等待超时

bash
# 排查步骤:# 1. 查看后端处理时间grep "request completed" /www/wwwroot/api/logs/application.log# 2. 检查是否有慢 SQLtail -f /var/lib/mysql/localhost-slow.log# 3. 临时增加 Nginx 超时时间# location /api/ {#     proxy_read_timeout 120s;  # 默认 60s# }

10.3 前端页面白屏

原因:JS/CSS 文件加载失败或路由配置问题

bash
# 排查步骤:# 1. 浏览器 F12 → Console 查看报错# 2. F12 → Network 查看哪些资源加载失败(404)# 3. 检查 Nginx root 路径是否正确ls -la /www/wwwroot/web/# 4. 检查 index.html 中的资源路径cat /www/wwwroot/web/index.html# 5. 如果是子目录部署,检查 vite.config.js 的 base 配置

10.4 内存溢出(OOM)

bash
# 症状:Java 进程突然消失,日志中有 OutOfMemoryError# 排查步骤:# 1. 查看系统日志dmesg | grep -i "out of memory"# 2. 查看应用日志grep "OutOfMemoryError" /www/wwwroot/api/logs/application.log# 3. 如果有 heapdump 文件,用 jvisualvm 或 MAT 分析ls -la /www/wwwroot/api/logs/*.hprof# 临时解决:增加 JVM 堆内存# -Xmx2g → -Xmx3g# 根本解决:排查内存泄漏(看 heapdump 中哪些对象占用最多)

10.5 端口被占用

bash
# 查看端口占用lsof -i:8080# 或netstat -tlnp | grep 8080# 杀掉占用进程kill -15 <PID># 如果杀不掉kill -9 <PID>

10.6 跨域问题

症状:浏览器控制台报 CORS policy 错误

解决:

  1. 优先用 Nginx 反向代理统一域名(第四章方案)
  2. 如需跨域,在后端添加跨域配置或 Nginx 添加 CORS 头
  3. 注意:Nginx CORS 和 Spring Boot CORS 不要同时配置,否则会重复添加头导致报错

10.7 数据库连接数过多

sql
-- 查看当前连接数SHOW STATUS LIKE 'Threads_connected';-- 查看最大连接数SHOW VARIABLES LIKE 'max_connections';-- 查看所有连接SHOW PROCESSLIST;-- 杀掉空闲连接KILL <ID>;-- 修改最大连接数(临时)SET GLOBAL max_connections = 500;-- 永久修改:my.cnf-- [mysqld]-- max_connections = 500

10.8 Redis 连接失败

bash
# 检查 Redis 是否运行systemctl status redisredis-cli ping  # 应返回 PONG# 检查密码redis-cli -a YourPassword ping# 检查端口lsof -i:6379# 常见原因:# 1. 密码不对(application.yml 中的密码与 redis.conf 不一致)# 2. Redis 未启动# 3. bind 配置限制(redis.conf 中 bind 127.0.0.1)

十一、运维脚本合集

11.1 一键完整部署脚本

bash
#!/bin/bash# /www/wwwroot/deploy_all.sh — 一键部署前后端echo "===== 一键部署开始 ====="# 配置APP_JAR="my-project.jar"APP_DIR="/www/wwwroot/api"WEB_DIR="/www/wwwroot/web"UPLOAD_DIR="/www/wwwroot/upload"# 1. 部署后端echo "[1/4] 部署后端..."if [ -f "${UPLOAD_DIR}/${APP_JAR}" ]; then    systemctl stop api.service    cp "${UPLOAD_DIR}/${APP_JAR}" "${APP_DIR}/${APP_JAR}"    systemctl start api.service    echo "  ✅ 后端已更新"else    echo "  ⚠️ 未找到 ${APP_JAR},跳过后端更新"fi# 2. 部署前端echo "[2/4] 部署前端..."if [ -f "${UPLOAD_DIR}/dist.tar.gz" ]; then    rm -rf ${WEB_DIR}/*    tar -zxvf "${UPLOAD_DIR}/dist.tar.gz" -C ${WEB_DIR}    echo "  ✅ 前端已更新"else    echo "  ⚠️ 未找到 dist.tar.gz,跳过前端更新"fi# 3. 重载 Nginxecho "[3/4] 重载 Nginx..."nginx -t && nginx -s reloadecho "  ✅ Nginx 已重载"# 4. 健康检查echo "[4/4] 健康检查..."sleep 8if curl -s http://127.0.0.1:8080/actuator/health | grep -q "UP"; then    echo "  ✅ 后端健康检查通过"else    echo "  ❌ 后端健康检查失败"    tail -20 ${APP_DIR}/logs/application.logfi# 清理上传文件rm -f ${UPLOAD_DIR}/${APP_JAR} ${UPLOAD_DIR}/dist.tar.gzecho "===== 部署完成 ====="

11.2 服务器状态检查脚本

bash
#!/bin/bash# /www/wwwroot/check_status.sh — 服务器状态检查echo "========== 服务器状态 =========="echo "时间: $(date)"echo ""echo "--- 系统信息 ---"echo "主机名: $(hostname)"echo "运行时间: $(uptime -p)"echo "负载: $(cat /proc/loadavg | awk '{print $1, $2, $3}')"echo ""echo "--- CPU ---"CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}')echo "CPU 使用率: ${CPU_USAGE}%"echo ""echo "--- 内存 ---"free -h | grep -v "Swap"echo ""echo "--- 磁盘 ---"df -h | grep -v "tmpfs"echo ""echo "--- Java 进程 ---"if ps aux | grep -v grep | grep java > /dev/null; then    ps aux | grep java | grep -v grep | awk '{print "PID: "$2" | CPU: "$3"% | MEM: "$4"% | "$11" "$12}'else    echo "❌ Java 进程未运行"fiecho ""echo "--- MySQL ---"if systemctl is-active mysql > /dev/null 2>&1; then    echo "✅ MySQL 运行中"else    echo "❌ MySQL 未运行"fiecho ""echo "--- Redis ---"if systemctl is-active redis > /dev/null 2>&1; then    echo "✅ Redis 运行中"else    echo "❌ Redis 未运行"fiecho ""echo "--- Nginx ---"if systemctl is-active nginx > /dev/null 2>&1; then    echo "✅ Nginx 运行中"else    echo "❌ Nginx 未运行"fiecho ""echo "--- 端口监听 ---"echo "80:   $(lsof -i:80 -sTCP:LISTEN > /dev/null 2>&1 && echo '✅' || echo '❌')"echo "443:  $(lsof -i:443 -sTCP:LISTEN > /dev/null 2>&1 && echo '✅' || echo '❌')"echo "8080: $(lsof -i:8080 -sTCP:LISTEN > /dev/null 2>&1 && echo '✅' || echo '❌')"echo "3306: $(lsof -i:3306 -sTCP:LISTEN > /dev/null 2>&1 && echo '✅' || echo '❌')"echo "6379: $(lsof -i:6379 -sTCP:LISTEN > /dev/null 2>&1 && echo '✅' || echo '❌')"

11.3 定时任务配置

bash
# 编辑定时任务crontab -e# 添加以下任务:# ┌──────── 分钟 (0-59)# │ ┌────── 小时 (0-23)# │ │ ┌──── 日 (1-31)# │ │ │ ┌── 月 (1-12)# │ │ │ │ ┌ 星期 (0-7, 0和7都是周日)# │ │ │ │ │# MySQL 每日备份(凌晨 3 点)0 3 * * * /www/backup/mysql_backup.sh >> /www/backup/mysql/backup.log 2>&1# 磁盘清理(每周日凌晨 4 点)0 4 * * 0 find /www/backup -name "*.gz" -mtime +30 -delete# 健康检查(每 5 分钟)*/5 * * * * /www/wwwroot/check_status.sh > /www/wwwlogs/health_check.log 2>&1# 资源告警(每 10 分钟)*/10 * * * * /www/backup/alert.sh >> /www/wwwlogs/alert.log 2>&1# Nginx 日志切割(每天凌晨 0 点)0 0 * * * /www/server/nginx/sbin/nginx -s reopen

十二、安全加固

12.1 防火墙配置

bash
# 查看防火墙状态systemctl status firewalld# 开放必要端口firewall-cmd --permanent --add-port=80/tcpfirewall-cmd --permanent --add-port=443/tcpfirewall-cmd --permanent --add-port=22/tcpfirewall-cmd --permanent --add-port=8888/tcp  # 宝塔(建议改端口)# 开放后重载firewall-cmd --reload# 查看已开放的端口firewall-cmd --list-ports

12.2 SSH 安全加固

bash
# 修改 SSH 配置vim /etc/ssh/sshd_config# 修改以下配置:Port 22222                    # 修改默认端口(22 → 其他)PermitRootLogin no            # 禁止 root 直接登录(用普通用户 + sudo)PasswordAuthentication no     # 禁用密码登录(用密钥登录)MaxAuthTries 3                # 最大认证尝试次数# 重启 SSHsystemctl restart sshd# 使用密钥登录(在本地生成密钥对)ssh-keygen -t rsa -b 4096ssh-copy-id -p 22222 user@1.94.33.51

12.3 MySQL 安全

sql
-- 修改 root 密码ALTER USER 'root'@'localhost' IDENTIFIED BY 'StrongPassword123!';-- 删除匿名用户DELETE FROM mysql.user WHERE User='';-- 删除测试数据库DROP DATABASE IF EXISTS test;-- 只允许本地访问-- 确认 root 只能从 localhost 连接SELECT User, Host FROM mysql.user WHERE User='root';-- 如果有 root@'%',删除:DROP USER 'root'@'%';-- 创建专用业务用户CREATE USER 'travel_user'@'localhost' IDENTIFIED BY 'TravelUserPassword123!';GRANT SELECT, INSERT, UPDATE, DELETE ON travel_db.* TO 'travel_user'@'localhost';FLUSH PRIVILEGES;

12.4 Redis 安全

bash
# 修改 redis.confvim /www/server/redis/redis.conf# 关键配置:bind 127.0.0.1              # 只允许本地访问requirepass YourRedisPass   # 设置密码port 6379                   # 建议改端口protected-mode yes          # 开启保护模式# 重启 Redissystemctl restart redis

12.5 Nginx 安全头

nginx
# 在 server 块中添加:add_header X-Frame-Options "SAMEORIGIN";           # 防止点击劫持add_header X-Content-Type-Options "nosniff";        # 防止 MIME 嗅探add_header X-XSS-Protection "1; mode=block";        # XSS 过滤add_header Referrer-Policy "strict-origin-when-cross-origin";  # Referer 控制

12.6 Fail2Ban 防暴力破解

bash
# 安装yum install -y epel-releaseyum install -y fail2ban# 配置cat > /etc/fail2ban/jail.local << 'EOF'[sshd]enabled = trueport = 22222maxretry = 3findtime = 600bantime = 3600EOF# 启动systemctl enable fail2bansystemctl start fail2ban# 查看封禁状态fail2ban-client status sshd

附录:完整部署 Checklist

首次部署

文本
□ 域名已解析到服务器 IP□ 服务器安全组已放行 80/443/22 端口□ 宝塔面板已安装,端口已修改□ JDK 已安装,java -version 正常□ MySQL 已安装,root 密码已修改,业务库已创建□ Redis 已安装,密码已设置□ Nginx 已安装□ 后端 jar 已上传,application.yml 已配置□ systemd 服务已创建并启动□ 后端健康检查通过(curl localhost:8080/actuator/health)□ 前端 dist 已上传□ Nginx 反向代理已配置□ SSL 证书已申请并配置□ HTTP 已跳转 HTTPS□ 防火墙已配置□ SSH 已加固□ MySQL/Redis 安全已加固□ 数据库备份定时任务已配置□ 日志切割已配置

每次更新

文本
□ 后端:停止服务 → 备份旧 jar → 替换新 jar → 启动 → 健康检查□ 前端:备份旧 dist → 替换新 dist → Nginx reload□ 测试核心功能是否正常□ 保留旧版本备份(至少保留最近 3 个版本)

每周检查

文本
□ 检查磁盘空间(df -h)□ 检查内存使用(free -h)□ 检查 Nginx 错误日志□ 检查应用错误日志□ 检查数据库备份是否正常□ 检查 SSL 证书有效期

总结

Java + Vue 项目的部署和运维,核心就四件事:

  1. 环境准备到位——JDK、MySQL、Redis、Nginx 版本对齐,安全组配置正确
  2. 前后端部署规范——后端用 systemd 管理,前端用 Nginx 托管,配置文件外部化
  3. Nginx 反向代理是枢纽——统一域名、统一入口,解决跨域,配置 HTTPS
  4. 运维日常化——备份、监控、日志、告警,缺一不可

部署不是一次性工作,而是一个持续的过程。好的运维习惯(备份、监控、文档)比一次完美的部署更重要。 出了问题不要慌,按"网络 → 服务 → 应用 → 日志"的顺序排查,80% 的问题能在 10 分钟内定位。

前端、后端、部署、客服都是我一个人。写这里的东西都是真踩过的坑,不是抄来的教程。