/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
系列文章: 分布式系统开发(1)——MinIO 对象存储 分布式系统开发(2)——Redis 缓存与分布式锁
分布式系统开发(3)——RabbitMQ独立模块
分布式系统开发(4)——Dubbo+Zookeeper(RPC服务)
分布式系统开发(5)——整合所有组件
分布式系统开发(6)——项目打包部署与Nginx代理前端
本文在 分布式系统开发(5)——整合所有组件 基础上,进行打包和部署工作。
1. 后端打包
1.1 打包过程和原理
Maven多模块打包流程:
执行 mvn clean install
↓
1. 按依赖顺序编译各模块
ds-api → common → ds-rabbit → ds-provider & ds-consumer
↓
2. 将每个模块打成 JAR 包
├── ds-api-1.0-SNAPSHOT.jar (接口库)
├── common-1.0-SNAPSHOT.jar (工具库)
├── ds-rabbit-1.0-SNAPSHOT.jar (MQ库)
├── ds-provider-1.0-SNAPSHOT.jar (可执行JAR) ← 包含所有依赖
└── ds-consumer-1.0-SNAPSHOT.jar (可执行JAR) ← 包含所有依赖
↓
3. 安装到本地 Maven 仓库 (~/.m2/repository)
供其他模块引用
Spring Boot打包做了什么
spring-boot-maven-plugin 的作用:
├── 把你的代码打包进去
├── 把所有依赖的 JAR 包也打包进去(BOOT-INF/lib/)
├── 添加启动器(Main-Class、Start-Class)
└── 生成可执行的 Fat JAR(胖包)
所以生成的 JAR 包很大(30-40MB),因为包含了运行所需的一切。
| common/target/ | common-1.0-SNAPSHOT.jar | ❌ 普通库,被其他模块依赖 |
| ds-api/target/ | ds-api-1.0-SNAPSHOT.jar | ❌ 接口定义,被其他模块依赖 |
| ds-rabbit/target/ | ds-rabbit-1.0-SNAPSHOT.jar | ❌ 被 ds-consumer 依赖 |
| ds-provider/target/ | ds-provider-1.0-SNAPSHOT.jar | ✅ 可执行JAR |
| ds-consumer/target/ | ds-consumer-1.0-SNAPSHOT.jar | ✅ 可执行JAR |
这个项目是分布式项目,主要由三部分构成,ds-provider、ds-consumer、ds-rabbit模块,原则上这三个模块可以独立打包,独立部署。但是本文在ds-consumer已经整合了RabbitMQ功能,所以只打包ds-provier、ds-consumer两个包。
(所以,我们只在ds-provider、ds-consumer两个模块加了build标签)
1.2 修改代码
先打开根目录的pom.xml,增加打包内容
添加完后,整个pom.xml参考如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.guo</groupId>
<artifactId>ds-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>ds-rabbit</module>
<module>ds-api</module>
<module>ds-provider</module>
<module>ds-consumer</module>
<module>common</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<minio.version>8.4.3</minio.version>
<dubbo.version>3.3.0</dubbo.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!– Dubbo 核心依赖 –>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!– Dubbo 依赖的 ZooKeeper 客户端(Curator) –>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
修改provider的依赖文件,增加打包相关配置
增加完后,这个模块的代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.guo</groupId>
<artifactId>ds-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>ds-provider</artifactId>
<dependencies>
<!— Dubbo starter —>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.guo</groupId>
<artifactId>ds-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<!–打包相关配置 —>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
然后,修改consumer模块的内容,增加打包相关配置
consumer模块的pom.xml的配置内容参考如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.guo</groupId>
<artifactId>ds-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>ds-consumer</artifactId>
<dependencies>
<!– Dubbo starter –>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.guo</groupId>
<artifactId>ds-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.guo</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.guo</groupId>
<artifactId>ds-rabbit</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.3 打包
打开右侧的maven,找到ds-demo(这个是根目录) 然后,双击下面的install (就是mvn clean install 命令)
然后,下面的控制台开始输出内容。

