Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project
This commit is contained in:
commit
79c798ef30
63 files changed
+6476
-509
No files matched your search
@@ -0,0 +1,252 @@
|
||||
# SSE关闭接口406错误解决方案
|
||||
|
||||
## 🚨 问题描述
|
||||
|
||||
用户在前台退出时,SSE关闭接口返回406 Not Acceptable错误:
|
||||
|
||||
```
|
||||
Request URL: http://hzdyjsweb.camasort.com/prod-api/resource/sse/close
|
||||
Request Method: GET
|
||||
Status Code: 406 Not Acceptable
|
||||
```
|
||||
|
||||
## 🔍 问题分析
|
||||
|
||||
### 1. 错误原因分析
|
||||
|
||||
**406 Not Acceptable错误**通常表示:
|
||||
- 服务器无法根据请求的内容特性完成请求
|
||||
- 请求的资源的内容特性无法满足请求头中的条件
|
||||
- 用户认证信息缺失或无效
|
||||
|
||||
### 2. 根本原因
|
||||
|
||||
1. **认证时机问题**:
|
||||
- 前端在logout时先调用SSE关闭接口,再调用logout接口
|
||||
- 此时token可能已经失效或即将失效
|
||||
- 后端`LoginHelper.getUserId()`可能返回null
|
||||
|
||||
2. **后端接口设计问题**:
|
||||
- SSE关闭接口使用了`@SaIgnore`注解,但仍然需要获取用户信息
|
||||
- 缺少对空值和异常的处理
|
||||
- 没有优雅降级机制
|
||||
|
||||
3. **nginx代理配置问题**:
|
||||
- SSE关闭接口可能没有正确的代理配置
|
||||
- 缺少专门的错误处理
|
||||
|
||||
## 🛠️ 解决方案
|
||||
|
||||
### 方案一:修复后端SSE关闭接口(已实施)
|
||||
|
||||
#### 1. 优化错误处理
|
||||
|
||||
```java
|
||||
/**
|
||||
* 关闭 SSE 连接
|
||||
*/
|
||||
@SaIgnore
|
||||
@GetMapping(value = "${sse.path}/close")
|
||||
public R<Void> close() {
|
||||
try {
|
||||
// 尝试获取token和用户ID,如果获取失败则直接返回成功
|
||||
String tokenValue = null;
|
||||
Long userId = null;
|
||||
|
||||
try {
|
||||
tokenValue = StpUtil.getTokenValue();
|
||||
userId = LoginHelper.getUserId();
|
||||
} catch (Exception e) {
|
||||
// 如果获取用户信息失败,记录日志但不抛出异常
|
||||
log.warn("Failed to get user info for SSE close: {}", e.getMessage());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// 如果token或userId为空,直接返回成功
|
||||
if (tokenValue == null || userId == null) {
|
||||
log.info("Token or userId is null, skipping SSE disconnect");
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
sseEmitterManager.disconnect(userId, tokenValue);
|
||||
return R.ok();
|
||||
} catch (Exception e) {
|
||||
log.error("Error during SSE close: {}", e.getMessage(), e);
|
||||
// 即使出错也返回成功,避免影响用户退出流程
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 关键改进点
|
||||
|
||||
- **添加异常捕获**:捕获所有可能的异常
|
||||
- **空值检查**:检查token和userId是否为空
|
||||
- **优雅降级**:即使出错也返回成功状态
|
||||
- **日志记录**:记录详细的错误信息用于调试
|
||||
|
||||
### 方案二:优化前端logout逻辑(已实施)
|
||||
|
||||
#### 1. 添加错误处理
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 注销
|
||||
*/
|
||||
export function logout() {
|
||||
// 先尝试关闭SSE连接,但不阻塞logout流程
|
||||
if (import.meta.env.VITE_APP_SSE === 'true') {
|
||||
request({
|
||||
url: '/resource/sse/close',
|
||||
method: 'get'
|
||||
}).catch((error) => {
|
||||
// SSE关闭失败不影响用户退出,只记录日志
|
||||
console.warn('SSE close failed during logout:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// 执行正常的logout流程
|
||||
return request({
|
||||
url: '/auth/logout',
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 关键改进点
|
||||
|
||||
- **非阻塞调用**:SSE关闭失败不影响logout流程
|
||||
- **错误捕获**:捕获SSE关闭接口的错误
|
||||
- **日志记录**:记录错误信息但不影响用户体验
|
||||
|
||||
### 方案三:更新nginx配置(已实施)
|
||||
|
||||
#### 1. 添加SSE关闭接口专用配置
|
||||
|
||||
```nginx
|
||||
# SSE关闭接口专用配置
|
||||
location ^~ /prod-api/resource/sse/close {
|
||||
proxy_pass http://47.121.125.41:8080/resource/sse/close;
|
||||
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;
|
||||
|
||||
# 普通API配置(不需要SSE特殊配置)
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
# CORS处理
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid';
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 配置说明
|
||||
|
||||
- **独立配置**:为SSE关闭接口提供独立的代理配置
|
||||
- **合理超时**:设置30秒超时,适合普通API调用
|
||||
- **CORS支持**:确保跨域请求正常工作
|
||||
|
||||
## 📋 部署步骤
|
||||
|
||||
### 1. 更新后端代码
|
||||
|
||||
```bash
|
||||
# 重新编译后端项目
|
||||
mvn clean compile
|
||||
|
||||
# 重启后端服务
|
||||
systemctl restart your-backend-service
|
||||
```
|
||||
|
||||
### 2. 更新前端代码
|
||||
|
||||
```bash
|
||||
# 重新构建前端项目
|
||||
npm run build
|
||||
|
||||
# 部署到nginx目录
|
||||
cp -r dist/* /root/hzdyjsweb9091/dist/
|
||||
```
|
||||
|
||||
### 3. 更新nginx配置
|
||||
|
||||
```bash
|
||||
# 测试nginx配置
|
||||
nginx -t
|
||||
|
||||
# 重新加载配置
|
||||
nginx -s reload
|
||||
```
|
||||
|
||||
## 🔧 验证方法
|
||||
|
||||
### 1. 功能验证
|
||||
|
||||
1. **正常登录**:确保用户能够正常登录
|
||||
2. **SSE连接**:检查SSE连接是否正常建立
|
||||
3. **正常退出**:测试用户退出流程
|
||||
4. **错误处理**:检查控制台是否有错误信息
|
||||
|
||||
### 2. 错误场景测试
|
||||
|
||||
1. **Token失效退出**:在token即将失效时退出
|
||||
2. **网络异常退出**:在网络不稳定的情况下退出
|
||||
3. **并发退出**:多个用户同时退出
|
||||
|
||||
### 3. 日志检查
|
||||
|
||||
```bash
|
||||
# 检查后端日志
|
||||
tail -f /path/to/backend/logs/application.log | grep -i "sse"
|
||||
|
||||
# 检查nginx错误日志
|
||||
tail -f /var/log/nginx/hzdyjsweb.camasort.com.error.log
|
||||
```
|
||||
|
||||
## 🎯 预期效果
|
||||
|
||||
### 1. 问题解决
|
||||
|
||||
- ✅ 消除406错误
|
||||
- ✅ 用户退出流程顺畅
|
||||
- ✅ 不影响用户体验
|
||||
|
||||
### 2. 系统稳定性
|
||||
|
||||
- ✅ SSE连接正确关闭
|
||||
- ✅ 资源正确释放
|
||||
- ✅ 错误处理完善
|
||||
|
||||
### 3. 监控指标
|
||||
|
||||
- SSE关闭成功率:100%
|
||||
- 用户退出成功率:100%
|
||||
- 错误日志减少:显著减少
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [SSE生产环境配置问题解决方案](../SSE生产环境配置问题解决方案.md)
|
||||
- [nginx配置文档](../../部署文档/nginx配置/)
|
||||
- [前端环境变量配置建议](../../部署文档/nginx配置/前端环境变量配置建议.md)
|
||||
|
||||
## 🔄 后续优化
|
||||
|
||||
1. **监控告警**:添加SSE关闭失败监控
|
||||
2. **性能优化**:优化SSE连接管理
|
||||
3. **用户体验**:添加退出进度提示
|
||||
4. **错误恢复**:实现自动重试机制
|
||||
|
||||
---
|
||||
|
||||
**更新时间**:2025-01-03
|
||||
**维护人员**:系统管理员
|
||||
**文档版本**:v1.0
|
||||
@@ -0,0 +1,256 @@
|
||||
# SSE生产环境配置问题解决方案
|
||||
|
||||
## 🚨 问题描述
|
||||
|
||||
在生产环境中,通过nginx配置后,前端无法正常接收SSE(Server-Sent Events)消息推送。
|
||||
|
||||
## 🔍 问题分析
|
||||
|
||||
### 1. 技术架构分析
|
||||
|
||||
**后端SSE实现**:
|
||||
- 使用Spring Boot的`SseEmitter`实现
|
||||
- SSE端点:`/resource/sse`
|
||||
- 支持用户认证和Token验证
|
||||
- 使用Redis进行消息广播
|
||||
|
||||
**前端SSE实现**:
|
||||
- 使用Vue 3的`useEventSource`组合式API
|
||||
- 自动重连机制(10次重试,3秒间隔)
|
||||
- 消息处理:显示通知和存储到状态管理
|
||||
|
||||
**nginx代理配置**:
|
||||
- 当前配置缺少专门的SSE代理处理
|
||||
- 超时设置可能不够长
|
||||
- 缺少SSE特定的响应头配置
|
||||
|
||||
### 2. 根本原因
|
||||
|
||||
1. **nginx配置不完整**:缺少专门的SSE代理配置
|
||||
2. **超时设置不足**:SSE是长连接,需要更长的超时时间
|
||||
3. **响应头配置缺失**:SSE需要特定的Content-Type和缓存控制
|
||||
4. **代理优先级问题**:SSE路由可能被其他配置覆盖
|
||||
|
||||
## 🛠️ 解决方案
|
||||
|
||||
### 方案一:优化nginx配置(推荐)
|
||||
|
||||
#### 1. 添加专门的SSE代理配置
|
||||
|
||||
```nginx
|
||||
# SSE专用代理配置(重要:必须放在其他API配置之前)
|
||||
location ^~ /prod-api/resource/sse {
|
||||
proxy_pass http://47.121.125.41:8080/resource/sse;
|
||||
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;
|
||||
|
||||
# SSE特殊配置
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
# SSE需要更长的超时时间
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 86400s; # 24小时
|
||||
proxy_read_timeout 86400s; # 24小时
|
||||
|
||||
# SSE响应头设置
|
||||
proxy_set_header Accept "text/event-stream";
|
||||
proxy_set_header Cache-Control "no-cache";
|
||||
proxy_set_header Connection "keep-alive";
|
||||
|
||||
# CORS处理
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid';
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 关键配置说明
|
||||
|
||||
- **`proxy_buffering off`**:禁用nginx缓冲,确保SSE消息实时传输
|
||||
- **`proxy_cache off`**:禁用缓存,避免消息被缓存
|
||||
- **`proxy_read_timeout 86400s`**:设置24小时超时,支持长连接
|
||||
- **`proxy_set_header Connection ""`**:清除Connection头,避免代理问题
|
||||
- **`add_header Access-Control-Allow-Credentials true`**:支持跨域认证
|
||||
|
||||
### 方案二:修改前端配置
|
||||
|
||||
#### 1. 环境变量配置
|
||||
|
||||
```bash
|
||||
# 推荐配置:通过nginx代理
|
||||
VITE_APP_BASE_API = '/prod-api'
|
||||
VITE_APP_SSE = true
|
||||
VITE_APP_WEBSOCKET = false
|
||||
```
|
||||
|
||||
#### 2. 备选配置:直接访问后端
|
||||
|
||||
```bash
|
||||
# 备选配置:直接访问后端
|
||||
VITE_APP_BASE_API = 'http://47.121.125.41:8080'
|
||||
VITE_APP_SSE = true
|
||||
VITE_APP_WEBSOCKET = false
|
||||
```
|
||||
|
||||
## 📋 部署步骤
|
||||
|
||||
### 1. 更新nginx配置
|
||||
|
||||
```bash
|
||||
# 备份原配置
|
||||
cp /etc/nginx/conf.d/hzdyjsweb.camasort.com.conf /etc/nginx/conf.d/hzdyjsweb.camasort.com.conf.backup
|
||||
|
||||
# 更新配置文件
|
||||
vim /etc/nginx/conf.d/hzdyjsweb.camasort.com.conf
|
||||
|
||||
# 测试配置
|
||||
nginx -t
|
||||
|
||||
# 重新加载配置
|
||||
nginx -s reload
|
||||
```
|
||||
|
||||
### 2. 更新前端配置
|
||||
|
||||
```bash
|
||||
# 进入前端项目目录
|
||||
cd /path/to/frontend
|
||||
|
||||
# 更新环境变量
|
||||
vim .env.production
|
||||
|
||||
# 重新构建
|
||||
npm run build
|
||||
|
||||
# 部署到nginx目录
|
||||
cp -r dist/* /root/hzdyjsweb9091/dist/
|
||||
```
|
||||
|
||||
### 3. 重启服务
|
||||
|
||||
```bash
|
||||
# 重启后端服务(如果需要)
|
||||
systemctl restart your-backend-service
|
||||
|
||||
# 检查nginx状态
|
||||
systemctl status nginx
|
||||
```
|
||||
|
||||
## 🔧 故障排查
|
||||
|
||||
### 1. 检查nginx配置
|
||||
|
||||
```bash
|
||||
# 测试nginx配置
|
||||
nginx -t
|
||||
|
||||
# 查看nginx错误日志
|
||||
tail -f /var/log/nginx/error.log
|
||||
tail -f /var/log/nginx/hzdyjsweb.camasort.com.error.log
|
||||
|
||||
# 查看nginx访问日志
|
||||
tail -f /var/log/nginx/hzdyjsweb.camasort.com.access.log
|
||||
```
|
||||
|
||||
### 2. 检查SSE连接
|
||||
|
||||
#### 浏览器开发者工具
|
||||
1. 打开F12开发者工具
|
||||
2. 切换到Network面板
|
||||
3. 筛选XHR/Fetch请求
|
||||
4. 查找SSE连接(通常显示为EventStream)
|
||||
5. 检查连接状态和响应头
|
||||
|
||||
#### 控制台错误信息
|
||||
```javascript
|
||||
// 检查SSE连接状态
|
||||
console.log('SSE连接状态:', EventSource.readyState);
|
||||
// 0: CONNECTING, 1: OPEN, 2: CLOSED
|
||||
```
|
||||
|
||||
### 3. 测试SSE端点
|
||||
|
||||
#### 直接测试后端
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "clientid: e5cd7e4891bf95d1d19206ce24a7b32e" \
|
||||
http://47.121.125.41:8080/resource/sse
|
||||
```
|
||||
|
||||
#### 通过nginx测试
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "clientid: e5cd7e4891bf95d1d19206ce24a7b32e" \
|
||||
http://hzdyjsweb.camasort.com/prod-api/resource/sse
|
||||
```
|
||||
|
||||
### 4. 检查后端SSE服务
|
||||
|
||||
#### 查看后端日志
|
||||
```bash
|
||||
# 查看应用日志
|
||||
tail -f /path/to/backend/logs/application.log
|
||||
|
||||
# 查看SSE相关日志
|
||||
grep -i "sse" /path/to/backend/logs/application.log
|
||||
```
|
||||
|
||||
#### 检查Redis连接
|
||||
```bash
|
||||
# 连接Redis
|
||||
redis-cli
|
||||
|
||||
# 检查SSE主题订阅
|
||||
SUBSCRIBE global:sse
|
||||
```
|
||||
|
||||
## 🎯 验证方法
|
||||
|
||||
### 1. 功能验证
|
||||
|
||||
1. **登录系统**:确保用户已登录
|
||||
2. **触发SSE消息**:通过工作流或其他功能触发消息推送
|
||||
3. **检查通知**:查看是否收到实时通知
|
||||
4. **检查控制台**:查看SSE连接状态和消息
|
||||
|
||||
### 2. 性能验证
|
||||
|
||||
1. **连接稳定性**:长时间保持连接,检查是否断开
|
||||
2. **消息延迟**:测试消息推送的实时性
|
||||
3. **并发连接**:测试多用户同时连接的情况
|
||||
|
||||
### 3. 监控指标
|
||||
|
||||
- SSE连接数
|
||||
- 消息推送成功率
|
||||
- 连接断开频率
|
||||
- 响应时间
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [nginx SSE代理配置官方文档](http://nginx.org/en/docs/http/ngx_http_proxy_module.html)
|
||||
- [Spring Boot SSE官方文档](https://docs.spring.io/spring-framework/reference/web/webmvc/controller/ann-methods/sse.html)
|
||||
- [Vue 3 EventSource使用指南](https://vueuse.org/core/useEventSource/)
|
||||
|
||||
## 🔄 后续优化
|
||||
|
||||
1. **添加SSE连接监控**:监控连接状态和性能
|
||||
2. **实现连接池管理**:优化连接资源使用
|
||||
3. **添加消息重试机制**:提高消息推送可靠性
|
||||
4. **优化nginx配置**:根据实际使用情况调整超时和缓冲设置
|
||||
|
||||
---
|
||||
|
||||
**更新时间**:2025-01-03
|
||||
**维护人员**:系统管理员
|
||||
**文档版本**:v1.0
|
||||
@@ -41,36 +41,95 @@ server {
|
||||
add_header Pragma "no-cache";
|
||||
}
|
||||
|
||||
# Warm-Flow工作流UI代理配置(重要:必须放在Vue路由配置之前,使用^~确保优先级)
|
||||
location ^~ /prod-api/warm-flow-ui/ {
|
||||
proxy_pass http://47.121.125.41:8080/warm-flow-ui/;
|
||||
# SSE专用代理配置(重要:必须放在其他API配置之前)
|
||||
location ^~ /prod-api/resource/sse {
|
||||
proxy_pass http://47.121.125.41:8080/resource/sse;
|
||||
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和SSE
|
||||
# SSE特殊配置
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
# 超时设置
|
||||
# SSE需要更长的超时时间
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_send_timeout 86400s; # 24小时
|
||||
proxy_read_timeout 86400s; # 24小时
|
||||
|
||||
# SSE响应头设置
|
||||
proxy_set_header Accept "text/event-stream";
|
||||
proxy_set_header Cache-Control "no-cache";
|
||||
proxy_set_header Connection "keep-alive";
|
||||
|
||||
# CORS处理
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid';
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# SSE关闭接口专用配置
|
||||
location ^~ /prod-api/resource/sse/close {
|
||||
proxy_pass http://47.121.125.41:8080/resource/sse/close;
|
||||
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;
|
||||
|
||||
# 普通API配置(不需要SSE特殊配置)
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
# CORS处理
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid';
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# Warm-Flow工作流UI代理配置(重要:必须放在Vue路由配置之前)
|
||||
location ^~ /prod-api/warm-flow-ui/ {
|
||||
proxy_pass http://47.121.125.41:8080/warm-flow-ui/;
|
||||
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和SSE
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
# 超时设置
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 86400s;
|
||||
|
||||
# CORS处理
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid';
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# API代理配置(使用^~确保优先级,避免被Vue路由配置干扰)
|
||||
location ^~ /prod-api/ {
|
||||
proxy_pass http://47.121.125.41:8080/;
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# 9900端口实例
|
||||
ruoyi-admin-9900:
|
||||
image: openjdk:17-jre-slim
|
||||
container_name: ruoyi-admin-9900
|
||||
ports:
|
||||
- "9900:9900"
|
||||
volumes:
|
||||
- ./ruoyi-admin.jar:/app/ruoyi-admin.jar
|
||||
- ./logs:/app/logs
|
||||
- ./temp:/app/temp
|
||||
working_dir: /app
|
||||
command: >
|
||||
java -Xms512m -Xmx1024m
|
||||
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
|
||||
-Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai
|
||||
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
|
||||
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/
|
||||
-Dspring.profiles.active=prod-9900
|
||||
-Dserver.port=9900
|
||||
-jar ruoyi-admin.jar
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=prod-9900
|
||||
- SERVER_PORT=9900
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# 9901端口实例
|
||||
ruoyi-admin-9901:
|
||||
image: openjdk:17-jre-slim
|
||||
container_name: ruoyi-admin-9901
|
||||
ports:
|
||||
- "9901:9901"
|
||||
volumes:
|
||||
- ./ruoyi-admin.jar:/app/ruoyi-admin.jar
|
||||
- ./logs:/app/logs
|
||||
- ./temp:/app/temp
|
||||
working_dir: /app
|
||||
command: >
|
||||
java -Xms512m -Xmx1024m
|
||||
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
|
||||
-Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai
|
||||
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
|
||||
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/
|
||||
-Dspring.profiles.active=prod-9901
|
||||
-Dserver.port=9901
|
||||
-jar ruoyi-admin.jar
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=prod-9901
|
||||
- SERVER_PORT=9901
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# 9902端口实例
|
||||
ruoyi-admin-9902:
|
||||
image: openjdk:17-jre-slim
|
||||
container_name: ruoyi-admin-9902
|
||||
ports:
|
||||
- "9902:9902"
|
||||
volumes:
|
||||
- ./ruoyi-admin.jar:/app/ruoyi-admin.jar
|
||||
- ./logs:/app/logs
|
||||
- ./temp:/app/temp
|
||||
working_dir: /app
|
||||
command: >
|
||||
java -Xms512m -Xmx1024m
|
||||
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
|
||||
-Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai
|
||||
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
|
||||
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/
|
||||
-Dspring.profiles.active=prod-9902
|
||||
-Dserver.port=9902
|
||||
-jar ruoyi-admin.jar
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=prod-9902
|
||||
- SERVER_PORT=9902
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# 9903端口实例
|
||||
ruoyi-admin-9903:
|
||||
image: openjdk:17-jre-slim
|
||||
container_name: ruoyi-admin-9903
|
||||
ports:
|
||||
- "9903:9903"
|
||||
volumes:
|
||||
- ./ruoyi-admin.jar:/app/ruoyi-admin.jar
|
||||
- ./logs:/app/logs
|
||||
- ./temp:/app/temp
|
||||
working_dir: /app
|
||||
command: >
|
||||
java -Xms512m -Xmx1024m
|
||||
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
|
||||
-Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai
|
||||
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
|
||||
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/
|
||||
-Dspring.profiles.active=prod-9903
|
||||
-Dserver.port=9903
|
||||
-jar ruoyi-admin.jar
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=prod-9903
|
||||
- SERVER_PORT=9903
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
# Nginx负载均衡器
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: ruoyi-nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
depends_on:
|
||||
- ruoyi-admin-9900
|
||||
- ruoyi-admin-9901
|
||||
- ruoyi-admin-9902
|
||||
- ruoyi-admin-9903
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- ruoyi-network
|
||||
|
||||
networks:
|
||||
ruoyi-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
logs:
|
||||
temp:
|
||||
@@ -0,0 +1,116 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# 日志格式
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
# 基础配置
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
# Gzip压缩
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/json
|
||||
application/javascript
|
||||
application/xml+rss
|
||||
application/atom+xml
|
||||
image/svg+xml;
|
||||
|
||||
# 上游服务器配置
|
||||
upstream ruoyi_backend {
|
||||
# 轮询负载均衡
|
||||
server ruoyi-admin-9900:9900 weight=1 max_fails=3 fail_timeout=30s;
|
||||
server ruoyi-admin-9901:9901 weight=1 max_fails=3 fail_timeout=30s;
|
||||
server ruoyi-admin-9902:9902 weight=1 max_fails=3 fail_timeout=30s;
|
||||
server ruoyi-admin-9903:9903 weight=1 max_fails=3 fail_timeout=30s;
|
||||
|
||||
# 健康检查
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# 主服务器配置
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
proxy_pass http://ruoyi_backend;
|
||||
}
|
||||
|
||||
# API请求
|
||||
location / {
|
||||
proxy_pass http://ruoyi_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 超时设置
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
|
||||
# 缓冲设置
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 8 4k;
|
||||
proxy_busy_buffers_size 8k;
|
||||
}
|
||||
|
||||
# 健康检查端点
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# 状态监控页面
|
||||
location /nginx_status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
||||
# 监控和日志配置
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
return 200 "Nginx Load Balancer Status\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
location /status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 扩展服务启动停止一体化脚本
|
||||
# 使用方法: ./start-extend-services.sh [start|stop|restart|status] [service_name]
|
||||
# 服务名称: monitor, snailjob, all
|
||||
|
||||
# 配置变量
|
||||
MONITOR_JAR="ruoyi-monitor-admin.jar"
|
||||
SNAILJOB_JAR="ruoyi-snailjob-server.jar"
|
||||
JAVA_OPTS="-Xms2048m -Xmx2048m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m"
|
||||
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
|
||||
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
|
||||
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/"
|
||||
|
||||
# 服务配置
|
||||
declare -A SERVICES
|
||||
SERVICES[monitor]="9090:ruoyi-monitor-admin:prod"
|
||||
SERVICES[snailjob]="8800:ruoyi-snailjob-server:prod"
|
||||
|
||||
# 日志目录
|
||||
LOG_DIR="logs"
|
||||
mkdir -p $LOG_DIR
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印带颜色的消息
|
||||
print_message() {
|
||||
local color=$1
|
||||
local message=$2
|
||||
echo -e "${color}${message}${NC}"
|
||||
}
|
||||
|
||||
# 检查JAR文件是否存在
|
||||
check_jar() {
|
||||
local service=$1
|
||||
local jar_file=""
|
||||
|
||||
case $service in
|
||||
monitor)
|
||||
jar_file="./$MONITOR_JAR"
|
||||
;;
|
||||
snailjob)
|
||||
jar_file="./$SNAILJOB_JAR"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -f "$jar_file" ]; then
|
||||
print_message $RED "错误: $jar_file 文件不存在!"
|
||||
print_message $YELLOW "请先编译项目: mvn clean package -Pprod"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# 启动单个服务
|
||||
start_service() {
|
||||
local service=$1
|
||||
local config=${SERVICES[$service]}
|
||||
local port=$(echo $config | cut -d: -f1)
|
||||
local app_name=$(echo $config | cut -d: -f2)
|
||||
local profile=$(echo $config | cut -d: -f3)
|
||||
|
||||
local pid_file="logs/${app_name}.pid"
|
||||
local log_file="logs/${app_name}.log"
|
||||
|
||||
# 检查是否已经运行
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
print_message $YELLOW "服务 $service 已经在运行中 (PID: $pid)"
|
||||
return
|
||||
else
|
||||
# PID文件存在但进程不存在,清理PID文件
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查JAR文件
|
||||
if ! check_jar $service; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_message $GREEN "正在启动服务 $service (端口: $port, 配置文件: $profile)..."
|
||||
|
||||
# 启动应用
|
||||
case $service in
|
||||
monitor)
|
||||
local jar_path="../ruoyi-extend/ruoyi-monitor-admin/target/$MONITOR_JAR"
|
||||
nohup java $JAVA_OPTS \
|
||||
-Dspring.profiles.active=$profile \
|
||||
-Dserver.port=$port \
|
||||
-jar "$jar_path" \
|
||||
> "$log_file" 2>&1 &
|
||||
;;
|
||||
snailjob)
|
||||
local jar_path="../ruoyi-extend/ruoyi-snailjob-server/target/$SNAILJOB_JAR"
|
||||
nohup java $JAVA_OPTS \
|
||||
-Dspring.profiles.active=$profile \
|
||||
-Dserver.port=$port \
|
||||
-jar "$jar_path" \
|
||||
> "$log_file" 2>&1 &
|
||||
;;
|
||||
esac
|
||||
|
||||
local new_pid=$!
|
||||
echo $new_pid > "$pid_file"
|
||||
|
||||
# 等待启动
|
||||
sleep 5
|
||||
|
||||
# 检查启动状态
|
||||
if ps -p $new_pid > /dev/null 2>&1; then
|
||||
print_message $GREEN "服务 $service 启动成功 (PID: $new_pid)"
|
||||
print_message $GREEN "访问地址: http://localhost:$port"
|
||||
print_message $GREEN "日志文件: $log_file"
|
||||
|
||||
# 显示服务特定信息
|
||||
case $service in
|
||||
monitor)
|
||||
print_message $BLUE "监控中心: http://localhost:$port/admin"
|
||||
;;
|
||||
snailjob)
|
||||
print_message $BLUE "任务调度: http://localhost:$port/snail-job"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
print_message $RED "服务 $service 启动失败,请检查日志文件: $log_file"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止单个服务
|
||||
stop_service() {
|
||||
local service=$1
|
||||
local config=${SERVICES[$service]}
|
||||
local app_name=$(echo $config | cut -d: -f2)
|
||||
local pid_file="logs/${app_name}.pid"
|
||||
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
print_message $YELLOW "正在停止服务 $service (PID: $pid)..."
|
||||
kill $pid
|
||||
|
||||
# 等待进程结束
|
||||
local count=0
|
||||
while ps -p $pid > /dev/null 2>&1 && [ $count -lt 30 ]; do
|
||||
sleep 1
|
||||
count=$((count + 1))
|
||||
done
|
||||
|
||||
# 强制杀死进程
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
print_message $YELLOW "强制停止服务 $service..."
|
||||
kill -9 $pid
|
||||
fi
|
||||
|
||||
rm -f "$pid_file"
|
||||
print_message $GREEN "服务 $service 已停止"
|
||||
else
|
||||
print_message $YELLOW "服务 $service 未运行"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
else
|
||||
print_message $YELLOW "服务 $service 未运行"
|
||||
fi
|
||||
}
|
||||
|
||||
# 重启单个服务
|
||||
restart_service() {
|
||||
local service=$1
|
||||
print_message $YELLOW "重启服务 $service..."
|
||||
stop_service $service
|
||||
sleep 3
|
||||
start_service $service
|
||||
}
|
||||
|
||||
# 查看服务状态
|
||||
status_service() {
|
||||
local service=$1
|
||||
local config=${SERVICES[$service]}
|
||||
local port=$(echo $config | cut -d: -f1)
|
||||
local app_name=$(echo $config | cut -d: -f2)
|
||||
local pid_file="logs/${app_name}.pid"
|
||||
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
local memory=$(ps -o rss= -p $pid | awk '{print $1/1024 " MB"}')
|
||||
local uptime=$(ps -o etime= -p $pid)
|
||||
print_message $GREEN "服务 $service: 运行中 (PID: $pid, 端口: $port, 内存: $memory, 运行时间: $uptime)"
|
||||
else
|
||||
print_message $RED "服务 $service: 异常 (PID文件存在但进程不存在)"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
else
|
||||
print_message $YELLOW "服务 $service: 未运行 (端口: $port)"
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动所有服务
|
||||
start_all() {
|
||||
print_message $GREEN "启动所有扩展服务..."
|
||||
for service in "${!SERVICES[@]}"; do
|
||||
start_service $service
|
||||
sleep 3
|
||||
done
|
||||
}
|
||||
|
||||
# 停止所有服务
|
||||
stop_all() {
|
||||
print_message $YELLOW "停止所有扩展服务..."
|
||||
for service in "${!SERVICES[@]}"; do
|
||||
stop_service $service
|
||||
done
|
||||
}
|
||||
|
||||
# 重启所有服务
|
||||
restart_all() {
|
||||
print_message $YELLOW "重启所有扩展服务..."
|
||||
stop_all
|
||||
sleep 5
|
||||
start_all
|
||||
}
|
||||
|
||||
# 查看所有服务状态
|
||||
status_all() {
|
||||
print_message $GREEN "所有扩展服务状态:"
|
||||
for service in "${!SERVICES[@]}"; do
|
||||
status_service $service
|
||||
done
|
||||
}
|
||||
|
||||
# 编译项目
|
||||
build_project() {
|
||||
print_message $BLUE "开始编译扩展服务项目..."
|
||||
|
||||
# 编译monitor-admin
|
||||
print_message $YELLOW "编译 ruoyi-monitor-admin..."
|
||||
cd ../ruoyi-extend/ruoyi-monitor-admin
|
||||
mvn clean package -Pprod
|
||||
if [ $? -eq 0 ]; then
|
||||
print_message $GREEN "ruoyi-monitor-admin 编译成功"
|
||||
else
|
||||
print_message $RED "ruoyi-monitor-admin 编译失败"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 编译snailjob-server
|
||||
print_message $YELLOW "编译 ruoyi-snailjob-server..."
|
||||
cd ../ruoyi-snailjob-server
|
||||
mvn clean package -Pprod
|
||||
if [ $? -eq 0 ]; then
|
||||
print_message $GREEN "ruoyi-snailjob-server 编译成功"
|
||||
else
|
||||
print_message $RED "ruoyi-snailjob-server 编译失败"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 返回原目录
|
||||
cd ../../部署文档/杭州第一技师部署文档
|
||||
print_message $GREEN "所有扩展服务编译完成"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "扩展服务启动停止一体化脚本使用方法:"
|
||||
echo " $0 [命令] [服务名称]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " start 启动服务"
|
||||
echo " stop 停止服务"
|
||||
echo " restart 重启服务"
|
||||
echo " status 查看服务状态"
|
||||
echo " build 编译项目"
|
||||
echo ""
|
||||
echo "服务名称:"
|
||||
echo " monitor 启动监控中心服务 (端口: 9090)"
|
||||
echo " snailjob 启动任务调度服务 (端口: 8800)"
|
||||
echo " all 操作所有服务"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 start monitor # 启动监控中心服务"
|
||||
echo " $0 start snailjob # 启动任务调度服务"
|
||||
echo " $0 start all # 启动所有服务"
|
||||
echo " $0 stop all # 停止所有服务"
|
||||
echo " $0 status all # 查看所有服务状态"
|
||||
echo " $0 build # 编译所有扩展服务"
|
||||
echo ""
|
||||
echo "服务访问地址:"
|
||||
echo " 监控中心: http://localhost:9090/admin"
|
||||
echo " 任务调度: http://localhost:8800/snail-job"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local action=$1
|
||||
local service=$2
|
||||
|
||||
# 检查参数
|
||||
if [ -z "$action" ]; then
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case $action in
|
||||
start)
|
||||
if [ "$service" = "all" ]; then
|
||||
start_all
|
||||
elif [ -n "${SERVICES[$service]}" ]; then
|
||||
start_service $service
|
||||
else
|
||||
print_message $RED "无效的服务名称: $service"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ "$service" = "all" ]; then
|
||||
stop_all
|
||||
elif [ -n "${SERVICES[$service]}" ]; then
|
||||
stop_service $service
|
||||
else
|
||||
print_message $RED "无效的服务名称: $service"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
if [ "$service" = "all" ]; then
|
||||
restart_all
|
||||
elif [ -n "${SERVICES[$service]}" ]; then
|
||||
restart_service $service
|
||||
else
|
||||
print_message $RED "无效的服务名称: $service"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
if [ "$service" = "all" ]; then
|
||||
status_all
|
||||
elif [ -n "${SERVICES[$service]}" ]; then
|
||||
status_service $service
|
||||
else
|
||||
print_message $RED "无效的服务名称: $service"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
build)
|
||||
build_project
|
||||
;;
|
||||
*)
|
||||
print_message $RED "无效的命令: $action"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
@@ -0,0 +1,296 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM 多实例启动脚本 (Windows版本)
|
||||
REM 使用方法: start-multi-instance.bat [start|stop|restart|status] [instance_name]
|
||||
REM 实例名称: 9900, 9901, 9902, 9903, all
|
||||
|
||||
REM 配置变量
|
||||
set JAR_NAME=ruoyi-admin.jar
|
||||
set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
|
||||
set JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai
|
||||
set JAVA_OPTS=%JAVA_OPTS% -XX:+UseG1GC -XX:MaxGCPauseMillis=200
|
||||
set JAVA_OPTS=%JAVA_OPTS% -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs\
|
||||
|
||||
REM 日志目录
|
||||
if not exist logs mkdir logs
|
||||
|
||||
REM 颜色输出函数
|
||||
:print_message
|
||||
set color=%1
|
||||
set message=%2
|
||||
if "%color%"=="RED" (
|
||||
echo [31m%message%[0m
|
||||
) else if "%color%"=="GREEN" (
|
||||
echo [32m%message%[0m
|
||||
) else if "%color%"=="YELLOW" (
|
||||
echo [33m%message%[0m
|
||||
) else (
|
||||
echo %message%
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 检查JAR文件是否存在
|
||||
:check_jar
|
||||
if not exist "%JAR_NAME%" (
|
||||
call :print_message RED "错误: %JAR_NAME% 文件不存在!"
|
||||
call :print_message YELLOW "请确保在正确的目录下运行此脚本,或先执行 mvn clean package -Pprod 编译项目"
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 启动单个实例
|
||||
:start_instance
|
||||
set port=%1
|
||||
set profile=%2
|
||||
set pid_file=logs\ruoyi-admin-%port%.pid
|
||||
set log_file=logs\ruoyi-admin-%port%.log
|
||||
|
||||
REM 检查是否已经运行
|
||||
if exist "%pid_file%" (
|
||||
for /f "tokens=*" %%i in (%pid_file%) do set pid=%%i
|
||||
tasklist /FI "PID eq !pid!" 2>nul | find /I "!pid!" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
call :print_message YELLOW "实例 %port% 已经在运行中 (PID: !pid!)"
|
||||
goto :eof
|
||||
) else (
|
||||
REM PID文件存在但进程不存在,清理PID文件
|
||||
del "%pid_file%" 2>nul
|
||||
)
|
||||
)
|
||||
|
||||
call :print_message GREEN "正在启动实例 %port% (配置文件: %profile%)..."
|
||||
|
||||
REM 启动应用
|
||||
start /b java %JAVA_OPTS% -Dspring.profiles.active=%profile% -Dserver.port=%port% -jar %JAR_NAME% > "%log_file%" 2>&1
|
||||
|
||||
REM 等待启动
|
||||
timeout /t 3 /nobreak >nul
|
||||
|
||||
REM 查找新启动的进程
|
||||
for /f "tokens=2" %%i in ('tasklist /FI "IMAGENAME eq java.exe" /FO CSV ^| findstr /I "java.exe"') do (
|
||||
set new_pid=%%i
|
||||
set new_pid=!new_pid:"=!
|
||||
echo !new_pid! > "%pid_file%"
|
||||
call :print_message GREEN "实例 %port% 启动成功 (PID: !new_pid!)"
|
||||
call :print_message GREEN "访问地址: http://localhost:%port%"
|
||||
call :print_message GREEN "日志文件: %log_file%"
|
||||
goto :eof
|
||||
)
|
||||
|
||||
call :print_message RED "实例 %port% 启动失败,请检查日志文件: %log_file%"
|
||||
if exist "%pid_file%" del "%pid_file%"
|
||||
goto :eof
|
||||
|
||||
REM 停止单个实例
|
||||
:stop_instance
|
||||
set port=%1
|
||||
set pid_file=logs\ruoyi-admin-%port%.pid
|
||||
|
||||
if exist "%pid_file%" (
|
||||
for /f "tokens=*" %%i in (%pid_file%) do set pid=%%i
|
||||
tasklist /FI "PID eq !pid!" 2>nul | find /I "!pid!" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
call :print_message YELLOW "正在停止实例 %port% (PID: !pid!)..."
|
||||
taskkill /PID !pid! /F >nul 2>&1
|
||||
del "%pid_file%" 2>nul
|
||||
call :print_message GREEN "实例 %port% 已停止"
|
||||
) else (
|
||||
call :print_message YELLOW "实例 %port% 未运行"
|
||||
del "%pid_file%" 2>nul
|
||||
)
|
||||
) else (
|
||||
call :print_message YELLOW "实例 %port% 未运行"
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 重启单个实例
|
||||
:restart_instance
|
||||
set port=%1
|
||||
call :print_message YELLOW "重启实例 %port%..."
|
||||
call :stop_instance %port%
|
||||
timeout /t 2 /nobreak >nul
|
||||
if "%port%"=="9900" call :start_instance %port% prod-9900
|
||||
if "%port%"=="9901" call :start_instance %port% prod-9901
|
||||
if "%port%"=="9902" call :start_instance %port% prod-9902
|
||||
if "%port%"=="9903" call :start_instance %port% prod-9903
|
||||
goto :eof
|
||||
|
||||
REM 查看实例状态
|
||||
:status_instance
|
||||
set port=%1
|
||||
set pid_file=logs\ruoyi-admin-%port%.pid
|
||||
|
||||
if exist "%pid_file%" (
|
||||
for /f "tokens=*" %%i in (%pid_file%) do set pid=%%i
|
||||
tasklist /FI "PID eq !pid!" 2>nul | find /I "!pid!" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
call :print_message GREEN "实例 %port%: 运行中 (PID: !pid!)"
|
||||
) else (
|
||||
call :print_message RED "实例 %port%: 异常 (PID文件存在但进程不存在)"
|
||||
del "%pid_file%" 2>nul
|
||||
)
|
||||
) else (
|
||||
call :print_message YELLOW "实例 %port%: 未运行"
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 启动所有实例
|
||||
:start_all
|
||||
call :print_message GREEN "启动所有实例..."
|
||||
call :start_instance 9900 prod-9900
|
||||
timeout /t 2 /nobreak >nul
|
||||
call :start_instance 9901 prod-9901
|
||||
timeout /t 2 /nobreak >nul
|
||||
call :start_instance 9902 prod-9902
|
||||
timeout /t 2 /nobreak >nul
|
||||
call :start_instance 9903 prod-9903
|
||||
goto :eof
|
||||
|
||||
REM 停止所有实例
|
||||
:stop_all
|
||||
call :print_message YELLOW "停止所有实例..."
|
||||
call :stop_instance 9900
|
||||
call :stop_instance 9901
|
||||
call :stop_instance 9902
|
||||
call :stop_instance 9903
|
||||
goto :eof
|
||||
|
||||
REM 重启所有实例
|
||||
:restart_all
|
||||
call :print_message YELLOW "重启所有实例..."
|
||||
call :stop_all
|
||||
timeout /t 3 /nobreak >nul
|
||||
call :start_all
|
||||
goto :eof
|
||||
|
||||
REM 查看所有实例状态
|
||||
:status_all
|
||||
call :print_message GREEN "所有实例状态:"
|
||||
call :status_instance 9900
|
||||
call :status_instance 9901
|
||||
call :status_instance 9902
|
||||
call :status_instance 9903
|
||||
goto :eof
|
||||
|
||||
REM 显示帮助信息
|
||||
:show_help
|
||||
echo 多实例启动脚本使用方法:
|
||||
echo %0 [命令] [实例名称]
|
||||
echo.
|
||||
echo 命令:
|
||||
echo start 启动实例
|
||||
echo stop 停止实例
|
||||
echo restart 重启实例
|
||||
echo status 查看实例状态
|
||||
echo.
|
||||
echo 实例名称:
|
||||
echo 9900 启动9900端口实例
|
||||
echo 9901 启动9901端口实例
|
||||
echo 9902 启动9902端口实例
|
||||
echo 9903 启动9903端口实例
|
||||
echo all 操作所有实例
|
||||
echo.
|
||||
echo 示例:
|
||||
echo %0 start 9900 # 启动9900端口实例
|
||||
echo %0 start all # 启动所有实例
|
||||
echo %0 stop 9900 # 停止9900端口实例
|
||||
echo %0 status all # 查看所有实例状态
|
||||
echo %0 restart all # 重启所有实例
|
||||
goto :eof
|
||||
|
||||
REM 主函数
|
||||
:main
|
||||
set action=%1
|
||||
set instance=%2
|
||||
|
||||
REM 检查参数
|
||||
if "%action%"=="" (
|
||||
call :show_help
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 检查JAR文件
|
||||
call :check_jar
|
||||
if !errorlevel! neq 0 goto :eof
|
||||
|
||||
if "%action%"=="start" (
|
||||
if "%instance%"=="all" (
|
||||
call :start_all
|
||||
) else if "%instance%"=="9900" (
|
||||
call :start_instance 9900 prod-9900
|
||||
) else if "%instance%"=="9901" (
|
||||
call :start_instance 9901 prod-9901
|
||||
) else if "%instance%"=="9902" (
|
||||
call :start_instance 9902 prod-9902
|
||||
) else if "%instance%"=="9903" (
|
||||
call :start_instance 9903 prod-9903
|
||||
) else (
|
||||
call :print_message RED "无效的实例名称: %instance%"
|
||||
call :show_help
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else if "%action%"=="stop" (
|
||||
if "%instance%"=="all" (
|
||||
call :stop_all
|
||||
) else if "%instance%"=="9900" (
|
||||
call :stop_instance 9900
|
||||
) else if "%instance%"=="9901" (
|
||||
call :stop_instance 9901
|
||||
) else if "%instance%"=="9902" (
|
||||
call :stop_instance 9902
|
||||
) else if "%instance%"=="9903" (
|
||||
call :stop_instance 9903
|
||||
) else (
|
||||
call :print_message RED "无效的实例名称: %instance%"
|
||||
call :show_help
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else if "%action%"=="restart" (
|
||||
if "%instance%"=="all" (
|
||||
call :restart_all
|
||||
) else if "%instance%"=="9900" (
|
||||
call :restart_instance 9900
|
||||
) else if "%instance%"=="9901" (
|
||||
call :restart_instance 9901
|
||||
) else if "%instance%"=="9902" (
|
||||
call :restart_instance 9902
|
||||
) else if "%instance%"=="9903" (
|
||||
call :restart_instance 9903
|
||||
) else (
|
||||
call :print_message RED "无效的实例名称: %instance%"
|
||||
call :show_help
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else if "%action%"=="status" (
|
||||
if "%instance%"=="all" (
|
||||
call :status_all
|
||||
) else if "%instance%"=="9900" (
|
||||
call :status_instance 9900
|
||||
) else if "%instance%"=="9901" (
|
||||
call :status_instance 9901
|
||||
) else if "%instance%"=="9902" (
|
||||
call :status_instance 9902
|
||||
) else if "%instance%"=="9903" (
|
||||
call :status_instance 9903
|
||||
) else (
|
||||
call :print_message RED "无效的实例名称: %instance%"
|
||||
call :show_help
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
call :print_message RED "无效的命令: %action%"
|
||||
call :show_help
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
goto :eof
|
||||
@@ -0,0 +1,284 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 多实例启动脚本
|
||||
# 使用方法: ./start-multi-instance.sh [start|stop|restart|status] [instance_name]
|
||||
# 实例名称: 9900, 9901, 9902, 9903, all
|
||||
|
||||
# 配置变量
|
||||
JAR_NAME="ruoyi-admin.jar"
|
||||
JAVA_OPTS="-Xms8192m -Xmx8192m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m"
|
||||
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
|
||||
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
|
||||
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/"
|
||||
|
||||
# 实例配置
|
||||
declare -A INSTANCES
|
||||
INSTANCES[9900]="prod-9900"
|
||||
INSTANCES[9901]="prod-9901"
|
||||
INSTANCES[9902]="prod-9902"
|
||||
INSTANCES[9903]="prod-9903"
|
||||
|
||||
# 日志目录
|
||||
LOG_DIR="logs"
|
||||
mkdir -p $LOG_DIR
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印带颜色的消息
|
||||
print_message() {
|
||||
local color=$1
|
||||
local message=$2
|
||||
echo -e "${color}${message}${NC}"
|
||||
}
|
||||
|
||||
# 检查JAR文件是否存在
|
||||
check_jar() {
|
||||
if [ ! -f "$JAR_NAME" ]; then
|
||||
print_message $RED "错误: $JAR_NAME 文件不存在!"
|
||||
print_message $YELLOW "请确保在正确的目录下运行此脚本,或先执行 mvn clean package -Pprod 编译项目"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动单个实例
|
||||
start_instance() {
|
||||
local port=$1
|
||||
local profile=$2
|
||||
|
||||
local pid_file="logs/ruoyi-admin-${port}.pid"
|
||||
local log_file="logs/ruoyi-admin-${port}.log"
|
||||
|
||||
# 检查是否已经运行
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
print_message $YELLOW "实例 $port 已经在运行中 (PID: $pid)"
|
||||
return
|
||||
else
|
||||
# PID文件存在但进程不存在,清理PID文件
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_message $GREEN "正在启动实例 $port (配置文件: $profile)..."
|
||||
|
||||
# 启动应用
|
||||
nohup java $JAVA_OPTS \
|
||||
-Dspring.profiles.active=prod,$profile \
|
||||
-Dserver.port=$port \
|
||||
-jar $JAR_NAME \
|
||||
> "$log_file" 2>&1 &
|
||||
|
||||
local new_pid=$!
|
||||
echo $new_pid > "$pid_file"
|
||||
|
||||
# 等待启动
|
||||
sleep 3
|
||||
|
||||
# 检查启动状态
|
||||
if ps -p $new_pid > /dev/null 2>&1; then
|
||||
print_message $GREEN "实例 $port 启动成功 (PID: $new_pid)"
|
||||
print_message $GREEN "访问地址: http://localhost:$port"
|
||||
print_message $GREEN "日志文件: $log_file"
|
||||
else
|
||||
print_message $RED "实例 $port 启动失败,请检查日志文件: $log_file"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止单个实例
|
||||
stop_instance() {
|
||||
local port=$1
|
||||
local pid_file="logs/ruoyi-admin-${port}.pid"
|
||||
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
print_message $YELLOW "正在停止实例 $port (PID: $pid)..."
|
||||
kill $pid
|
||||
|
||||
# 等待进程结束
|
||||
local count=0
|
||||
while ps -p $pid > /dev/null 2>&1 && [ $count -lt 30 ]; do
|
||||
sleep 1
|
||||
count=$((count + 1))
|
||||
done
|
||||
|
||||
# 强制杀死进程
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
print_message $YELLOW "强制停止实例 $port..."
|
||||
kill -9 $pid
|
||||
fi
|
||||
|
||||
rm -f "$pid_file"
|
||||
print_message $GREEN "实例 $port 已停止"
|
||||
else
|
||||
print_message $YELLOW "实例 $port 未运行"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
else
|
||||
print_message $YELLOW "实例 $port 未运行"
|
||||
fi
|
||||
}
|
||||
|
||||
# 重启单个实例
|
||||
restart_instance() {
|
||||
local port=$1
|
||||
print_message $YELLOW "重启实例 $port..."
|
||||
stop_instance $port
|
||||
sleep 2
|
||||
start_instance $port ${INSTANCES[$port]}
|
||||
}
|
||||
|
||||
# 查看实例状态
|
||||
status_instance() {
|
||||
local port=$1
|
||||
local pid_file="logs/ruoyi-admin-${port}.pid"
|
||||
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
local memory=$(ps -o rss= -p $pid | awk '{print $1/1024 " MB"}')
|
||||
local uptime=$(ps -o etime= -p $pid)
|
||||
print_message $GREEN "实例 $port: 运行中 (PID: $pid, 内存: $memory, 运行时间: $uptime)"
|
||||
else
|
||||
print_message $RED "实例 $port: 异常 (PID文件存在但进程不存在)"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
else
|
||||
print_message $YELLOW "实例 $port: 未运行"
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动所有实例
|
||||
start_all() {
|
||||
print_message $GREEN "启动所有实例..."
|
||||
for port in "${!INSTANCES[@]}"; do
|
||||
start_instance $port ${INSTANCES[$port]}
|
||||
sleep 2
|
||||
done
|
||||
}
|
||||
|
||||
# 停止所有实例
|
||||
stop_all() {
|
||||
print_message $YELLOW "停止所有实例..."
|
||||
for port in "${!INSTANCES[@]}"; do
|
||||
stop_instance $port
|
||||
done
|
||||
}
|
||||
|
||||
# 重启所有实例
|
||||
restart_all() {
|
||||
print_message $YELLOW "重启所有实例..."
|
||||
stop_all
|
||||
sleep 3
|
||||
start_all
|
||||
}
|
||||
|
||||
# 查看所有实例状态
|
||||
status_all() {
|
||||
print_message $GREEN "所有实例状态:"
|
||||
for port in "${!INSTANCES[@]}"; do
|
||||
status_instance $port
|
||||
done
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "多实例启动脚本使用方法:"
|
||||
echo " $0 [命令] [实例名称]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " start 启动实例"
|
||||
echo " stop 停止实例"
|
||||
echo " restart 重启实例"
|
||||
echo " status 查看实例状态"
|
||||
echo ""
|
||||
echo "实例名称:"
|
||||
echo " 9900 启动9900端口实例"
|
||||
echo " 9901 启动9901端口实例"
|
||||
echo " 9902 启动9902端口实例"
|
||||
echo " 9903 启动9903端口实例"
|
||||
echo " all 操作所有实例"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 start 9900 # 启动9900端口实例"
|
||||
echo " $0 start all # 启动所有实例"
|
||||
echo " $0 stop 9900 # 停止9900端口实例"
|
||||
echo " $0 status all # 查看所有实例状态"
|
||||
echo " $0 restart all # 重启所有实例"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local action=$1
|
||||
local instance=$2
|
||||
|
||||
# 检查参数
|
||||
if [ -z "$action" ]; then
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查JAR文件
|
||||
check_jar
|
||||
|
||||
case $action in
|
||||
start)
|
||||
if [ "$instance" = "all" ]; then
|
||||
start_all
|
||||
elif [ -n "${INSTANCES[$instance]}" ]; then
|
||||
start_instance $instance ${INSTANCES[$instance]}
|
||||
else
|
||||
print_message $RED "无效的实例名称: $instance"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ "$instance" = "all" ]; then
|
||||
stop_all
|
||||
elif [ -n "${INSTANCES[$instance]}" ]; then
|
||||
stop_instance $instance
|
||||
else
|
||||
print_message $RED "无效的实例名称: $instance"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
if [ "$instance" = "all" ]; then
|
||||
restart_all
|
||||
elif [ -n "${INSTANCES[$instance]}" ]; then
|
||||
restart_instance $instance
|
||||
else
|
||||
print_message $RED "无效的实例名称: $instance"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
if [ "$instance" = "all" ]; then
|
||||
status_all
|
||||
elif [ -n "${INSTANCES[$instance]}" ]; then
|
||||
status_instance $instance
|
||||
else
|
||||
print_message $RED "无效的实例名称: $instance"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
print_message $RED "无效的命令: $action"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 配置修复验证脚本
|
||||
# 使用方法: ./test-config-fix.sh
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_message() {
|
||||
local color=$1
|
||||
local message=$2
|
||||
echo -e "${color}${message}${NC}"
|
||||
}
|
||||
|
||||
print_message $GREEN "=== 多实例配置修复验证脚本 ==="
|
||||
|
||||
# 检查JAR文件
|
||||
JAR_NAME="ruoyi-admin.jar"
|
||||
if [ ! -f "$JAR_NAME" ]; then
|
||||
print_message $RED "错误: $JAR_NAME 文件不存在!"
|
||||
print_message $YELLOW "请先执行 mvn clean package -Pprod 编译项目"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_message $GREEN "✓ JAR文件存在"
|
||||
|
||||
# 检查配置文件
|
||||
CONFIG_FILES=(
|
||||
"backend/ruoyi-admin/src/main/resources/application-prod-9900.yml"
|
||||
"backend/ruoyi-admin/src/main/resources/application-prod-9901.yml"
|
||||
"backend/ruoyi-admin/src/main/resources/application-prod-9902.yml"
|
||||
"backend/ruoyi-admin/src/main/resources/application-prod-9903.yml"
|
||||
)
|
||||
|
||||
for config_file in "${CONFIG_FILES[@]}"; do
|
||||
if [ -f "$config_file" ]; then
|
||||
# 检查是否包含spring.profiles.include
|
||||
if grep -q "spring.profiles.include" "$config_file"; then
|
||||
print_message $RED "✗ $config_file 仍包含 spring.profiles.include"
|
||||
else
|
||||
print_message $GREEN "✓ $config_file 配置正确"
|
||||
fi
|
||||
else
|
||||
print_message $RED "✗ $config_file 文件不存在"
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查启动脚本
|
||||
START_SCRIPT="docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh"
|
||||
if [ -f "$START_SCRIPT" ]; then
|
||||
if grep -q "spring.profiles.active=prod,\$profile" "$START_SCRIPT"; then
|
||||
print_message $GREEN "✓ 启动脚本配置正确"
|
||||
else
|
||||
print_message $RED "✗ 启动脚本配置错误"
|
||||
fi
|
||||
else
|
||||
print_message $RED "✗ 启动脚本不存在"
|
||||
fi
|
||||
|
||||
print_message $GREEN ""
|
||||
print_message $GREEN "=== 验证完成 ==="
|
||||
print_message $YELLOW "如果所有检查都通过,可以尝试启动实例:"
|
||||
print_message $YELLOW " ./start-multi-instance.sh start 9900"
|
||||
@@ -0,0 +1,96 @@
|
||||
# 多实例启动配置修复说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
在使用 `start-multi-instance.sh` 启动实例时,出现以下错误:
|
||||
|
||||
```
|
||||
org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property 'spring.profiles.include' imported from location 'class path resource [application-prod-9900.yml]' is invalid in a profile specific resource
|
||||
```
|
||||
|
||||
## 问题原因
|
||||
|
||||
在Spring Boot 2.4+版本中,`spring.profiles.include` 属性不能在profile-specific的配置文件中使用(如 `application-prod-9900.yml`)。这个属性只能在主配置文件(`application.yml`)中使用。
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 修改配置文件结构
|
||||
|
||||
移除了所有实例配置文件中的 `spring.profiles.include: prod` 配置:
|
||||
|
||||
**修复前:**
|
||||
```yaml
|
||||
# application-prod-9900.yml
|
||||
spring:
|
||||
profiles:
|
||||
include: prod # ❌ 这在profile-specific文件中不被允许
|
||||
```
|
||||
|
||||
**修复后:**
|
||||
```yaml
|
||||
# application-prod-9900.yml
|
||||
spring:
|
||||
application:
|
||||
name: RuoYi-Vue-Plus-9900 # ✅ 直接配置应用名称
|
||||
```
|
||||
|
||||
### 2. 修改启动脚本
|
||||
|
||||
修改启动脚本,在启动时同时激活 `prod` 和具体的实例profile:
|
||||
|
||||
**修复前:**
|
||||
```bash
|
||||
-Dspring.profiles.active=$profile # 只激活 prod-9900
|
||||
```
|
||||
|
||||
**修复后:**
|
||||
```bash
|
||||
-Dspring.profiles.active=prod,$profile # 同时激活 prod 和 prod-9900
|
||||
```
|
||||
|
||||
## 修复的文件
|
||||
|
||||
1. `backend/ruoyi-admin/src/main/resources/application-prod-9900.yml`
|
||||
2. `backend/ruoyi-admin/src/main/resources/application-prod-9901.yml`
|
||||
3. `backend/ruoyi-admin/src/main/resources/application-prod-9902.yml`
|
||||
4. `backend/ruoyi-admin/src/main/resources/application-prod-9903.yml`
|
||||
5. `docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh`
|
||||
|
||||
## 配置加载顺序
|
||||
|
||||
修复后的配置加载顺序:
|
||||
|
||||
1. `application.yml` (主配置文件)
|
||||
2. `application-prod.yml` (生产环境基础配置)
|
||||
3. `application-prod-9900.yml` (9900端口实例特定配置)
|
||||
|
||||
## 验证方法
|
||||
|
||||
1. 重新编译项目:
|
||||
```bash
|
||||
mvn clean package -Pprod
|
||||
```
|
||||
|
||||
2. 启动单个实例测试:
|
||||
```bash
|
||||
./start-multi-instance.sh start 9900
|
||||
```
|
||||
|
||||
3. 检查启动日志,确认没有配置错误
|
||||
|
||||
4. 访问应用验证功能正常:
|
||||
```bash
|
||||
curl http://localhost:9900
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保在修改配置文件后重新编译项目
|
||||
2. 如果之前有实例在运行,需要先停止再重新启动
|
||||
3. 检查日志文件确认配置加载正确
|
||||
4. 验证监控中心和SnailJob配置是否正常工作
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Spring Boot Profile配置最佳实践](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.profile-specific)
|
||||
- [多实例部署指南](../README.md)
|
||||
@@ -0,0 +1,293 @@
|
||||
# 🚀 RuoYi-Admin 多实例部署说明
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档说明如何在生产环境中部署多个 `ruoyi-admin.jar` 实例到不同端口,实现负载均衡和高可用。
|
||||
|
||||
## 🏗️ 部署架构
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ 前端应用 │ │ Nginx │ │ 后端实例 │
|
||||
│ │ │ 负载均衡器 │ │ │
|
||||
│ http://80 │───▶│ http://80 │───▶│ 9900, 9901 │
|
||||
│ │ │ │ │ 9902, 9903 │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
backend/
|
||||
├── ruoyi-admin.jar # 编译后的JAR包
|
||||
├── application-prod-9900.yml # 9900端口配置文件
|
||||
├── application-prod-9901.yml # 9901端口配置文件
|
||||
├── application-prod-9902.yml # 9902端口配置文件
|
||||
├── application-prod-9903.yml # 9903端口配置文件
|
||||
├── start-multi-instance.sh # Linux启动脚本
|
||||
├── start-multi-instance.bat # Windows启动脚本
|
||||
├── docker-compose-multi.yml # Docker部署配置
|
||||
├── nginx.conf # Nginx负载均衡配置
|
||||
└── logs/ # 日志目录
|
||||
├── ruoyi-admin-9900.log
|
||||
├── ruoyi-admin-9901.log
|
||||
├── ruoyi-admin-9902.log
|
||||
└── ruoyi-admin-9903.log
|
||||
```
|
||||
|
||||
## 🔧 部署步骤
|
||||
|
||||
### 1. 编译项目
|
||||
|
||||
```bash
|
||||
# 进入backend目录
|
||||
cd backend
|
||||
|
||||
# 编译生产环境JAR包
|
||||
mvn clean package -Pprod
|
||||
|
||||
# 检查生成的JAR文件
|
||||
ls -la ruoyi-admin/target/ruoyi-admin.jar
|
||||
```
|
||||
|
||||
### 2. 准备部署文件
|
||||
|
||||
```bash
|
||||
# 复制JAR文件到部署目录
|
||||
cp ruoyi-admin/target/ruoyi-admin.jar ./
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p logs
|
||||
|
||||
# 设置脚本执行权限 (Linux)
|
||||
chmod +x start-multi-instance.sh
|
||||
```
|
||||
|
||||
### 3. 启动多实例
|
||||
|
||||
#### Linux环境
|
||||
|
||||
```bash
|
||||
# 启动所有实例
|
||||
./start-multi-instance.sh start all
|
||||
|
||||
# 启动单个实例
|
||||
./start-multi-instance.sh start 9900
|
||||
./start-multi-instance.sh start 9901
|
||||
./start-multi-instance.sh start 9902
|
||||
./start-multi-instance.sh start 9903
|
||||
|
||||
# 查看实例状态
|
||||
./start-multi-instance.sh status all
|
||||
|
||||
# 停止实例
|
||||
./start-multi-instance.sh stop all
|
||||
|
||||
# 重启实例
|
||||
./start-multi-instance.sh restart all
|
||||
```
|
||||
|
||||
#### Windows环境
|
||||
|
||||
```cmd
|
||||
# 启动所有实例
|
||||
start-multi-instance.bat start all
|
||||
|
||||
# 启动单个实例
|
||||
start-multi-instance.bat start 9900
|
||||
start-multi-instance.bat start 9901
|
||||
start-multi-instance.bat start 9902
|
||||
start-multi-instance.bat start 9903
|
||||
|
||||
# 查看实例状态
|
||||
start-multi-instance.bat status all
|
||||
|
||||
# 停止实例
|
||||
start-multi-instance.bat stop all
|
||||
|
||||
# 重启实例
|
||||
start-multi-instance.bat restart all
|
||||
```
|
||||
|
||||
### 4. Docker部署 (可选)
|
||||
|
||||
```bash
|
||||
# 启动所有服务
|
||||
docker-compose -f docker-compose-multi.yml up -d
|
||||
|
||||
# 查看服务状态
|
||||
docker-compose -f docker-compose-multi.yml ps
|
||||
|
||||
# 查看日志
|
||||
docker-compose -f docker-compose-multi.yml logs -f
|
||||
|
||||
# 停止服务
|
||||
docker-compose -f docker-compose-multi.yml down
|
||||
```
|
||||
|
||||
## 🌐 访问地址
|
||||
|
||||
### 直接访问各实例
|
||||
- **实例1**: http://localhost:9900
|
||||
- **实例2**: http://localhost:9901
|
||||
- **实例3**: http://localhost:9902
|
||||
- **实例4**: http://localhost:9903
|
||||
|
||||
### 通过负载均衡器访问
|
||||
- **主入口**: http://localhost (Nginx负载均衡)
|
||||
- **监控页面**: http://localhost:8080/status
|
||||
|
||||
## 📊 监控和管理
|
||||
|
||||
### 1. 实例状态监控
|
||||
|
||||
```bash
|
||||
# 查看所有实例状态
|
||||
./start-multi-instance.sh status all
|
||||
|
||||
# 查看单个实例状态
|
||||
./start-multi-instance.sh status 9900
|
||||
```
|
||||
|
||||
### 2. 日志监控
|
||||
|
||||
```bash
|
||||
# 查看实时日志
|
||||
tail -f logs/ruoyi-admin-9900.log
|
||||
tail -f logs/ruoyi-admin-9901.log
|
||||
tail -f logs/ruoyi-admin-9902.log
|
||||
tail -f logs/ruoyi-admin-9903.log
|
||||
|
||||
# 查看错误日志
|
||||
grep "ERROR" logs/ruoyi-admin-*.log
|
||||
```
|
||||
|
||||
### 3. 进程监控
|
||||
|
||||
```bash
|
||||
# 查看Java进程
|
||||
ps aux | grep java
|
||||
|
||||
# 查看端口占用
|
||||
netstat -tlnp | grep :99
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 端口冲突
|
||||
- 确保9900-9903端口未被其他服务占用
|
||||
- 检查防火墙设置,确保端口可访问
|
||||
|
||||
### 2. 内存配置
|
||||
- 每个实例默认配置:最小512MB,最大1024MB
|
||||
- 根据服务器配置调整 `JAVA_OPTS` 参数
|
||||
|
||||
### 3. 数据库连接
|
||||
- 所有实例共享同一个数据库
|
||||
- 确保数据库连接池配置合理,避免连接数过多
|
||||
|
||||
### 4. 文件上传
|
||||
- 临时文件目录:`/ruoyi/server/temp`
|
||||
- 确保目录存在且有写权限
|
||||
|
||||
## 🔄 故障排查
|
||||
|
||||
### 1. 实例启动失败
|
||||
|
||||
```bash
|
||||
# 检查JAR文件是否存在
|
||||
ls -la ruoyi-admin.jar
|
||||
|
||||
# 检查端口是否被占用
|
||||
netstat -tlnp | grep :99
|
||||
|
||||
# 查看启动日志
|
||||
tail -f logs/ruoyi-admin-9900.log
|
||||
```
|
||||
|
||||
### 2. 实例无法访问
|
||||
|
||||
```bash
|
||||
# 检查防火墙设置
|
||||
firewall-cmd --list-ports
|
||||
|
||||
# 检查SELinux状态 (CentOS/RHEL)
|
||||
getenforce
|
||||
|
||||
# 测试端口连通性
|
||||
telnet localhost 9900
|
||||
```
|
||||
|
||||
### 3. 负载均衡不工作
|
||||
|
||||
```bash
|
||||
# 检查Nginx状态
|
||||
docker-compose -f docker-compose-multi.yml ps nginx
|
||||
|
||||
# 查看Nginx日志
|
||||
docker-compose -f docker-compose-multi.yml logs nginx
|
||||
|
||||
# 测试后端服务
|
||||
curl http://localhost:9900/health
|
||||
```
|
||||
|
||||
## 📈 性能优化建议
|
||||
|
||||
### 1. JVM调优
|
||||
```bash
|
||||
# 根据服务器内存调整JVM参数
|
||||
JAVA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
|
||||
```
|
||||
|
||||
### 2. 数据库连接池优化
|
||||
```yaml
|
||||
# 在application-prod-*.yml中调整
|
||||
spring:
|
||||
datasource:
|
||||
hikari:
|
||||
maxPoolSize: 100 # 根据实例数调整
|
||||
minIdle: 20
|
||||
connectionTimeout: 30000
|
||||
```
|
||||
|
||||
### 3. 缓存配置
|
||||
```yaml
|
||||
# Redis连接池优化
|
||||
redisson:
|
||||
threads: 32
|
||||
nettyThreads: 64
|
||||
singleServerConfig:
|
||||
connectionPoolSize: 128
|
||||
```
|
||||
|
||||
## 🚀 扩展部署
|
||||
|
||||
### 1. 添加更多实例
|
||||
- 复制配置文件模板
|
||||
- 修改端口号和应用名称
|
||||
- 更新启动脚本和Docker配置
|
||||
|
||||
### 2. 集群部署
|
||||
- 使用Redis集群
|
||||
- 配置数据库主从复制
|
||||
- 实现会话共享
|
||||
|
||||
### 3. 容器化部署
|
||||
- 使用Kubernetes管理
|
||||
- 配置自动扩缩容
|
||||
- 实现滚动更新
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如遇到部署问题,请检查:
|
||||
1. 系统日志和应用日志
|
||||
2. 端口占用情况
|
||||
3. 防火墙和网络配置
|
||||
4. 数据库连接状态
|
||||
5. 内存和磁盘空间
|
||||
|
||||
---
|
||||
|
||||
**📝 文档版本**: v1.0
|
||||
**🔄 最后更新**: 2025-01-03
|
||||
**👨💻 维护人员**: 开发团队
|
||||
@@ -0,0 +1,96 @@
|
||||
# SnailJob命名空间配置修复方案
|
||||
|
||||
## 问题描述
|
||||
|
||||
四个ruoyi-admin实例启动后都提示SnailJob Token认证失败:
|
||||
|
||||
```
|
||||
2025-08-31 01:44:09 [snail-job-grpc-client-executor-172.16.0.67-1216] ERROR c.a.s.c.c.handler.ClientRegister
|
||||
- heartbeat check requestId:[1216] message:[Token authentication failed. [namespace:prod,prod-9900 groupName:ruoyi_group_9900 token:SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT]]
|
||||
```
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 根本原因
|
||||
1. **命名空间配置不匹配**:客户端尝试连接的命名空间是 `prod,prod-9900`,但数据库中的命名空间只有 `prod`
|
||||
2. **动态配置问题**:配置文件使用 `${spring.profiles.active}` 作为命名空间,导致9900-9903实例的命名空间变成了 `prod-9900`, `prod-9901` 等
|
||||
3. **数据库数据缺失**:数据库中缺少对应的命名空间记录
|
||||
|
||||
### 配置对比
|
||||
|
||||
**数据库中的命名空间:**
|
||||
```sql
|
||||
1 Development dev
|
||||
2 Production prod
|
||||
```
|
||||
|
||||
**客户端尝试连接的命名空间:**
|
||||
- prod,prod-9900
|
||||
- prod,prod-9901
|
||||
- prod,prod-9902
|
||||
- prod,prod-9903
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:修改配置文件(已实施)
|
||||
|
||||
将所有实例的命名空间统一为 `prod`,而不是使用 `${spring.profiles.active}`:
|
||||
|
||||
**修改的文件:**
|
||||
- `application-prod-9900.yml`
|
||||
- `application-prod-9901.yml`
|
||||
- `application-prod-9902.yml`
|
||||
- `application-prod-9903.yml`
|
||||
|
||||
**修改内容:**
|
||||
```yaml
|
||||
snail-job:
|
||||
namespace: "prod" # 从 ${spring.profiles.active} 改为固定值
|
||||
```
|
||||
|
||||
### 方案二:数据库方案(备选)
|
||||
|
||||
如果希望保持动态命名空间配置,可以在数据库中创建对应的命名空间:
|
||||
|
||||
```sql
|
||||
-- 为每个实例创建对应的命名空间
|
||||
INSERT INTO `sj_namespace` VALUES (3, 'Production-9900', 'prod-9900', '', 0, now(), now());
|
||||
INSERT INTO `sj_namespace` VALUES (4, 'Production-9901', 'prod-9901', '', 0, now(), now());
|
||||
INSERT INTO `sj_namespace` VALUES (5, 'Production-9902', 'prod-9902', '', 0, now(), now());
|
||||
INSERT INTO `sj_namespace` VALUES (6, 'Production-9903', 'prod-9903', '', 0, now(), now());
|
||||
|
||||
-- 为每个命名空间创建对应的组配置
|
||||
INSERT INTO `sj_group_config` VALUES (13, 'prod-9900', 'ruoyi_group_9900', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
|
||||
INSERT INTO `sj_group_config` VALUES (14, 'prod-9901', 'ruoyi_group_9901', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
|
||||
INSERT INTO `sj_group_config` VALUES (15, 'prod-9902', 'ruoyi_group_9902', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
|
||||
INSERT INTO `sj_group_config` VALUES (16, 'prod-9903', 'ruoyi_group_9903', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now());
|
||||
```
|
||||
|
||||
## 验证步骤
|
||||
|
||||
1. **重启应用实例**
|
||||
```bash
|
||||
# 重启所有实例
|
||||
./hot-start.sh
|
||||
```
|
||||
|
||||
2. **检查日志**
|
||||
- 确认不再出现Token认证失败错误
|
||||
- 确认SnailJob客户端成功注册
|
||||
|
||||
3. **验证连接**
|
||||
- 访问SnailJob管理界面
|
||||
- 确认所有实例都显示为在线状态
|
||||
|
||||
## 最佳实践建议
|
||||
|
||||
1. **命名空间管理**:建议使用统一的命名空间(如 `prod`),避免动态配置导致的复杂性
|
||||
2. **组名管理**:使用有意义的组名,便于识别和管理
|
||||
3. **配置验证**:在部署前验证配置文件与数据库配置的一致性
|
||||
4. **监控告警**:配置SnailJob连接状态的监控告警
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [SnailJob官方文档](https://github.com/aizuda/snail-job)
|
||||
- [项目部署文档](../部署文档/)
|
||||
- [数据库设计文档](../设计文档/database/)
|
||||
Reference in new issue
Block a user