兄弟先说个我自己的经历。上个月帮一个朋友把这套微人事 vhr 从他的 Windows 开发机上搬到了阿里云轻量服务器本来以为就是个java -jar的事结果从前端跨域到数据库认证、再到 Nginx 静态资源缓存整整折腾了一个周末。回头复盘的时候发现网上关于 vhr 部署的教程大多是开发环境跑通级别真正从零开始配一台干净服务器的完整流程反而没人写清楚。这篇文章就是把我那次部署的全过程、中间踩过的坑、最后稳定运行的配置方案原原本本整理出来。内容面向的是拿到 vhr 源码但不知道怎么往服务器上放的读者也适合第一次接触前后端分离项目部署的新手。我会把每一环节的为什么这么做也讲明白而不是给你一串复制粘贴的命令就完事。先说结论vhr 的部署难度不算高但它的价值恰恰在于让你完整走一遍 Spring Boot Vue MySQL Redis Nginx 的真实链路。你把这个流程打通了以后部署任何类似结构的前后端分离项目基本就是换个姿势重复一遍。1. 部署前先想清楚vhr 跑起来需要哪几块拼图1.1 项目本质前后端分离的经典结构微人事 vhr 是一个相当标准的前后端分离项目。后端是 Spring Boot 写的 REST 接口一端连着数据库做持久化另一端把 JSON 数据抛给前端前端是 Vue 2 的项目加载完之后在浏览器里渲染页面通过 HTTP 请求去调后端接口。很多第一次接触这个项目的人会犯一个认知错误以为把后端 jar 包丢到服务器上运行就算部署完了。实际上一台生产服务器上要装的东西是这么几块JDK跑 Spring Boot 后端的基本环境MySQLvhr 的数据全存在这里Redis项目里部分功能依赖的缓存组件Node.js npm用来构建前端静态资源Nginx托管前端页面同时把 API 请求反向代理到后端端口Maven打包后端代码成可执行 jar这几样的版本选择、安装顺序、配置方式任何一个环节出问题都会让部署过程变得非常折磨人。我建议你按我下面的顺序走原因后面会逐步解释先装底层数据服务MySQL、Redis再装构建工具JDK、Maven、Node最后装 Nginx 做接入层。1.2 拓扑简单但链路不简单如果你只有一台 2 核 4G 的云服务器完全够用。部署完成后的运行拓扑是这样的浏览器 │ ▼ Nginx80端口 ├── 静态页面前端 dist 目录 └── /api 等路径 → 反向代理 → Spring Boot8080端口 ├── MySQL3306 └── Redis6379每一次页面交互浏览器先去 Nginx 拿静态文件然后 JS 代码再发起 API 请求给 NginxNginx 判断路径带/api前缀就转发给 8080 端口的 Spring Boot 应用后端再去数据库和缓存里存取数据。理解这个数据流非常重要因为后面遇到页面能打开但登录失败接口 404 但静态资源正常这类问题时靠这张图就能快速定位是哪个环节断了。1.3 我选的操作系统与版本基线我这次用的是 CentOS 7.9 系统JDK 用的 1.8后面细说为什么MySQL 8.0Redis 6.xNode 16.xNginx 1.20。这套组合我在部署前和部署后都验证过兼容性没有问题。如果你手头是 Ubuntu 22.04 或者 Debian命令换成apt即可原理完全一致Windows 服务器则建议直接放弃手动部署老老实实装个 Docker 或者虚拟机。2. 环境安装看着简单但每一步都有隐藏的坑2.1 JDK 选型别纠结版本但别装错发行版vhr 后端的 Spring Boot 版本我用的是 2.x 系列这个系列对 JDK 8 的兼容性最好也最稳妥。所以别一上来就上 JDK 17 或 21虽然理论上能跑但会有一些依赖和反射相关的兼容问题生产环境没必要冒这个险。安装命令很简单yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel java -version但我遇到的真坑是官方 OpenJDK 和某些发行版自带的 JDK在 SSL 加密强度上表现不一样。vhr 里如果要连外网邮箱服务发通知邮件JDK 8 自带的加密策略文件可能不支持 TLS 1.2 以上的高级加密套件导致报了SSLHandshakeException。解决办法是下载 Java Cryptography Extension 的 unlimited strength JAR 包覆盖到 JDK 的lib/security目录。如果你部署时不涉及邮件功能这步可以跳过但知道有这回事总没坏处。检查安装是否成功时不要只看java -version要顺手验证一下JAVA_HOME环境变量echo $JAVA_HOME如果输出为空在/etc/profile末尾加两行再source /etc/profile。这一步不做后面 Maven 打包时会找不到 JDK。2.2 MySQL 8.0 初始化密码策略和认证插件是重灾区数据库安装我直接用官方 yum 源wget https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm rpm -ivh mysql80-community-release-el7-3.noarch.rpm yum install -y mysql-community-server systemctl start mysqld装完之后MySQL 8.0 会自动生成一个临时密码藏在日志里grep temporary password /var/log/mysqld.log用这个密码登录后第一件事是改密码。但 8.0 的默认密码策略是validate_password强校验必须包含大小写字母、数字和特殊字符。很多人在这儿卡住我推荐直接设一个类似Admin2024这种格式的强密码后续业务连接就用这个。真正更深的一个坑在认证插件。MySQL 8.0 默认用的是caching_sha2_password而 vhr 后端项目里的驱动包如果是老版本 MySQL Connector/J5.x 系列根本不认这个插件连接时会报Unable to load authentication plugin caching_sha2_password。解决方式两种要么升级驱动要么给 vhr 的数据库账号指定老插件CREATE USER vhr% IDENTIFIED WITH mysql_native_password BY 你的密码; GRANT ALL PRIVILEGES ON vhr.* TO vhr%; FLUSH PRIVILEGES;我建议直接用第二种因为改驱动涉及 pom.xml容易引入新的依赖冲突。生产库记得不要用 root 账号连应用权限收敛到vhr这一个库就够了。2.3 Redis不建议裸奔但配置也别过度vhr 里 Redis 的用途主要是缓存一些高频读的数据和会话相关的临时信息。安装最简单yum install -y redis systemctl start redis默认配置下 Redis 只监听127.0.0.1没有密码。如果你后端也跑在同一台机器上这个配置其实够用毕竟外部访问不到。但如果你跟我一样把 Redis 和后端分开了或者云服务器的安全组没挡住公网端口那就必须设置密码vim /etc/redis.conf # 找到 requirepass 这一行去掉注释并改成 requirepass your-redis-password改完密码之后记得同步修改 vhr 后端配置文件里的 Redis 连接信息不然启动时连接被拒。2.4 Maven 和 Node两个构建工具的镜像加速Maven 装好后默认的中央仓库地址在国内下载依赖非常慢。我直接在~/.m2/settings.xml里配置了阿里云镜像mirrors mirror idaliyunmaven/id mirrorOfcentral/mirrorOf nameAliyun Maven Mirror/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrorsNode 这边同样npm 默认源慢到怀疑人生直接换淘宝源npm config set registry https://registry.npmmirror.com这一步属于磨刀不误砍柴工忘了配置的话npm install可能要等十分钟起步还会偶尔报超时错误心态直接崩。3. 数据库脚本与后端配置打开生产环境的大门3.1 把 SQL 脚本导入 MySQL 的正确姿势vhr 源码里通常带一个数据库初始化脚本文件名类似vhr.sql或者db_vhr.sql。这个脚本不仅建表还会插入初始的管理员账号和基础部门数据所以不能跳过。导入之前建议先用vim打开看一眼文件头确认里面没有你不想执行的危险操作也确认目标数据库名称和脚本里写的用的是不是同一个。很多项目在本机开发时用的是vhr这个库名你如果建了别的库名直接导入会报Unknown database。执行导入的命令mysql -u root -p mysql CREATE DATABASE IF NOT EXISTS vhr DEFAULT CHARACTER SET utf8mb4; mysql USE vhr; mysql SOURCE /root/vhr.sql;跑完可以验证一下关键表是否建好SHOW TABLES;正常情况下能看到一堆hr、menu、role、employee这类表名。要是表数量明显少于预期多半是脚本中间某条命令报错中断了回头仔细看日志。3.2 后端配置文件里的灵魂几项vhr 后端的核心配置在application.yml里。开发环境默认配置通常能直接用但生产环境部署时有几项必须改数据源这一项要检查 URL、用户名、密码spring: datasource: url: jdbc:mysql://localhost:3306/vhr?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: vhr password: 你的密码注意 URL 里的serverTimezone参数。如果你服务器时区设置不对或没写这个参数数据库时间会差 8 小时到时候考勤、工资记录里的日期全乱排查起来特别隐蔽。Redis 配置对应改成你实际设置的密码spring: redis: host: localhost port: 6379 password: 你的redis密码最后是 JWT 相关的配置。vhr 的登录态是 JWT 方案配置里会有jwt.secret或类似密钥字段。生产环境上强烈建议把密钥改成你自己的随机长字符串别用项目默认值否则等于给别人发了万能令牌。生成随机密钥可以用openssl rand -base64 64。3.3 打包后端 jar 并启动验证确认完配置后进入后端目录执行打包mvn clean package -DskipTests如果报依赖下载错误基本就是 Maven 镜像问题回上一步查 settings.xml。打包成功后target目录下会有一个xxx.jar文件。启动时我建议先用前台方式试跑一次方便看启动日志java -jar xxx.jar等日志出现Started字样后先用本机 curl 验证接口通不通curl http://localhost:8080/如果直接映射了上下文路径记得带上。确认能通再CtrlC停掉改用后台方式跑nohup java -jar -Xms256m -Xmx512m xxx.jar vhr.log 21 -Xms和-Xmx这两个 JVM 参数值得说一下2G 内存的服务器给后端 512M 堆就够用给太大反而跟操作系统抢内存导致整机 swap 频繁、卡顿。内存够大的机器也别上来就-Xmx2g按需调整才是关键vhr 作为一个中型单体应用512M 到 1G 是个合理区间。4. 前端构建与 Nginx 接入让浏览器能找到界面4.1 前端构建npm install 之前的三个检查项vhr 前端项目在源码里的前端目录中安装依赖之后构建npm install npm run build但在执行之前必须检查前端代码里 API 请求的根路径是怎么写的。这里有一个典型的坑如果前端代码里把接口地址硬编码成了http://localhost:8080那么不管你 Nginx 怎么配用户浏览器根本访问不到localhost因为localhost指的是用户自己的电脑。正确做法是把请求地址改成相对路径/api或者在构建时通过环境变量注入服务器实际地址。vhr 项目基于 Vue CLI最省事的方式是看它的.env.development和.env.production文件生产环境配置里通常已经把 API 地址指向了相对路径你只要确认一遍就行。构建产物会在dist目录把整个目录拷到服务器上的/usr/share/nginx/html/vhr或者你指定的任意路径下即可Nginx 配置会指向这里。4.2 一份可用的 Nginx 配置示例我的 Nginx 配置如下直接可抄server { listen 80; server_name 你的域名或IP; client_max_body_size 20m; location / { root /usr/share/nginx/html/vhr; index index.html; 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_read_timeout 60s; } }这里两个细节要重点说。try_files $uri $uri/ /index.html是保障前端路由能正常刷新的关键。Vue 项目是单页应用路由切换是靠 JS 控制 URL 的如果用户直接访问/employee这种二级路由Nginx 会去找对应的物理文件找不到就 404加上这一行才会回退到首页让 Vue 自己解析路由。proxy_pass http://127.0.0.1:8080/;最后的斜杠不能省。带斜杠意味着把/api/前缀剥掉再转发后端。比如请求GET /api/hr/hello后端实际收到的是GET /hr/hello。到底要不要剥掉取决于后端 Controller 里有没有统一加/api前缀。vhr 项目的接口路径没有/api开头所以必须剥掉否则 Spring Boot 会找不到映射返回 404。4.3 部署后最常见的两类问题及排查链路配置完成后nginx -t检查语法、systemctl reload nginx重载。然后打开浏览器输入服务器 IP最常见的两类问题页面能打开但登录时报接口 404。这类问题的排查思路是先打开浏览器开发者工具的 Network 面板看具体是哪个请求返回 404接着在服务器上直接请求后端验证curl http://127.0.0.1:8080/你的接口路径如果本机请求接口是通的那就是 Nginx 代理的路径和你后端 Controller 实际路径对不上回到上一节看斜杠问题。如果本机请求都 404那就是后端没启动成功回去看 vhr.log。接口返回 502 Bad Gateway。这通常是 Nginx 连不上后端端口。先确认后端进程还活着ps -ef | grep java然后确认端口监听正常netstat -tlnp | grep 8080如果后端没启动大概率是启动日志里某一步报错退出了把nohup输出的 vhr.log 翻出来看。如果端口在但 Nginx 还是 502检查 SELinux 是不是拦截了代理访问setenforce 0CentOS 默认 SELinux 开启时Nginx 向外发起的连接会被限制这一条目前能解决 70% 的诡异 502 问题。5. 验证、守护与后续维护部署完成只是开始5.1 按核心业务流做一次完整的功能验证部署完成后别只看登录页能打开就说部署成功了。我习惯按一条真实业务链路来冒烟测试用初始管理员账号登录系统确认能进主界面打开员工管理页确认列表能加载出数据尝试新增一条员工记录保存成功后再修改和删除确认写操作正常打开部门管理拖动树节点调整层级确认数据库变更生效退出登录再登录一次确认 JWT 令牌没有把状态搞坏这套流程走下来前端构建、后端接口、数据库读写、Redis 缓存基本都覆盖到了。只要有一条不通背后的配置问题你按第四节的排查思路基本能定位到。5.2 用 systemd 把 Java 进程管起来nohup java -jar这种方式适合临时跑但服务器重启后不会自动拉起来。生产环境建议写一个 systemd service 文件[Unit] Descriptionvhr Backend Server Afternetwork.target mysql.service redis.service [Service] Typesimple Userroot ExecStart/usr/bin/java -Xms256m -Xmx512m -jar /opt/vhr/vhr-server.jar Restarton-failure RestartSec10 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target保存到/etc/systemd/system/vhr.service然后systemctl daemon-reload systemctl enable --now vhr以后重启服务器后端会自动跟着拉起来日志统一走journalctl -u vhr查看比 nohup 输出的日志文件清爽得多。5.3 我踩过的三个容易再犯的坑第一个坑时间同步。我部署完发现所有考勤记录的时间都对了但系统里显示的当前登录时间比实际慢了 8 小时。排查一圈发现不是代码问题是云服务器的系统时区默认是 UTC配了 MySQL 的serverTimezone只管 JDBC 连接层面系统本身的date命令都差着 8 小时。一劳永逸的办法是timedatectl set-timezone Asia/Shanghai第二个坑端口占用。后端启动报Port 8080 was already in use时别犹豫直接netstat -tlnp | grep 8080找出是哪个进程占用的八成是之前调试时忘了关的 Java 进程。找到后kill掉再重新启动有时候会有多条 Java 进程互相干扰全部干掉重启最干净。第三个坑静态资源更新后浏览器还是老页面。前端改完重新npm run build并覆盖 dist 目录后用户浏览器里还是旧页面。这是因为 Nginx 对静态文件做了缓存或者浏览器缓存了 index.html。处理方式两种一是 Nginx 配置里给 js/css 文件加带 hash 的文件名Vue 构建默认已经带 hash二是客户端的强缓存可以在 Nginx 里设置Cache-Control规则只对带 hash 的资源开缓存index.html永远走 no-cache这样每次发版后用户刷新就是新版。5.4 延伸如果服务器配置了 Docker可以考虑容器化部署如果你的服务器上已经习惯用 Docker 管理服务vhr 也可以容器化。思路是把 MySQL、Redis、后端、前端分别容器化或用 docker-compose 编排。后端 Dockerfile 大致如下FROM openjdk:8-jre-alpine WORKDIR /app COPY vhr-server.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]前端打包之后用一个带 Nginx 的基础镜像把 dist 拷进去配置文件挂载进去。容器化的好处是环境隔离迁移服务器时基本不用重新踩环境安装的坑。但坏处是调试没有裸机直观日志收集、参数调整对新手来说学习成本更高。我的建议是第一次部署 vhr 务必走一遍裸机流程把每个组件是什么、干什么、怎么配都搞清楚再考虑容器化否则出了问题你可能连日志都不知道去哪里看。最后再分享一个我自己真实的体会vhr 这种项目在开发机上跑通和在生产服务器上跑通是完完全全两码事。开发机上有 IDE 帮你处理环境变量有本地数据库随便折腾遇到问题还能随时断点调试。到了服务器上一切都要靠配置、日志、命令行去解决你会被迫去理解项目里每一个组件之间的关系。这个过程虽然折腾但走完一遍之后你会对整个后端服务的运行机制有非常直观的认知以后再部署任何东西心里都有一张清晰的链路图。遇到项目跑不起来别急着怀疑代码有问题先从环境、配置、路径这三样最基础的地方查起——十次里有八次问题就藏在这些不起眼的环节里。