先找到provier模块的包,在当前模块的 target目录下 这个ds-provider-1.0-SNAPSHOT.jar就是打包后的,可以把它放到服务器上 
找到consumer模块的包,在当前模块的target目录下。 这个ds-consumer-1.0-SNAPSHOT.jar就是打包后的,可以复制它到服务器上。 
打包完后,我们有 了两个jar包,我们复制一下这两个jar包到一个文件夹(这个文件夹一般是服务器上的,但是为了测试目前就放到windows上某一个文件夹)
这里,如果你觉得名字太长可以简单修改一下jar包的名字。
2.前端打包与nginx代理
正常情况下, 前端一般是一个项目,比如vue,需要先执行前端的打包命令(一般是npm run build),将项目打包,然后再部署到nginx中。本项目的前端目前只有一个html页面。不用额外打包,直接挂载到nginx中即可。 找到上一章节的upload.html。 这个upload.html的参考代码如下:
<!–启动所有模块后,运行此文件,注意端口号–>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MinIO 上传下载工具</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: "Microsoft Yahei", sans-serif;
}
body {
padding: 30px;
max-width: 900px;
margin: 0 auto;
}
.box {
margin-bottom: 20px;
}
h3 {
margin-bottom: 12px;
color: #222;
}
label {
display: inline-block;
margin: 8px 0 4px;
}
input[type="text"],
input[type="file"] {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
button {
margin-top: 12px;
padding: 8px 20px;
background-color: #1677ff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #0958d9;
}
/* 分割线 */
.divider {
border: none;
border-top: 2px solid #eee;
margin: 30px 0;
}
/* JSON 结果展示区 */
#uploadResult {
margin-top: 15px;
padding: 12px;
background: #f6f8fa;
border-radius: 4px;
min-height: 60px;
white-space: pre-wrap;
font-family: Consolas, monospace;
font-size: 13px;
color: #333;
}
</style>
</head>
<body>
<!— 上半部分:头像上传 —>
<div class="box">
<h3>用户头像上传</h3>
<label>上传接口地址:</label>
<input type="text" id="uploadUrl" value="http://localhost:8084/api/user/avatar">
<form id="uploadForm" enctype="multipart/form-data">
<label>用户ID:</label>
<input type="text" name="userId" id="userIdInput" placeholder="请输入用户ID">
<label>选择文件:</label>
<input type="file" name="file" id="fileInput">
<br>
<button type="submit">开始上传</button>
</form>
<!— 上传返回 JSON 展示区域 —>
<div id="uploadResult"></div>
</div>
<!— 中间分割线 —>
<hr class="divider">
<!— 下半部分:文件下载 —>
<div class="box">
<h3>文件下载</h3>
<label>文件下载地址:</label>
<input type="text" id="downloadUrl" placeholder="请输入文件下载链接">
<br>
<button id="downloadBtn">开始下载</button>
</div>
<script>
// 上传逻辑
const uploadForm = document.getElementById('uploadForm');
const uploadUrl = document.getElementById('uploadUrl');
const uploadResult = document.getElementById('uploadResult');
uploadForm.addEventListener('submit', async function (e) {
e.preventDefault();
const url = uploadUrl.value.trim();
if (!url) {
uploadResult.innerText = '请填写上传接口地址';
return;
}
try {
const formData = new FormData(this);
const res = await fetch(url, {
method: 'POST',
body: formData
});
const data = await res.json();
// 格式化 JSON 展示
uploadResult.innerText = JSON.stringify(data, null, 2);
} catch (err) {
uploadResult.innerText = `请求异常:${err.message}`;
}
});
// 下载逻辑
const downloadUrl = document.getElementById('downloadUrl');
const downloadBtn = document.getElementById('downloadBtn');
downloadBtn.addEventListener('click', function () {
const url = downloadUrl.value.trim();
if (!url) {
alert('请填写下载地址');
return;
}
// 打开新标签页/直接触发浏览器下载
window.open(url, '_blank');
});
</script>
</body>
</html>
找到nginx配置,把这个文件放到ngingx目录中,

把upload.html放到这里面 然后打开conf文件夹,修改nginx.conf


改成下面这样:

3.测试场景一
3.1 场景描述:
只有一个电脑,这台电脑上部署 前端、后端Spring Boot和各种服务(比如minio、 redis)。这是我们的默认场景。 过程:
3.2 启动服务
minio、rabbitMQ、zookeeper、redis等服务全部启动
注意本地JDK问题,我们的项目是在IDEA中用的JDK17,现在要运行打包的代码,那么本身(服务器)环境也要是JDK17,保持一致。
3.3 启动后端:
我们 把刚才打包好的两个jar包,复制到一个文件夹中,然后在文件处按住 shift+鼠标右键。
然后选择”在此处打开Power shell窗口“ 
先输入第一个指令,启动provider包
java -jar ds-provider-1.0-SNAPSHOT.jar

然后,上面的窗口不要关掉。
再在空白处 按住shift+鼠标右键,选择 ”在此处打开powershell窗口“ 然后再输入
java -jar ds-consumer-1.0-SNAPSHOT.jar


3.5 启动前端
双击ngingx.exe,它会一闪而过,这是正常的。 
3.6 测试
在浏览器中打开localhost:8085 (因为前面nginx.conf中配置的端口是8085)

在浏览器中打开localhost:8085 ,就会看到下面的界面

先填写用户ID,再从本地上传一个文件,然后,点击”开始上传“ 
根据网络情况,然后就可以看到返回的信息了。 
注意,文件下载功能不好用,因为代码里面没写文件下载的代码。
4.测试场景二
4.1 场景描述
现在有两台电脑,两台电脑叫A和B,通过局域网连接。 A电脑的IP,通过ipconfig查看是192.168.66.188 
B电脑的IP,通过ipconfig查看是192.168.66.216 
A电脑ping一下B电脑,看是否连通
B电脑ping一下A电脑,看是否连通 
电脑A放后端Spring Boot和各种服务。电脑B存放Nginx相关(配置和第2部分:前端打包与nginxd代理完全一致)
ping不通的,关闭防火墙。

4.2 启动服务
注意,一定要先 切换网络,再启动服务。 电脑A先启动各种服务(redis、RabbitMQ、Dubbo等) 保持默认配置即可。
4.3 启动后端
整个后端都在电脑A上。
跟上面的操作一样,找到打包好的jar包 、
按住shift+鼠标右键, 然后选择"在此处打开Power shell窗口",然后执行第一个命令
java -jar ds-provider-1.0-SNAPSHOT.jar
然后,上面的窗口最小化。
再次按住shift+鼠标右键,然后选择”在此处打开Power shell窗口“,然后执行第二个命令
java -jar ds-consumer-1.0-SNAPSHOT.jar
这个窗口也最小化。

4.3 启动前端
前端在电脑B上,配置方式和上面的一样(upload.html也需要放到nginx中html文件夹下面) 
电脑B上的ngingx.conf配置文件,可以参考下面内容(注意端口号):

然后,再电脑B上启动nginx,打开浏览器,访问
localhost:8085
8085是nginx设置的端口。
4.4 测试
在B电脑,打开网站localhost:8085,
然后在下面的”上传接口地址“栏,写上
http://192.168.66.188:8084/api/user/avatar
这里的地址,就是A电脑的地址和端口。 然后输入用户ID,再选择文件,然后点击”开始上传“ 就可以看到上传成功了。 
5.测试场景三
5.1 场景描述
现在有两台电脑,两台电脑叫A和B,通过局域网连接。 A电脑的IP,通过ipconfig查看是192.168.66.188 
B电脑的IP,通过ipconfig查看是192.168.66.216 
A电脑ping一下B电脑,看是否连通
B电脑ping一下A电脑,看是否连通 
电脑A放各种服务。电脑B放前后端。
ping不通的,关闭防火墙。

