云计算百科
云计算领域专业知识百科平台

Java Snowy 框架生产环境安全部署全流程(服务器篇)

文章目录

    • 一、写在前面
      • 1.1 项目简介
      • 1.2 源码获取与私有仓库迁移
      • 1.3 整体架构(安全部署版)
      • 1.4 服务器环境说明
      • 1.5 所需环境清单
    • 二、设计思路与目录规划
      • 2.1 为什么不能直接用 root?
      • 2.2 用户权限设计
      • 2.3 目录结构设计
      • 2.4 自动化与未来扩展
    • 三、环境准备
      • 3.1 创建系统用户
      • 3.2 安装软件环境
      • 3.3 开发工具与 Git 配置
        • 3.3.1 配置 SSH 密钥(用于拉取私有仓库代码)
        • 3.3.2 Git 常用配置
        • 3.3.3 安装 tig(Git 日志查看工具)
        • 3.3.4 解决 SSH 连接警告(服务器密钥变更)
      • 3.4 配置用户权限
    • 四、后端部署
      • 4.1 克隆后端代码
      • 4.2 配置Maven镜像
      • 4.3 导入SQL到数据库
      • 4.4 修改后端配置文件
      • 4.5 编译和打包
        • 4.5.1 编译安装所有模块
        • 4.5.2 编译阶段常见问题及解决方案
        • 4.5.3 打包启动模块
        • 4.5.4 打包启动模块常见问题
      • 4.6 创建软链接
      • 4.7 systemd服务
        • 4.7.1 创建systemd服务
        • 4.7.2 systemctl 常用命令说明
      • 4.8 各类错误问题排查和处理
        • 4.8.1 查看错误日志的方法
        • 4.8.2 问题列表及解决方案
        • 4.8.3 通用排查流程
      • 4.9 后端 Nginx 配置
        • 4.9.1 安装Nginx
        • 4.9.2 配置 Nginx 虚拟主机
        • 4.9.3 后端 Nginx 配置
    • 五、前端部署
      • 5.1 克隆前端代码
      • 5.2 配置环境变量
      • 5.3 安装依赖并构建
      • 5.4 部署静态文件到 Nginx 目录
      • 5.5 前端 Nginx 配置
      • 5.6 验证部署结果
    • 六、后端环境变量配置
      • 6.1 配置文件规划
      • 6.2 修改application.properties
      • 6.3 创建本地开发配置文件
      • 6.4 创建远程测试环境配置文件
      • 6.5 创建配置模板
      • 6.6 设置 `.gitignore`
      • 6.7 本地 IntelliJ IDEA 运行配置
        • 6.7.1 使用默认配置运行
        • 6.7.2 自定义配置运行
        • 6.7.3 本地运行效果验证
      • 6.8 服务器部署时指定 Profile
        • 6.8.1 通过参数指定profile
        • 6.8.2 进阶:使用系统环境变量(更安全)
        • 6.8.3 测试环境效果验证
    • 七、服务器自动化部署脚本
      • 7.1 后端部署脚本
      • 7.2 前端构建脚本
      • 7.3 日常使用
    • 八、合并 Snowy 官方更新
      • 8.1 Git 仓库设置
      • 8.2 手动更新合并流程
      • 8.3 自动化同步脚本
      • 8.4 处理冲突
      • 8.5 注意事项
    • 九、常见问题排查
      • Q1:Nginx 返回 403 Forbidden
      • Q2:后端启动失败,日志显示 "Permission denied"
      • Q3:sudo 执行脚本时提示没有权限
      • Q4:前端刷新页面 404
      • Q5:Maven 依赖下载失败
    • 十、总结
      • 未来扩展考虑
      • 文章说明
      • 参考链接

作者笔记:本文以最小权限原则和清晰目录结构为核心,详细记录了在 CentOS 服务器上部署 Snowy(小诺)前后端项目的完整过程。包含专用运维用户创建、私有 Git 仓库迁移、目录规划、自动化脚本、systemd 服务、Nginx 反向代理等生产级实践。所有命令已在阿里云 Linux 4(CentOS 兼容)上验证通过。

备注:昨天我已经在本地MacOS环境搭建了《Java Snowy v3.6.2 前后端本地环境搭建全流程(踩坑实录)》,感兴趣的可以点击阅读。


一、写在前面

1.1 项目简介

Snowy 是目前国内非常热门的 Java 前后端分离快速开发平台,基于以下技术栈:

层面技术
后端 SpringBoot 3.5 + MyBatis-Plus + Sa-Token + HuTool
前端 Vue 3 + Ant Design Vue 4 + Vite 5
数据库 MySQL 8.0
缓存 Redis
特色 国密算法(SM2/SM3/SM4)、多租户、代码生成、工作流等

项目地址:https://gitee.com/xiaonuobase/snowy (Star 33K+)

1.2 源码获取与私有仓库迁移

生产环境部署时,应使用公司自己的代码仓库(如内部 GitLab、Gitee 私有仓库等),而不是每次都从官方仓库拉取。这样可保证版本稳定、便于二次开发。

首次迁移操作(在开发机或临时服务器执行一次):

# 1. 克隆官方源码
git clone https://gitee.com/xiaonuobase/snowy.git
cd snowy

# 2. 删除原有 .git 目录,断开与官方仓库的关联
rm -rf .git

# 3. 移动前端代码到另一个目录
mv snowy-admin-web ../

# 4. 初始化新仓库并关联到公司自己的远程仓库
git init
git remote add origin https://git.yourcompany.com/your-team/snowy-backend.git # 替换为实际地址
git add .
git commit -m "Initial commit from Snowy"
git push -u origin main

提示:需要将前后端拆分为两个独立仓库,便于独立版本管理。前端代码位于 snowy/snowy-admin-web 目录,同样执行上述迁移步骤,推送到另一个仓库如 snowy-frontend.git。

后续在服务器上部署时,直接从公司私有仓库克隆:

# 后端仓库地址
git clone https://git.yourcompany.com/your-team/snowy-backend.git
# 后端访问域名和nginx设置
# https://crm-api-test.mywebsite.com
# proxy_pass http://127.0.0.1:8082

# 前端仓库地址
git clone https://git.yourcompany.com/your-team/snowy-frontend.git
# 前端访问域名和nginx设置
# https://crm-web-test.mywebsite.com

1.3 整体架构(安全部署版)

#mermaid-svg-xhM9H5mQu9zNosEi{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xhM9H5mQu9zNosEi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xhM9H5mQu9zNosEi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xhM9H5mQu9zNosEi .error-icon{fill:#552222;}#mermaid-svg-xhM9H5mQu9zNosEi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xhM9H5mQu9zNosEi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xhM9H5mQu9zNosEi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xhM9H5mQu9zNosEi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xhM9H5mQu9zNosEi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xhM9H5mQu9zNosEi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xhM9H5mQu9zNosEi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xhM9H5mQu9zNosEi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xhM9H5mQu9zNosEi .marker.cross{stroke:#333333;}#mermaid-svg-xhM9H5mQu9zNosEi svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xhM9H5mQu9zNosEi p{margin:0;}#mermaid-svg-xhM9H5mQu9zNosEi .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xhM9H5mQu9zNosEi .cluster-label text{fill:#333;}#mermaid-svg-xhM9H5mQu9zNosEi .cluster-label span{color:#333;}#mermaid-svg-xhM9H5mQu9zNosEi .cluster-label span p{background-color:transparent;}#mermaid-svg-xhM9H5mQu9zNosEi .label text,#mermaid-svg-xhM9H5mQu9zNosEi span{fill:#333;color:#333;}#mermaid-svg-xhM9H5mQu9zNosEi .node rect,#mermaid-svg-xhM9H5mQu9zNosEi .node circle,#mermaid-svg-xhM9H5mQu9zNosEi .node ellipse,#mermaid-svg-xhM9H5mQu9zNosEi .node polygon,#mermaid-svg-xhM9H5mQu9zNosEi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xhM9H5mQu9zNosEi .rough-node .label text,#mermaid-svg-xhM9H5mQu9zNosEi .node .label text,#mermaid-svg-xhM9H5mQu9zNosEi .image-shape .label,#mermaid-svg-xhM9H5mQu9zNosEi .icon-shape .label{text-anchor:middle;}#mermaid-svg-xhM9H5mQu9zNosEi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xhM9H5mQu9zNosEi .rough-node .label,#mermaid-svg-xhM9H5mQu9zNosEi .node .label,#mermaid-svg-xhM9H5mQu9zNosEi .image-shape .label,#mermaid-svg-xhM9H5mQu9zNosEi .icon-shape .label{text-align:center;}#mermaid-svg-xhM9H5mQu9zNosEi .node.clickable{cursor:pointer;}#mermaid-svg-xhM9H5mQu9zNosEi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xhM9H5mQu9zNosEi .arrowheadPath{fill:#333333;}#mermaid-svg-xhM9H5mQu9zNosEi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xhM9H5mQu9zNosEi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xhM9H5mQu9zNosEi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xhM9H5mQu9zNosEi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xhM9H5mQu9zNosEi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xhM9H5mQu9zNosEi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xhM9H5mQu9zNosEi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xhM9H5mQu9zNosEi .cluster text{fill:#333;}#mermaid-svg-xhM9H5mQu9zNosEi .cluster span{color:#333;}#mermaid-svg-xhM9H5mQu9zNosEi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xhM9H5mQu9zNosEi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xhM9H5mQu9zNosEi rect.text{fill:none;stroke-width:0;}#mermaid-svg-xhM9H5mQu9zNosEi .icon-shape,#mermaid-svg-xhM9H5mQu9zNosEi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xhM9H5mQu9zNosEi .icon-shape p,#mermaid-svg-xhM9H5mQu9zNosEi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xhM9H5mQu9zNosEi .icon-shape .label rect,#mermaid-svg-xhM9H5mQu9zNosEi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xhM9H5mQu9zNosEi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xhM9H5mQu9zNosEi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xhM9H5mQu9zNosEi :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

后端_Spring_Boot

Nginx_服务器

浏览器

/api/* 请求

https://crm-web-test.mywebsite.com

Nginx :443进程用户: nobody

静态文件/home/snowy/app/code/frontend/pc-web

反向代理proxy_pass localhost:8082

后端服务 :8082进程用户: snowy

MySQL 8.0:3306

Redis:6379 db1/db2

1.4 服务器环境说明

本次部署的服务器信息如下:

[root@iZ2zecuztvoy0v29eoraqjZ ~]# cat /etc/os-release
NAME="Alibaba Cloud Linux"
VERSION="4"

💡 阿里云 Linux 4 基于 OpenAnolis,命令与 CentOS 7/8 完全兼容,本文所有命令均可在 CentOS 7/8 及兼容系统上执行。

1.5 所需环境清单

组件版本要求安装方式
JDK 17+(推荐 21 LTS) yum 安装
MySQL 8.0 yum 安装
Redis 最新版 yum 安装
Maven 3.6+ yum 安装
Node.js 18+(推荐 20 LTS) NodeSource 安装
Git 最新版 yum 安装

二、设计思路与目录规划

2.1 为什么不能直接用 root?

  • 安全风险:若 Java 应用被入侵,攻击者将获得 root 权限,可控制整台服务器。
  • 权限最小化:应用只需要读写自己的日志、上传文件,不应拥有系统级权限。
  • 便于审计:不同用户运行不同服务,问题定位更清晰。

因此,我们创建一个专用用户 snowy 来管理整个 Snowy 应用。

2.2 用户权限设计

设计思路:snowy 用户 可以登录,但受限。

运维人员需要使用该用户进行代码拉取、构建、重启服务等操作,所以 snowy 必须能够登录。但为了安全,我们通过 sudo 权限限制 使其只能执行特定命令(如 systemctl restart snowy-web-app、journalctl),而无法安装软件、修改系统配置。

思考:如果将来有多个员工需要维护,可以创建独立的个人账号(如 zhangsan),加入 snowy 组,并同样授予有限 sudo 权限,避免共享账号。

2.3 目录结构设计

设计思路:集中、可扩展、易迁移。

考虑到未来可能在同一台服务器上部署多个 Snowy 项目(例如 app2、app3),我们将所有项目文件放在 /home/snowy/app 下,而不是散落在家目录根下。这样既整洁,又便于整体打包迁移。需要创建一个snowy的系统用户。

/home/snowy/
├── .bashrc # 用户配置文件(自然存在)
├── .ssh/ # SSH 密钥(自然存在)
├── .m2/ # Maven 本地仓库(自然存在)
└── app/ # 当前 Snowy 项目根目录
├── code/
│ ├── git/
│ │ ├── snowy-backend/ # 后端源码(从公司私有仓库克隆)
│ │ └── snowy-frontend/ # 前端源码(从公司私有仓库克隆)
│ ├── frontend/
│ │ └── pc-web/ # 前端静态文件(Nginx 读取)
│ │ └── miniprogram/ # 小程序文件(Nginx 读取)
│ └── backend/
│ └── api/ # 后端运行文件(软链接指向 JAR)
├── logs/
│ ├── backend/ # 后端日志
│ └── frontend/ # 前端构建日志(可选)
├── data/
│ ├── uploads/ # 用户上传文件(可选)
│ └── backups/ # 备份文件(可选)
└── scripts/
├── backend-deploy.sh # 后端部署脚本
└── frontend-build.sh # 前端构建脚本

权限要点:

  • 整个 /home/snowy/app 目录属主 snowy:snowy,权限 755(目录需要 +x 才能进入)。
  • Nginx 静态文件目录 /home/snowy/app/code/frontend/pc-web 需要被 Nginx worker 进程(用户 nobody)读取,因此需开放 755 权限,且所有父目录(/home、/home/snowy、/home/snowy/app、/home/snowy/app/code)也需对 nobody 有 +x 权限。
  • 如果想简单处理,也可以修改nginx.conf的user snowy;
  • 日志和数据目录只需 snowy 用户读写,无需对外开放。

2.4 自动化与未来扩展

本文使用 git pull + shell 脚本 实现快速部署。后续可引入 Jenkins、GitLab CI 等自动化运维工具。


三、环境准备

提示:这里使用 root 安装系统软件。

3.1 创建系统用户

具体操作:创建 snowy 用户并设置目录

# 创建可登录的 snowy 用户(用于管理代码和服务)
useradd -m -s /bin/bash snowy
passwd snowy # 可选,若使用密钥登录可跳过

# 创建 app 目录结构
mkdir -p /home/snowy/app/{code/{git,frontend,backend},logs/{backend,frontend},data/{uploads,backups},scripts}
chown -R snowy:snowy /home/snowy/app

3.2 安装软件环境

具体操作:安装 Git、JDK 21、Maven、Node.js、MySQL 8、Redis

# 安装 Git
yum install -y git

# 安装 Java环境
yum install -y java-21-openjdk java-21-openjdk-devel
yum install -y maven

# (可选)前置检查:查看所有已安装的 Java 版本
# alternatives –config java # 如果输出有多个版本,建议卸载旧版本
# 卸载 Java旧版本
# yum remove -y java-11-openjdk java-11-openjdk-headless

# 安装 Node.js 20
curl -sL https://rpm.nodesource.com/setup_20.x | bash –
yum install -y nodejs

############# 如果用阿里云的数据库服务,就跳过安装MySQL和Redis #################
# 安装 MySQL 8
wget https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm
yum localinstall -y mysql80-community-release-el7-11.noarch.rpm
yum install -y mysql-community-server
systemctl start mysqld
systemctl enable mysqld

# 配置 MySQL 数据库
# 获取临时密码
grep 'temporary password' /var/log/mysqld.log
mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourStrongPassword123!';
CREATE DATABASE IF NOT EXISTS snowy DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
EXIT;

# 安装 Redis
yum install -y redis
systemctl start redis
systemctl enable redis

提示:如果用阿里云的数据库服务,就跳过安装MySQL和Redis。

安装后的效果查看(示例)

[root@myServer ~]# git -v
git version 2.47.3

[root@myServer ~]# java -version
openjdk version "21.0.10.0.10" 2026-03-27
OpenJDK Runtime Environment (Alibaba Dragonwell Extended Edition) (build 21.0.10.0.10+21)
OpenJDK 64-Bit Server VM (Alibaba Dragonwell Extended Edition) (build 21.0.10.0.10+21, mixed mode, sharing)

[root@myServer ~]# mvn -v
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /usr/local/maven
Java version: 21.0.10.0.10, vendor: Alibaba, runtime: /usr/lib/jvm/java-21-alibaba-dragonwell-21.0.10.0.10-1.1.alnx4.x86_64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.6.102-5.2.alnx4.x86_64", arch: "amd64", family: "unix"

[root@myServer ~]# node -v
v22.16.0

[root@myServer ~]# npm -v
10.9.2

3.3 开发工具与 Git 配置

3.3.1 配置 SSH 密钥(用于拉取私有仓库代码)

首先检查是否已有 SSH 密钥:ls -la ~/.ssh/

如果没有密钥,生成新密钥(一路回车即可):ssh-keygen

查看公钥内容:cat ~/.ssh/id_rsa.pub

将输出的公钥内容添加到 Git 仓库(如阿里云 Codeup、GitLab、GitHub)的 SSH 密钥设置中。然后就可以 git clone 代码了。

3.3.2 Git 常用配置

设置用户名和邮箱(用于记录提交信息):

git config –global user.name "你的名字"
git config –global user.email "你的邮箱@example.com"

设置常用别名(提高效率):

git config –global alias.s status # git s 代替 git status
git config –global alias.c checkout
git config –global alias.p pull
git config –global alias.b branch
git config –global alias.d diff

验证配置:git config –global –list

3.3.3 安装 tig(Git 日志查看工具)

tig 是一个基于终端的 Git 日志查看工具,可以用交互式界面浏览提交历史、查看文件变更,比命令行 git log 更直观高效。

安装步骤:

# 安装编译依赖
yum install -y ncurses-devel make gcc

# 下载源码
cd /tmp
git clone https://github.com/jonas/tig.git
cd tig

# 编译安装
make
make install

# 验证安装
tig –version

使用方式:在 Git 仓库目录下执行 tig 即可打开交互式日志界面。

如果 git clone 下载慢,可以跳过 tig,不影响核心部署。

3.3.4 解决 SSH 连接警告(服务器密钥变更)

当服务器系统升级或重装后,SSH 连接可能会报错:

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
Host key verification failed.

解决方法:删除本地保存的旧密钥记录:ssh-keygen -R 服务器IP

例如:ssh-keygen -R 123.23.34.56

然后重新连接:ssh root@123.23.34.56,输入 yes 接受新的主机密钥即可。

原因说明:该警告是 SSH 的安全机制,防止中间人攻击。服务器重装系统后主机密钥会改变,本地需要更新记录。

3.4 配置用户权限

具体操作:为 snowy 用户配置有限 sudo 权限。

编辑 sudoers 文件(使用 visudo),添加以下行,使 snowy 能够重启服务、查看日志,但不能做其他系统管理操作:使用 visudo

命令,然后添加:

snowy ALL=(ALL) NOPASSWD: /bin/systemctl restart snowy-web-app, /bin/systemctl stop snowy-web-app, /bin/systemctl start snowy-web-app, /bin/systemctl status snowy-web-app, /bin/journalctl -u snowy-web-app

这样 snowy 可以执行 sudo systemctl restart snowy-web-app,但无法 yum install 或修改 /etc 下的文件。


四、后端部署

提示:这里以 snowy 用户操作。

4.1 克隆后端代码

具体操作:切换用户并克隆后端代码(从公司私有仓库)

su – snowy
cd /home/snowy/app/code/git
git clone https://git.yourcompany.com/your-team/snowy-backend.git # 替换为实际仓库地址
cd snowy-backend
# 如需要,切换到对应分支(例如 main 或 master)
git checkout main

TODO:后续如果需要合并snowy官方的更新,等待后续我再想想兼容方案,到时候最好能写个自动化脚本自动更新同步。

4.2 配置Maven镜像

提示:配置 Maven 国内镜像(加速依赖下载)

mkdir -p /home/snowy/.m2
cat > /home/snowy/.m2/settings.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<mirrors>
<mirror>
<id>aliyun</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>http://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
</settings>
EOF

4.3 导入SQL到数据库

# 创建数据库
mysql -uroot -p
CREATE DATABASE snowy DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

# 导入数据
mysql -u root -p snowy < /home/snowy/app/code/git/snowy-backend/snowy-web-app/src/main/resources/_sql/snowy_mysql.sql

4.4 修改后端配置文件

cd /home/snowy/app/code/git/snowy-backend/snowy-web-app/src/main/resources
vim application.properties

修改以下关键配置:

# 数据库连接
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/snowy?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.dynamic.datasource.master.password=YourStrongPassword123!

# Redis
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
# spring.data.redis.username=user1 #可选
spring.data.redis.password=123
spring.data.redis.database=1

# 服务端口
server.port=8082

# 文件上传路径
snowy.file.local.path=/home/snowy/app/data/uploads

为什么 server.port 用 8082 而不是 82? Linux 系统规定:普通用户(非 root)无权绑定 1024 以下的端口(如 80、82、443 等)。如果使用 82 端口,启动时会报 java.net.BindException: Permission denied 错误。 解决方案有两种:① 改用大于 1024 的端口(如 8082);② 让 Java 服务以 root 用户运行(不推荐,有安全风险)。 因此,这里选择 8082 端口,既避免了权限问题,又符合最小权限安全原则。后续 Nginx 反向代理只需将请求转发到 127.0.0.1:8082 即可。

4.5 编译和打包

Snowy 是一个多模块项目,需要先安装所有模块到本地仓库,再打包启动模块。

4.5.1 编译安装所有模块

cd /home/snowy/app/code/git/snowy-backend
mvn clean install -DskipTests # 此命令会编译全部 21 个模块并安装到本地 Maven 仓库,首次执行大概需要3-5分钟(视服务器配置情况不同,时长也不同)

编译成功后的输出:

[INFO] ————————————————————————
[INFO] Reactor Summary for snowy 3.0.0:
[INFO]
[INFO] snowy ………………………………………. SUCCESS [ 6.818 s]
[INFO] snowy-common ………………………………… SUCCESS [01:13 min]
[INFO] snowy-plugin-api …………………………….. SUCCESS [ 0.033 s]
[INFO] snowy-plugin-auth-api ………………………… SUCCESS [ 3.361 s]
[INFO] snowy-plugin-sys-api …………………………. SUCCESS [ 1.210 s]
[INFO] snowy-plugin-client-api ………………………. SUCCESS [ 1.028 s]
[INFO] snowy-plugin-dev-api …………………………. SUCCESS [ 19.019 s]
[INFO] snowy-plugin ………………………………… SUCCESS [ 0.020 s]
[INFO] snowy-plugin-auth ……………………………. SUCCESS [ 9.300 s]
[INFO] snowy-plugin-biz-api …………………………. SUCCESS [ 1.225 s]
[INFO] snowy-plugin-biz …………………………….. SUCCESS [ 10.680 s]
[INFO] snowy-plugin-client ………………………….. SUCCESS [ 6.626 s]
[INFO] snowy-plugin-dev …………………………….. SUCCESS [ 14.891 s]
[INFO] snowy-plugin-gen-api …………………………. SUCCESS [ 0.762 s]
[INFO] snowy-plugin-mobile-api ………………………. SUCCESS [ 0.964 s]
[INFO] snowy-plugin-gen …………………………….. SUCCESS [ 9.167 s]
[INFO] snowy-plugin-mobile ………………………….. SUCCESS [ 4.877 s]
[INFO] snowy-plugin-sys …………………………….. SUCCESS [ 16.970 s]
[INFO] snowy-web-app ……………………………….. SUCCESS [ 14.032 s]
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 03:22 min
[INFO] Finished at: 2026-04-16T14:16:14+08:00
[INFO] ————————————————————————

4.5.2 编译阶段常见问题及解决方案

问题一:Maven 版本过低导致编译失败

  • 错误信息:The plugin org.apache.maven.plugins:maven-clean-plugin:3.4.1 requires Maven version 3.6.3

  • 原因:Snowy 3.0.0 需要 Maven 3.6.3 或更高版本,但系统自带的 Maven 版本较旧(如 3.6.2)。

  • 解决方案:升级 Maven 到 3.9.9

    # 卸载旧版
    yum remove -y maven

    # 下载新版
    cd /tmp
    wget https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz
    tar -xzf apache-maven-3.9.9-bin.tar.gz -C /usr/local/
    ln -s /usr/local/apache-maven-3.9.9 /usr/local/maven

    # 配置环境变量
    echo 'export MAVEN_HOME=/usr/local/maven' > /etc/profile.d/maven.sh
    echo 'export PATH=$MAVEN_HOME/bin:$PATH' >> /etc/profile.d/maven.sh
    source /etc/profile.d/maven.sh

    # 验证版本
    mvn -version # 应显示 3.9.x

问题二:Java 版本不正确

  • 错误信息:编译时提示 Java 版本不兼容,或 mvn -version 显示 Java 11 而非 21

  • 原因:系统同时安装了多个 JDK 版本,环境变量指向了旧版本。

  • 解决方案:切换默认 Java 版本或卸载旧 JDK

    # 查看所有已安装的 Java 版本
    alternatives –config java

    # 选择 Java 21(输入对应编号 1/2/3/….)

    # 或卸载 Java 11(可选)
    yum remove -y java-11-openjdk java-11-openjdk-headless

问题三:Maven 依赖下载缓慢或失败

  • 错误信息:Remote host terminated the handshake 或下载超时
  • 原因:访问国外 Maven 中央仓库网络不稳定。
  • 解决方案:配置阿里云镜像(详见 4.2 节)

问题四:内存不足导致编译失败

  • 错误信息:OutOfMemoryError 或 Java heap space

  • 原因:服务器内存不足(低于 2GB)或 Maven 分配的内存过小。

  • 解决方案:增加 Maven 编译内存

    export MAVEN_OPTS="-Xmx1024m -XX:MaxPermSize=512m"

    或修改 .m2/settings.xml 添加:

    <profiles>
    <profile>
    <id>myprofile</id>
    <properties>
    <MAVEN_OPTS>-Xmx1024m</MAVEN_OPTS>
    </properties>
    </profile>
    </profiles>

问题五:编译时卡住不动

  • 原因:依赖下载过程中网络中断或 Maven 进程假死。
  • 解决方案:按 Ctrl + C 终止进程,删除 Maven 本地仓库中下载失败的缓存文件:find ~/.m2/repository -name "*.lastUpdated" -delete,然后重新执行 mvn clean install -DskipTests
4.5.3 打包启动模块

cd snowy-web-app
mvn clean package -DskipTests

package 命令会生成可执行的 JAR 包,位于 target/snowy-web-app-*.jar。

构建成功后,输出显示:

[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 24.651 s
[INFO] Finished at: 2026-04-16T14:18:32+08:00
[INFO] ————————————————————————

4.5.4 打包启动模块常见问题

问题一:找不到依赖模块(编译失败)

  • 错误信息:Could not find artifact vip.xiaonuo:snowy-common:jar:3.0.0
  • 原因:直接执行 mvn package 打包 snowy-web-app 时,其他模块(如 snowy-common)尚未安装到本地 Maven 仓库。
  • 解决方案:先回到项目根目录执行 mvn clean install -DskipTests,再进入 snowy-web-app 执行 mvn package。

问题二:打包生成的 JAR 包无法启动

  • 错误信息:no main manifest attribute 或 Failed to launch application

  • 原因:pom.xml 中缺少 Spring Boot Maven 插件配置,导致打包的不是可执行 JAR。

  • 解决方案:检查 snowy-web-app/pom.xml 是否包含以下插件:

    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>

    如果没有,添加后重新打包。

问题三:打包后 JAR 包体积异常小

  • 现象:正常 JAR 包约 100-200MB,但生成的只有几十 KB。
  • 原因:打包时跳过了依赖,只打包了项目本身的代码(未打依赖包)。
  • 解决方案:确保使用 spring-boot-maven-plugin 且执行的是 package 而非 jar 插件。执行 mvn clean package 重新打包。

问题四:打包时测试用例失败

  • 错误信息:There are test failures
  • 原因:某些单元测试依赖外部环境(如数据库、Redis)或测试用例本身有 bug。
  • 解决方案:
    • 临时跳过测试:mvn clean package -DskipTests
    • 或只编译测试不执行:mvn clean package -Dmaven.test.skip=true

问题五:打包过程中内存溢出

  • 错误信息:OutOfMemoryError 或 GC overhead limit exceeded

  • 原因:项目模块较多,打包时内存不足。

  • 解决方案:增加 Maven 编译内存

    export MAVEN_OPTS="-Xmx2048m -XX:MaxMetaspaceSize=512m"
    mvn clean package -DskipTests

问题六:打包成功后 JAR 包找不到

  • 现象:ls target/*.jar 没有文件。
  • 原因:package 命令执行成功但没有生成 JAR,可能是 pom.xml 中 <packaging> 类型不是 jar。
  • 解决方案:检查 pom.xml 中是否有 <packaging>jar</packaging>,Spring Boot 项目默认为 jar。

经验总结:99% 的打包问题可以通过以下步骤解决:

  • 确保在项目根目录执行过 mvn clean install -DskipTests
  • 确保 Maven 版本 ≥ 3.6.3
  • 确保 Java 版本为 17 或 21
  • 配置了阿里云镜像加速依赖下载
  • 遇到 .lastUpdated 缓存文件时删除后重试
  • 4.6 创建软链接

    由于每次打包生成的 JAR 文件名包含版本号(如 snowy-web-app-3.0.0.jar),而 systemd 服务需要固定路径。因此创建一个软链接,指向最新构建的 JAR 包,后续更新时只需重新链接,无需修改服务配置。

    # 创建后端运行目录
    mkdir -p /home/snowy/app/code/backend/api

    # 软链接指向最新构建的 JAR
    ln -sf /home/snowy/app/code/git/snowy-backend/snowy-web-app/target/snowy-web-app-3.0.0.jar /home/snowy/app/code/backend/api/snowy-web-app.jar

    # 软链接指向成功
    ls -la
    lrwxrwxrwx 1 snowy snowy 95 Apr 16 14:25 snowy-web-app.jar –> /home/snowy/app/code/git/snowy-backend/snowy-web-app/target/snowy-web-app-3.0.0.jar

    # 如果 snowy-web-app-3.0.0.jar 找不到:就先搜索一下
    ls -la /home/snowy/app/code/git/snowy-backend/snowy-web-app/target/*.jar

    4.7 systemd服务

    4.7.1 创建systemd服务

    使用 systemd 管理后端服务,可实现开机自启、崩溃自动重启、统一日志管理。

    提示:需要切换到 root 用户创建服务文件:su – root

    exit # 回到 root

    cat > /etc/systemd/system/snowy-web-app.service << 'EOF'
    [Unit]
    Description=Snowy Web Application
    After=network.target mysql.service redis.service

    [Service]
    Type=simple
    User=snowy
    Group=snowy
    WorkingDirectory=/home/snowy/app/logs/backend
    Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk"
    ExecStart=/usr/bin/java -jar /home/snowy/app/code/backend/snowy-api/snowy-web-app.jar –spring.profiles.active=test
    Restart=on-failure
    RestartSec=10

    [Install]
    WantedBy=multi-user.target
    EOF

    启动并设置开机自启:

    systemctl daemon-reload
    systemctl start snowy-web-app
    systemctl enable snowy-web-app
    systemctl restart snowy-web-app
    systemctl stop snowy-web-app
    systemctl status snowy-web-app

    说明:User=snowy 指定以 snowy 用户运行,WorkingDirectory 为日志输出目录;

    –spring.profiles.active=test 激活测试环境配置。

    –spring.profiles.active=prod 激活生产环境配置。

    4.7.2 systemctl 常用命令说明
    命令作用使用场景
    systemctl daemon-reload 重新加载 systemd 配置文件 修改了 .service 文件后必须执行,否则修改不生效
    systemctl start snowy-web-app 启动服务 服务未运行时首次启动
    systemctl stop snowy-web-app 停止服务 需要暂停服务时使用(如维护数据库)
    systemctl restart snowy-web-app 重启服务 修改配置或更新 JAR 包后,需要重启使改动生效
    systemctl enable snowy-web-app 设置开机自启 首次部署时执行一次,确保服务器重启后服务自动运行
    systemctl status snowy-web-app 查看服务状态 检查服务是否正常运行、查看最近日志、排查问题时首选

    经验总结:

    • 修改 .service 文件后,必须执行 daemon-reload,否则 systemd 不会识别修改。
    • 日常更新代码后,使用 restart 即可,无需 stop 再 start。
    • status 是最常用的命令,建议每次操作后都执行一次确认状态。

    4.8 各类错误问题排查和处理

    在部署过程中,我遇到了多个典型问题。以下是问题现象、原因分析及解决方案的完整记录。

    4.8.1 查看错误日志的方法

    在排查问题前,首先要学会查看日志:

    # 查看 systemd 服务最新日志(实时滚动)
    journalctl -u snowy-web-app -f –no-pager

    # 查看最近 50 行日志
    journalctl -u snowy-web-app -n 50 –no-pager

    # 只查看错误和异常(加上 | tail -10 表示查看最后10行)
    journalctl -u snowy-web-app –no-pager | grep -E "ERROR|Exception|Caused" | tail -10

    # 查看应用自身日志(如果配置了文件输出)
    tail -100 /home/snowy/app/logs/backend/app-log/log_error.log

    4.8.2 问题列表及解决方案

    问题一:服务反复重启,状态显示 activating (auto-restart)

    • 错误关键字:restart counter is at X
    • 原因:应用启动失败导致 systemd 不断重试
    • 解决方法:查看具体错误日志,定位根本原因:journalctl -u snowy-web-app -n 50 –no-pager

    问题二:数据库连接失败:Unknown database 'snowy'

    • 错误关键字:SQLSyntaxErrorException: Unknown database 'snowy'
    • 原因:配置文件中的数据库名与实际不符,或数据库未创建
    • 解决方法:
    • 确认数据库名:mysql -u root -p -e "SHOW DATABASES;"
    • 修改 application.properties 中的 url 为正确数据库名
    • 重新打包 JAR

    问题三:Tomcat 启动失败:BindException: Permission denied

    • 错误关键字:java.net.BindException: Permission denied
    • 原因:普通用户无法绑定 1024 以下的端口(如 82)
    • 解决方法:改用大于 1024 的端口(如 8082),或让服务以 root 运行(不推荐)

    问题四:修改配置后不生效

    • 错误关键字:无报错但行为仍旧
    • 原因:JAR 包是旧版本,未重新打包
    • 解决方法:修改源码后必须执行 mvn clean package -DskipTests 重新生成 JAR,并重启服务

    问题五:软链接创建失败:No such file or directory

    • 错误关键字:ln: target 'xxx': No such file or directory
    • 原因:目标目录不存在,或通配符 * 在软链接中无法展开
    • 解决方法:先创建目录 mkdir -p,然后使用具体的 JAR 文件名(如 snowy-web-app-3.0.0.jar)创建软链接

    问题六:Redis 连接失败:NOAUTH Authentication required

    • 错误关键字:NOAUTH Authentication required
    • 原因:Redis 设置了密码,但配置文件中未填写
    • 解决方法:找到 Redis 密码(如通过 docker inspect 查看环境变量或启动脚本),在 application.properties 中填写 spring.data.redis.password=xxx

    问题七:Maven 编译失败:找不到内部模块

    • 错误关键字:Could not find artifact vip.xiaonuo:snowy-common:jar
    • 原因:直接打包 snowy-web-app 前未安装其他模块
    • 解决方法:先在根目录执行 mvn clean install -DskipTests,再进入 snowy-web-app 执行 mvn package

    问题八:Maven 依赖下载慢或 SSL 握手失败

    • 错误关键字:Remote host terminated the handshake
    • 原因:访问国外中央仓库网络不稳定
    • 解决方法:配置阿里云镜像(使用 HTTP 而非 HTTPS):在 ~/.m2/settings.xml 中设置 <url>http://maven.aliyun.com/repository/public</url>

    问题九:服务启动后端口未监听

    • 错误关键字:curl: (7) Failed to connect to port
    • 原因:应用启动未完成或启动失败
    • 解决方法:查看日志等待 Application is running! 出现,再用 netstat -tlnp | grep 端口 检查

    问题十:前端访问 API 返回 401 但后端正常

    • 错误关键字:{"code":401,"msg":"未能读取到有效 token"}
    • 原因:这是正常现象,表示 API 需要认证,不是错误
    • 解决方法:无需处理,登录后即可正常调用
    4.8.3 通用排查流程

    当服务出现问题时,按以下步骤快速定位:

    # 1. 检查服务状态
    systemctl status snowy-web-app

    # 2. 查看最近错误日志
    journalctl -u snowy-web-app -n 50 –no-pager | grep -E "ERROR|Exception"

    # 3. 确认端口是否监听
    netstat -tlnp | grep 8082 # 替换为你的端口

    # 4. 测试 API 是否响应
    curl http://localhost:8082/api/auth/getCaptcha

    # 5. 如果修改了代码,确保重新打包并更新软链接
    cd /home/snowy/app/code/git/snowy-backend
    mvn clean package -DskipTests
    ln -sf snowy-web-app/target/snowy-web-app-*.jar /home/snowy/app/code/backend/api/snowy-web-app.jar

    # 6. 重启服务
    systemctl restart snowy-web-app

    经验总结:80% 的问题出在配置文件未生效(未重新打包)、数据库名/密码错误、端口权限不足。养成修改配置后重新打包、重启服务的习惯,可以避免大部分坑。

    4.9 后端 Nginx 配置

    4.9.1 安装Nginx

    编译安装:

    # 安装编译依赖
    yum install -y gcc make pcre-devel zlib-devel openssl-devel

    # 下载 Nginx 源码
    cd /tmp
    wget http://nginx.org/download/nginx-1.24.0.tar.gz
    tar -xzf nginx-1.24.0.tar.gz
    cd nginx-1.24.0

    # 配置(启用 SSL)
    ./configure –prefix=/usr/local/nginx –with-http_ssl_module

    # 编译安装
    make && make install

    # 创建软链接(可选,方便命令行调用)
    ln -sf /usr/local/nginx/sbin/nginx /usr/local/bin/nginx

    # 验证安装
    nginx -v

    配置开机自启(创建 systemd 服务):

    cat > /etc/systemd/system/nginx.service << 'EOF'
    [Unit]
    Description=Nginx HTTP Server
    After=network.target

    [Service]
    Type=forking
    ExecStart=/usr/local/nginx/sbin/nginx
    ExecReload=/usr/local/nginx/sbin/nginx -s reload
    ExecStop=/usr/local/nginx/sbin/nginx -s stop
    PrivateTmp=true

    [Install]
    WantedBy=multi-user.target
    EOF

    systemctl daemon-reload
    systemctl enable nginx
    systemctl start nginx
    systemctl status nginx

    验证:curl http://localhost,应该显示 Nginx 欢迎页。

    之后统一用 systemd 管理 Nginx:

    systemctl status nginx # 查看状态
    systemctl restart nginx # 重启
    systemctl stop nginx # 停止

    4.9.2 配置 Nginx 虚拟主机

    为了方便管理,我们使用 conf.d 目录存放各个域名的独立配置文件。Nginx 会自动加载该目录下所有以 .conf 结尾的文件。

    第一步:创建 conf.d 目录(如果不存在)

    mkdir -p /usr/local/nginx/conf/conf.d

    第二步:在主配置文件中引入 conf.d

    编辑 /usr/local/nginx/conf/nginx.conf,在 http 块中添加:

    http {
    # … 其他配置 …
    include conf.d/*.conf;
    }

    4.9.3 后端 Nginx 配置

    如果希望直接通过域名访问后端 API(给外部系统调用),可以单独配置一个后端域名 crm-api-test.mywebsite.com。

    操作 vi /usr/local/nginx/conf/conf.d/crm-api-test.mywebsite.com.conf

    server {
    listen 80;
    server_name crm-api-test.mywebsite.com;
    return 301 https://$host$request_uri;
    }

    server {
    listen 443 ssl;
    server_name crm-api-test.mywebsite.com;

    ssl_certificate /usr/local/nginx/conf/ssl/mywebsite.com.pem;
    ssl_certificate_key /usr/local/nginx/conf/ssl/mywebsite.com.key;

    location / {
    proxy_pass http://127.0.0.1:8082;
    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;
    }
    }

    说明:此配置直接将所有请求转发到后端 8082 端口,适合作为 API 网关使用。如果不需要对外暴露 API 域名,可以省略此配置。

    还需要确保Nginx用户能读取静态文件。Nginx worker 进程以 nobody 用户运行,而静态文件目录属主是 snowy,需要开放读取权限:

    chmod -R 755 /home/snowy

    测试并重载 Nginx:

    # 测试配置文件语法
    /usr/local/nginx/sbin/nginx -t

    # 重载使配置生效
    /usr/local/nginx/sbin/nginx -s reload
    # 或 systemctl restart nginx

    验证配置:

    # 测试后端 API(通过后端域名直接访问)
    curl https://crm-api-test.mywebsite.com/api/auth/getCaptcha -k
    {"code":401,"msg":"未能读取到有效 token","data":null,"traceId":null}%

    备注: -k 参数:如果使用自签名证书,curl 会报证书错误,加 -k 忽略证书验证。生产环境使用正规证书后无需此参数。

    浏览器访问:

    接口HOST:https://crm-api-test.mywebsite.com/
    接口文档:https://crm-api-test.mywebsite.com/doc.html#/home

    image-20260417163822501


    五、前端部署

    前端项目是基于 Vue 3 + Vite 构建的,需要先安装依赖、配置环境变量,然后打包成静态文件,最后部署到 Nginx 目录。

    5.1 克隆前端代码

    从公司私有仓库克隆前端代码:

    cd /home/snowy/app/code/git
    git clone git@codeup.aliyun.com:xxxxxx/snowy-frontend.git
    cd snowy-frontend

    说明:如果仓库结构与官方 Snowy 一致,前端代码在 snowy-admin-web 子目录中。如果是自定义仓库,可能直接在根目录。请根据实际情况进入对应目录。

    5.2 配置环境变量

    需要区分开发、测试、生产环境,Vite 通过 .env.[mode] 文件实现。

    修改已有的.env.production 相关数据,然后复制一份为 .env.test作为测试环境cp .env.production .env.test,写入内容:

    # 接口地址
    VITE_API_BASEURL = https://crm-api-test.yoursite.com

    # 本地端口
    VITE_PORT = 8081

    # 开启设置抽屉
    VITE_SET_DRAWER = false

    # 检测更新(生产建议开启)
    VITE_VERSION_UPDATE = true

    修改 package.json 构建脚本,适配npm run dev / test / prod 和 npm run build / build:test / build:prod:

    "scripts": {
    "serve": "vite –host 0.0.0.0",
    "dev": "vite –mode development –host 0.0.0.0",
    "test": "vite –mode test",
    "prod": "vite –mode production",
    "preview": "vite preview",
    "build": "vite build",
    "build:test": "vite build –mode test",
    "build:prod": "vite build –mode production"
    }

    本地运行与打包:

    • 本地开发:npm run dev 自动加载 .env.development
    • 测试环境打包:npm run build:test 加载 .env.test
    • 生产环境打包:npm run build:prod 加载 .env.production

    5.3 安装依赖并构建

    # (可选)设置淘宝镜像(加速依赖下载)
    # npm config set registry https://registry.npmmirror.com

    # 安装依赖(首次约需 2-5 分钟)
    npm install

    # 生产环境打包
    npm run build:prod

    构建成功后,会在 dist 目录(或 snowy-admin-web/dist)下生成静态文件。

    💡 常见问题:如果 npm install 失败,可尝试删除 node_modules 和 package-lock.json 后重试。

    5.4 部署静态文件到 Nginx 目录

    将构建好的 dist 目录内容复制到 Nginx 配置的静态文件目录:

    # 创建目标目录(如果不存在)
    mkdir -p /home/snowy/app/code/frontend/pc-web

    # 复制新构建的文件(基于当前位置)
    cd /home/snowy/app/code/git/snowy-frontend
    cp -r ./dist/* /home/snowy/app/code/frontend/pc-web

    5.5 前端 Nginx 配置

    为前端域名 crm-web-test.mywebsite.com 配置 Nginx,实现静态文件托管和 API 反向代理。

    1. 创建 Nginx 配置文件 vi /usr/local/nginx/conf/conf.d/crm-web-test.mywebsite.com.conf

    2. 写入以下配置

    server {
    listen 80;
    server_name crm-web-test.mywebsite.com;
    return 301 https://$host$request_uri;
    }

    server {
    listen 443 ssl;
    server_name crm-web-test.mywebsite.com;

    ssl_certificate /usr/local/nginx/conf/ssl/mywebsite.com.pem;
    ssl_certificate_key /usr/local/nginx/conf/ssl/mywebsite.com.key;

    location / {
    root /home/snowy/app/code/frontend/pc-web;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
    }

    location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
    root /home/snowy/app/code/frontend/pc-web;
    expires 1y;
    add_header Cache-Control "public, immutable";
    }
    }

    配置说明:

    • location /:托管前端静态文件,try_files 支持 Vue Router 的 history 模式。
    • 静态资源单独缓存,提升页面加载速度。

    3. 测试并重启 Nginx

    /usr/local/nginx/sbin/nginx -t
    /usr/local/nginx/sbin/nginx -s reload

    5.6 验证部署结果

    1. 验证配置

    # 测试前端页面
    curl https://crm-web-test.mywebsite.com -k

    # 测试 API 代理
    curl https://crm-web-test.mywebsite.com/api/auth/getCaptcha -k

    💡 -k 参数:如果使用自签名证书,curl 会报证书错误,加 -k 可忽略证书验证。生产环境使用正规 SSL 证书后无需此参数。

    2. 通过域名访问前端页面

    浏览器访问 https://crm-web-test.mywebsite.com,应看到 Snowy 登录页面。

    3. 测试登录

    使用默认账号登录:

    • 用户名:superAdmin
    • 密码:123456

    登录成功后,页面应跳转到后台管理界面。

    如果出现500,查看错误日志:tail -20 /usr/local/nginx/logs/error.log;如果出现错误:Permission denied,说明 Nginx无法读取 前端的目录。解决:修改nginx.conf,将user nobody;改为user snowy;,然后nginx -s reload。

    image-20260417194912942


    六、后端环境变量配置

    针对 Java snowy 项目,在实际企业开发中,往往需要同时维护本地开发环境、测试环境、生产环境,且每个环境的 数据库地址、Redis 地址、端口等配置各不相同。如果将这些配置硬编码在 application.properties 中并提交到 Git,不仅会导致频繁的代码冲突,还会带来严重的安全风险(密码泄露)。

    在部署生产环境之前,我们需要对后端配置文件进行改造。Snowy 默认将所有配置(包括数据库密码、Redis 密码等敏感信息)都写在 application.properties 中,这样做有两个问题:

  • 安全性:数据库密码等敏感信息会随着代码一起提交到 Git 仓库,造成泄露风险。
  • 环境隔离:开发、测试、生产环境使用的数据库地址、密码、端口 等通常不同,混在一起容易出错。
  • 因此,我们采用 Spring Boot 原生的 Profile 多环境配置 方案,将敏感信息与通用配置分离。

    6.1 配置文件规划

    文件是否提交 Git作用
    application.properties ✅ 提交 存放所有环境的通用配置,敏感信息使用占位符(如 ${DB_HOST})
    application-local.properties ❌ 不提交 本地开发环境配置(连接本地 MySQL/Redis),每个开发者自己维护
    application-dev.properties ❌ 不提交 开发/测试环境配置(连接远程测试数据库),供调试使用
    application-test.properties ❌ 不提交 测试服务器环境配置(由测试服务器管理)
    application-prod.properties ❌ 不提交 生产环境配置(由运维或部署系统管理)
    application-demo.properties ✅ 提交 配置模板,列出所有需要覆盖的敏感项,供团队成员参考

    设计思路:

    • application.properties 中只保留通用配置和占位符,可以安全提交到 Git。
    • 敏感信息(密码、地址等)通过不同的 application-{profile}.properties 文件提供,这些文件不提交 Git。
    • 本地开发默认使用 local profile,无需任何额外配置,开箱即用。

    6.2 修改application.properties

    编辑 application.properties,将数据库和 Redis 的可变部分全部替换为占位符(不设置默认值,强制要求环境配置):

    # ========== 其他通用配置 ==========
    server.port=${SERVER_PORT}
    spring.application.name=snowy
    snowy.config.common.front-url=${FRONT_URL}
    snowy.config.common.backend-url=${BACKEND_URL}

    # 默认激活 local profile(本地开发,不用改动!)
    spring.profiles.active=local

    # ========== 数据库配置(占位符) ==========
    spring.datasource.dynamic.datasource.master.url=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&useInformationSchema=true&rewriteBatchedStatements=true
    spring.datasource.dynamic.datasource.master.username=${DB_USER}
    spring.datasource.dynamic.datasource.master.password=${DB_PASSWORD}

    # ========== Redis 配置(占位符) ==========
    spring.data.redis.host=${REDIS_HOST}
    spring.data.redis.port=${REDIS_PORT}
    spring.data.redis.password=${REDIS_PASSWORD}
    spring.data.redis.database=${REDIS_DATABASE}

    说明:

    • ${DB_HOST} 表示必须通过环境配置文件提供该值,否则 Spring Boot 启动时会报错 Could not resolve placeholder。
    • 这样能强制开发者配置自己的环境,避免因遗漏而意外使用空值。

    6.3 创建本地开发配置文件

    新建 application-local.properties,该文件用于连接本地 MySQL 和 Redis,不提交 Git。

    # BASIC
    SERVER_PORT=82
    FRONT_URL=http://localhost:81
    BACKEND_URL=http://localhost:82

    # MySQL
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_NAME=snowy
    DB_USER=root
    DB_PASSWORD=12345678

    # Redis
    REDIS_HOST=127.0.0.1
    REDIS_PORT=6379
    REDIS_PASSWORD=
    REDIS_DATABASE=1

    6.4 创建远程测试环境配置文件

    新建: application-dev.properties,用于连接公司测试环境的数据库和 Redis,不提交 Git。

    # BASIC
    SERVER_PORT=8082
    FRONT_URL=http://www.your-website.com
    BACKEND_URL=http://api.your-website.com

    # MySQL
    DB_HOST=rm-xxx.mysql.rds.aliyuncs.com
    DB_PORT=3306
    DB_NAME=snowy
    DB_USER=user1
    DB_PASSWORD=pwd123123

    # Redis
    REDIS_HOST=r-xxx.redis.rds.aliyuncs.com
    REDIS_PORT=6379
    REDIS_PASSWORD=pwd213213
    REDIS_DATABASE=1

    6.5 创建配置模板

    新建: application-demo.properties,作为给团队其他成员看的示例文件,需要提交 Git。

    # ========== 敏感配置示例(请复制此文件并去掉 -demo 后缀) ==========
    # BASIC
    SERVER_PORT=82
    FRONT_URL=http://localhost:81
    BACKEND_URL=http://localhost:82

    # MySQL
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_NAME=snowy
    DB_USER=root
    DB_PASSWORD=12345678

    # Redis
    REDIS_HOST=127.0.0.1
    REDIS_PORT=6379
    REDIS_PASSWORD=
    REDIS_DATABASE=1

    6.6 设置 .gitignore

    在snowy-web-app/src/main/resources目录下创建 .gitignore 文件:

    # 忽略所有环境真实配置(但保留 demo 模板)
    application-*.properties
    !application-demo.properties

    6.7 本地 IntelliJ IDEA 运行配置

    6.7.1 使用默认配置运行
    • 直接点击运行按钮(绿色三角形),Spring Boot 会读取 application.properties 中的默认值 spring.profiles.active=local,自动加载 application-local.properties。
    • 无需任何额外配置,团队成员克隆代码后即可运行。
    6.7.2 自定义配置运行

    如果需要调试测试环境的问题,可以创建一个独立的运行配置:

  • 打开 Run/Debug Configurations(点击工具栏运行配置下拉框 → Edit Configurations)。
  • 点击左上角Copy Configuration #D,复制现有的 Spring Boot 配置(如 Application),重命名为 Application-dev。
  • 在右侧 Active profiles 输入框中填写:dev。
  • 点击 OK 保存。
  • image-20260420142004615

    之后在运行下拉框中选择 Application-dev 并运行,即可加载 application-dev.properties 连接测试环境数据库。

    6.7.3 本地运行效果验证
    • 运行 Application-local:控制台日志显示 The following 1 profile is active: "local",连接本地数据库。
    • 运行 Application-dev:控制台日志显示 The following 1 profile is active: "dev",连接远程测试数据库。

    image-20260420142044143

    6.8 服务器部署时指定 Profile

    6.8.1 通过参数指定profile

    在测试或生产服务器上,通过 systemd 服务文件的 ExecStart 参数指定 profile:ExecStart=/usr/bin/java -jar /home/snowy/app/code/backend/api/snowy-web-app.jar –spring.profiles.active=test

    完整的/etc/systemd/system/snowy-web-app.service内容如下:

    # 测试服务器(/etc/systemd/system/snowy-web-app.service)
    [Unit]
    Description=Snowy Web Application
    After=network.target mysql.service redis.service

    [Service]
    Type=simple
    User=snowy
    Group=snowy
    WorkingDirectory=/home/snowy/app/logs/backend
    Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk"
    ExecStart=/usr/bin/java -jar /home/snowy/app/code/backend/api/snowy-web-app.jar –spring.profiles.active=test
    Restart=on-failure
    RestartSec=10

    [Install]
    WantedBy=multi-user.target

    生产环境将 test 改为 prod 即可。

    6.8.2 进阶:使用系统环境变量(更安全)

    如果不希望在任何配置文件中保存密码(即使是 application-prod.properties),可以在服务器的 systemd 服务中直接定义环境变量:

    [Service]
    Environment="DB_HOST=rm-xxxx.mysql.rds.aliyuncs.com"
    Environment="DB_PORT=3306"
    Environment="DB_NAME=snowy"
    Environment="DB_USER=user1"
    Environment="DB_PASSWORD=your_password"
    Environment="REDIS_HOST=r-xxxx.redis.rds.aliyuncs.com"
    Environment="REDIS_PORT=6379"
    Environment="REDIS_PASSWORD=your_redis_password"
    Environment="REDIS_DATABASE=1"
    Environment="SPRING_PROFILES_ACTIVE=prod"
    ExecStart=/usr/bin/java -jar /home/snowy/app/code/backend/api/snowy-web-app.jar

    这样,application.properties 中的占位符会从系统环境变量中取值,完全不需要任何 application-*.properties 文件,安全性最高。


    总结:通过 Profile 机制 + 占位符,实现了配置与代码的完全分离。本地开发默认使用 local profile,团队成员零配置即可运行;需要切换环境时,只需在 IDEA 中修改 Active profiles 或创建多个运行配置。测试/生产环境通过启动参数指定 profile,敏感信息可进一步托管到系统环境变量中。

    6.8.3 测试环境效果验证

    服务器上重启相关的命令:

    systemctl daemon-reload # 重新加载systemd配置文件 | 修改了/etc/systemd/system/snowy-web-app.service 文件后必须执行,否则修改不生效
    systemctl enable snowy-web-app # 设置开机自启 | 首次部署时执行一次,确保服务器重启后服务自动运行
    systemctl start snowy-web-app # 启动服务 | 服务未运行时首次启动
    systemctl stop snowy-web-app # 停止服务 | 需要暂停服务时使用(如维护数据库)
    systemctl restart snowy-web-app # 重启服务 | 修改配置或更新 JAR 包后,需要重启使改动生效
    systemctl status snowy-web-app # 查看服务状态 | 检查服务是否正常运行、查看最近日志、排查问题时

    image-20260420144257701


    七、服务器自动化部署脚本

    7.1 后端部署脚本

    将以下脚本保存到指定目录下的 deploy-backend.sh:

    #!/bin/bash
    set -e

    BACKEND_DIR="/home/snowy/app/code/git/snowy-backend"
    JAR_LINK="/home/snowy/app/code/backend/api/snowy-web-app.jar"

    echo ">>> 拉取最新代码…"
    cd $BACKEND_DIR
    git pull origin main

    echo ">>> 编译打包…"
    mvn clean install -DskipTests -q
    cd snowy-web-app
    mvn clean package -DskipTests -q

    echo ">>> 更新软链接…"
    # 升级版本时(如从 3.0.0 升级到 3.0.1),记得同步修改脚本中的文件名
    ln -sf $BACKEND_DIR/snowy-web-app/target/snowy-web-app-3.0.0.jar $JAR_LINK

    echo ">>> 重启服务…"
    systemctl restart snowy-web-app

    echo ">>> 后端部署完成!"

    # 如果修改了 /etc/systemd/system/snowy-web-app.service 文件需要执行下面一行
    # systemctl daemon-reload

    echo ">>> 等待5秒后,查看服务状态:systemctl status snowy-web-app"
    sleep 5
    systemctl status snowy-web-app

    添加执行权限:deploy-backend.sh

    对应的 /etc/systemd/system/snowy-web-app.service 文件应如下(指定test环境):

    Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk"
    # 环境切换:测试环境用 –spring.profiles.active=test,正式环境将下面改为 –spring.profiles.active=prod
    # 如果修改了这个文件,需要执行 systemctl daemon-reload
    ExecStart=/usr/bin/java -jar /home/snowy/app/code/backend/api/snowy-web-app.jar –spring.profiles.active=test

    7.2 前端构建脚本

    保存为指定目录的 build-frontend.sh:

    #!/bin/bash
    set -e

    ENV=${1:-test} # 默认 test,可传 test/production

    FRONTEND_DIR="/home/snowy/app/code/git/snowy-frontend"
    DEPLOY_DIR="/home/snowy/app/code/frontend/pc-web"

    echo ">>> 拉取最新代码…"
    cd $FRONTEND_DIR
    git pull origin main

    echo ">>> 安装依赖…"
    npm install

    echo ">>> 构建 $ENV 环境包…"
    if [ "$ENV" = "test" ]; then
    npm run build:test
    else
    npm run build:prod
    fi

    echo ">>> 部署到目标目录…"
    rm -rf $DEPLOY_DIR/*
    cp -r dist/* $DEPLOY_DIR/

    echo ">>> 设置权限…"
    chmod -R 755 $DEPLOY_DIR

    echo ">>> 前端部署完成(环境:$ENV)"

    添加执行权限:build-frontend.sh

    7.3 日常使用

    # 后端更新(测试环境)
    ./deploy-backend.sh

    # 前端更新(生产环境)
    ./build-frontend.sh test | production


    八、合并 Snowy 官方更新

    Snowy 官方会不定期发布新版本(修复 bug、新增功能)。如何在不丢失自己团队定制内容的前提下,安全、高效地合并官方的新版本,以下是推荐的工作流和自动化脚本。

    8.1 Git 仓库设置

    • 公司私有仓库(origin)存放二次开发代码。
    • 添加官方仓库为远程上游(upstream)。

    git remote add upstream https://gitee.com/xiaonuobase/snowy.git

    8.2 手动更新合并流程

    # 1. 拉取官方最新代码
    git fetch upstream –tags

    # 2. 创建临时分支,基于官方最新 tag(例如 v3.6.5)
    git checkout -b upstream/v3.6.5 upstream/v3.6.5

    # 3. 切换到我们的主分支(如 main)
    git checkout main

    # 4. 合并官方更新(可能会产生冲突)
    git merge upstream/v3.6.5 –no-ff -m "Merge official v3.6.5"

    # 5. 解决冲突(保留自己的修改)
    # – 通常冲突发生在 pom.xml、application.properties、以及某些业务文件。
    # – 使用 IDE 或 vim 解决冲突,保留自己团队的配置。

    # 6. 提交合并结果,推送到私有仓库
    git push origin main

    # 7. 删除临时分支
    git branch -d upstream/v3.6.5

    8.3 自动化同步脚本

    以下脚本可以定期执行(例如每周一次),自动检测官方新 tag,尝试合并,并发送报告。

    #!/bin/bash
    # 文件名: merge-upstream.sh
    set -e

    REPO_DIR="/home/snowy/app/code/git/snowy-backend"
    cd $REPO_DIR

    # 获取当前最新本地 tag
    LOCAL_TAG=$(git describe –tags –abbrev=0 2>/dev/null || echo "none")

    # 获取官方最新 tag(需要网络)
    git fetch upstream –tags > /dev/null 2>&1
    UPSTREAM_TAG=$(git tag -l "v*" | sort -V | tail -n1)

    echo "本地版本: $LOCAL_TAG"
    echo "官方最新: $UPSTREAM_TAG"

    if [ "$LOCAL_TAG" = "$UPSTREAM_TAG" ]; then
    echo "已经是最新,无需合并。"
    exit 0
    fi

    # 创建临时分支并合并
    git checkout -b "auto-merge-$UPSTREAM_TAG" "upstream/$UPSTREAM_TAG"

    # 尝试合并到主分支
    git checkout main
    git pull origin main

    # 执行合并,允许空提交
    set +e
    git merge "auto-merge-$UPSTREAM_TAG" –no-edit –allow-unrelated-histories
    MERGE_EXIT=$?
    set -e

    if [ $MERGE_EXIT -ne 0 ]; then
    echo "ERROR: 合并冲突,需要人工解决。请手动处理。"
    echo "冲突文件列表:"
    git diff –name-only –diff-filter=U
    exit 1
    else
    echo "合并成功,无冲突。"
    # 打上新 tag
    git tag "$UPSTREAM_TAG"
    git push origin main –tags
    # 删除临时分支
    git branch -d "auto-merge-$UPSTREAM_TAG"
    fi

    echo "完成。"

    8.4 处理冲突

    当自动合并失败时,脚本会退出并提示冲突文件。此时需要人工介入:

    # 查看冲突文件
    git status

    # 手动解决冲突(使用 IDE 或 vim)
    # 解决后执行
    git add .
    git commit -m "Merge official $UPSTREAM_TAG, resolved conflicts"
    git push origin main

    8.5 注意事项

    • 永远不要在官方 tag 上直接开发,保持上游分支干净。
    • 自己的定制代码应集中在特定目录(如 snowy-plugin-biz),避免与官方核心代码大量冲突。
    • 对于 application.properties 等配置文件,建议使用 application-{profile}.properties 外部化,避免冲突。

    后续可根据团队规模引入 Jenkins、GitLab CI 等持续集成工具,将上述脚本集成到流水线中,实现全自动化。



    九、常见问题排查

    Q1:Nginx 返回 403 Forbidden

    • 检查 /home/snowy/app/code/frontend/pc-web 及父目录权限是否为 755。
    • 验证 nobody 用户能否读取:sudo -u nobody cat /home/snowy/app/code/frontend/pc-web/index.html

    Q2:后端启动失败,日志显示 “Permission denied”

    • 确保 /home/snowy/app/logs/backend 和 /home/snowy/app/data/uploads 目录存在且属主为 snowy:snowy。

    Q3:sudo 执行脚本时提示没有权限

    • 检查 visudo 中是否已添加 snowy 用户对 systemctl 相关命令的 NOPASSWD 权限。

    Q4:前端刷新页面 404

    • 确认 Nginx 配置中包含 try_files $uri $uri/ /index.html;

    Q5:Maven 依赖下载失败

    • 确认 /home/snowy/.m2/settings.xml 中镜像配置正确,且网络能访问 http://maven.aliyun.com/repository/public。

    十、总结

    本文采用安全、可扩展、易维护的设计思路,完成了 Snowy 框架的服务器生产环境部署:

    主题关键实践
    用户与权限 创建可登录的 snowy 用户,通过 sudo 限制其仅能操作服务,无法修改系统
    私有仓库 将官方源码迁移到公司自己的 Git 仓库,部署时从私有仓库克隆
    目录结构 所有项目文件集中在 /home/snowy/app 下,未来可扩展多个项目
    自动化脚本 deploy-backend.sh 和 build-frontend.sh 实现一键更新
    进程管理 systemd 服务以 snowy 用户运行,开机自启,崩溃自动重启
    Nginx 集成 静态文件 + 反向代理,支持 Vue Router history 模式

    通过遵循最小权限原则和清晰的目录规划,我们既保证了安全性,又为未来的扩展和自动化留下了充足空间。

    未来扩展考虑

    本文采用了 git pull + 手动执行脚本 的方式,这已经能够满足小型团队的需求。但对于更专业的持续集成/持续部署(CI/CD),你可以考虑:

    • Jenkins:配置 webhook,当代码推送到 Git 仓库时自动触发构建和重启。
    • GitLab CI:在仓库中编写 .gitlab-ci.yml,实现一键发布。
    • Ansible:编写 playbook,实现多服务器批量部署。

    后续有时间了,我可能会在后续文章中详细介绍。目前,我们的手动脚本已经为自动化打下了良好基础(脚本可以很容易地被 CI 工具调用)。

    文章说明

    • 本文部分内容由DeepSeek生成,前提是我给DeepSeek投喂了很多我的想法 _
    • 本文历经4天时间撰写和精心打磨,已排查和修正各类细节问题,各位看官可以放心阅读和自行调试。
    • 本文版权归 CSDN@码农兴哥 所有,未经允许请勿转载,翻版必究!

    参考链接

    • Snowy 官方仓库:https://gitee.com/xiaonuobase/snowy
    • Snowy 官方文档:https://xiaonuo.vip/doc
    • Spring Boot Profile 文档:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles
    • Nginx 配置静态文件权限:https://nginx.org/en/docs/ngx_core_module.html#user
    • Vite 环境变量:https://vitejs.dev/guide/env-and-mode.html

    本文基于 Snowy v3.6.3 版本,部署环境为 阿里云 Linux 4 / CentOS 7+。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Java Snowy 框架生产环境安全部署全流程(服务器篇)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!