5.2 修改RabbitMQ
RabbitMQ默认的guest账号和密码,只允许本地登录,我们使用上面的方式会报错”User can only log in via localhost“,有两种解决办法,一种是修改本地guest账号的权限,另外一种是新建账号。实际开发中一般采用第二种方法。
关于创建用户的方式也有两种,一种是使用命令行的方式, 另外一种是使用界面。前面的文章中,已经安装完RabbitMQ的界面插件了,所以直接登录访问即可,采用第二种方式。
http://localhost:15672/
用户名和密码,默认guest
点击”Admin“这个选项卡
然后,点击下面的”Add a user“
然后,输入Username。这里输入admin password输入123456,一共输入2次。 右侧,选”Admin“ 然后,点击下面的”Add user“
输入完了后,页面就能看到刚才添加的admin用户。 然后,点击”admin“
可以看到 配置权限界面。 然后点击下图的”Set permission“按钮。
点击完了,页面会出现下面的内容,可以看到当前的权限。 
5.3 修改redis配置
主要绑定IP的问题。 打开Redis配置文件,redis.windows.conf
找到bind 127.0.0.1
注释掉后,界面如下。
然后,保存一下文件,重启redis。
5.2 修改代码
因为后端代码和服务不在一块,所以后端代码不能访问localhost了,需要访问电脑A的地址。
需要找到配置文件中的localhost。
点击IDEA的右上角的放大镜。
把下面这些都改成A服务器的地址(192.168.66.188)
也可以参考下面一个一个修改:
(1)provider模块 
provider模块的配置文件修改完后如下:
server:
port: 8090 # HTTP 管理端口(健康检查等)
dubbo:
application:
name: demo–provider # 服务名称,在注册中心显示
registry:
address: zookeeper://192.168.66.188:2181 # 注册中心地址
protocol:
name: dubbo
port: 20880 # Dubbo 服务端口
(2) 修改consumer模块的yml文件。
修改完后的配置文件参考如下:
server:
port: 8084 # 消费者 HTTP 服务端口
dubbo:
application:
name: demo–consumer
qos-enable: false # 禁用 QoS 服务,避免端口冲突
registry:
address: zookeeper://192.168.66.188:2181
consumer:
check: false # 启动时不检查服务提供者是否存在(避免启动报错)
#timeout: 5000 # 全局调用超时5秒,覆盖默认1000ms
minio:
endpoint: http://192.168.66.188:9000
access-key: minioadmin
secret-key: minioadmin
bucket: online–file # 默认桶名称(可自定义)
connect-timeout: 60s
write-timeout: 60s
read-timeout: 60s
spring:
data:
redis:
host: 192.168.66.188
port: 6379
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
rabbitmq:
host: 192.168.66.188
port: 5672
username: admin
password: 123456
virtual-host: /
(3) 修改rabbitMQ模块

配置文件如下:
server:
port: 8083
spring:
rabbitmq:
host: 192.168.66.188
port: 5672
username: admin
password: 123456
virtual-host: /
(4)修改根目录的pom.xml
需要在pom.xml增加一个
<skipTests>true</skipTests>
这个表示跳过测试, 不然的话,因为需要连RabbitMQ,会报错。 
5.3 重新打包
找到根目录的 ”生命周期“,双击”install“ 
然后,还是把ds-porvider模块的target目录下的 这个 xxx.jar 复制到B电脑上。
再把 ds-consumer模块的xxx.jar复制到B服务器上。 
5.4 启动服务
注意,一定要先 切换网络,再启动服务。 在A电脑中启动各种服务(redis、RabbitMQ、Dubbo等) 保持默认配置即可。
5.5 启动后端
现在前端和后端都在B电脑上。 电脑B开两个cmd。和上面的流程一样。 在存放jar包的地方,按住shift+鼠标右键,然后选择”在此处打开PowerShell窗口“
分别输入下面的两个命令,启动两个jar包
java -jar ds-provider-1.0-SNAPSHOT.jar
java -jar ds-consumer-1.0-SNAPSHOT.jar
下图是在B电脑上启动后的界面。 
5.6 启动前端
前端的配置和前面一样,不用更改,打开浏览器,输入
localhost:8085
5.7 测试
这里上传接口地址是:
http://localhost:8084/api/user/avater
这里访问localhost:8084是因为后端就在B电脑上。
然后输入用户ID,再选择一个文件。
然后点击”开始上传“ (根据网络条件,稍微等一下)

就可以看到上传成功了,右侧consumer窗口下方可以看到上传成功后,输出的一些内容。
场景三打包的jar分享: 场景三打包链接: (1) 百度网盘:通过网盘分享的文件:场景三打包 链接: https://pan.baidu.com/s/1WAmDCief_K0q73HLW2fjRg?pwd=cyae 提取码: cyae
(2)夸克网盘:
我用夸克网盘给你分享了「场景三打包」,点击链接或复制整段内容,打开「夸克APP」即可获取。 /50573b4iVa😕 链接:https://pan.quark.cn/s/f5bdc14db6a0?pwd=Nd4g 提取码:Nd4g



