鍒濆鎻愪氦锛氱墰鍙暟鎹鐞嗙郴缁?- 鍖呭惈鍚庣Spring Boot鍜屽墠绔疺ue3椤圭洰
This commit is contained in:
483
backend/API_TEST.md
Normal file
483
backend/API_TEST.md
Normal file
@@ -0,0 +1,483 @@
|
||||
# 牛只数据管理 API 测试文档
|
||||
|
||||
## 服务器信息
|
||||
- **服务器地址**: 119.45.30.82
|
||||
- **端口**: 12240
|
||||
- **基础URL**: `http://119.45.30.82:12240`
|
||||
- **API基础路径**: `/api/cattle-data`
|
||||
|
||||
---
|
||||
|
||||
## 1. 检查服务是否正常运行
|
||||
|
||||
### 方法1: 浏览器访问
|
||||
```
|
||||
http://119.45.30.82:12240/swagger-ui.html
|
||||
```
|
||||
如果能看到 Swagger UI 界面,说明服务正常运行。
|
||||
|
||||
### 方法2: 使用 curl 命令
|
||||
```bash
|
||||
curl http://119.45.30.82:12240/api/cattle-data
|
||||
```
|
||||
|
||||
### 方法3: 检查服务状态(在服务器上)
|
||||
```bash
|
||||
# 检查端口是否监听
|
||||
netstat -tlnp | grep 8080
|
||||
|
||||
# 或使用 ss 命令
|
||||
ss -tlnp | grep 8080
|
||||
|
||||
# 检查进程
|
||||
ps aux | grep cattletends
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. API 接口测试
|
||||
|
||||
### 2.1 获取所有牛只数据
|
||||
|
||||
**接口**: `GET /api/cattle-data`
|
||||
|
||||
**测试方法1: 浏览器直接访问**
|
||||
```
|
||||
http://119.45.30.82:12240/api/cattle-data
|
||||
```
|
||||
|
||||
**测试方法2: 使用 curl**
|
||||
```bash
|
||||
curl -X GET "http://119.45.30.82:12240/api/cattle-data"
|
||||
```
|
||||
|
||||
**测试方法3: 使用 Postman**
|
||||
- Method: GET
|
||||
- URL: `http://119.45.30.82:12240/api/cattle-data`
|
||||
|
||||
---
|
||||
|
||||
### 2.2 按省份筛选数据
|
||||
|
||||
**接口**: `GET /api/cattle-data?province=安徽省`
|
||||
|
||||
**测试方法1: 浏览器**
|
||||
```
|
||||
http://119.45.30.82:12240/api/cattle-data?province=安徽省
|
||||
```
|
||||
|
||||
**测试方法2: curl**
|
||||
```bash
|
||||
curl -X GET "http://119.45.30.82:12240/api/cattle-data?province=安徽省"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 按品种筛选数据
|
||||
|
||||
**接口**: `GET /api/cattle-data?type=鲁西牛`
|
||||
|
||||
**测试方法1: 浏览器**
|
||||
```
|
||||
http://119.45.30.82:12240/api/cattle-data?type=鲁西牛
|
||||
```
|
||||
|
||||
**测试方法2: curl**
|
||||
```bash
|
||||
curl -X GET "http://119.45.30.82:12240/api/cattle-data?type=鲁西牛"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 根据ID获取单条数据
|
||||
|
||||
**接口**: `GET /api/cattle-data/{id}`
|
||||
|
||||
**示例**: 获取ID为1的数据
|
||||
|
||||
**测试方法1: 浏览器**
|
||||
```
|
||||
http://119.45.30.82:12240/api/cattle-data/1
|
||||
```
|
||||
|
||||
**测试方法2: curl**
|
||||
```bash
|
||||
curl -X GET "http://119.45.30.82:12240/api/cattle-data/1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.5 创建牛只数据
|
||||
|
||||
**接口**: `POST /api/cattle-data`
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体示例**:
|
||||
```json
|
||||
{
|
||||
"type": "鲁西牛",
|
||||
"province": "安徽省",
|
||||
"location": "安徽淮南市",
|
||||
"price": 25.50,
|
||||
"provincePrice": 24.80,
|
||||
"inventory23th": 1000,
|
||||
"slaughter23th": 500,
|
||||
"inventory24th": 1200,
|
||||
"slaughter24th": 600,
|
||||
"inventory25th": 1300,
|
||||
"slaughter25th": 650
|
||||
}
|
||||
```
|
||||
|
||||
**测试方法1: curl**
|
||||
```bash
|
||||
curl -X POST "http://119.45.30.82:12240/api/cattle-data" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "鲁西牛",
|
||||
"province": "安徽省",
|
||||
"location": "安徽淮南市",
|
||||
"price": 25.50
|
||||
}'
|
||||
```
|
||||
|
||||
**测试方法2: Postman**
|
||||
- Method: POST
|
||||
- URL: `http://119.45.30.82:12240/api/cattle-data`
|
||||
- Headers: `Content-Type: application/json`
|
||||
- Body (raw JSON): 使用上面的 JSON 示例
|
||||
|
||||
---
|
||||
|
||||
### 2.6 更新牛只数据
|
||||
|
||||
**接口**: `PUT /api/cattle-data/{id}`
|
||||
|
||||
**示例**: 更新ID为1的数据
|
||||
|
||||
**测试方法: curl**
|
||||
```bash
|
||||
curl -X PUT "http://119.45.30.82:12240/api/cattle-data/1" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "鲁西牛",
|
||||
"province": "安徽省",
|
||||
"location": "安徽淮南市",
|
||||
"price": 26.00
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.7 删除牛只数据
|
||||
|
||||
**接口**: `DELETE /api/cattle-data/{id}`
|
||||
|
||||
**示例**: 删除ID为1的数据
|
||||
|
||||
**测试方法: curl**
|
||||
```bash
|
||||
curl -X DELETE "http://119.45.30.82:12240/api/cattle-data/1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.8 批量导入数据(Excel文件)
|
||||
|
||||
**接口**: `POST /api/cattle-data/import`
|
||||
|
||||
**测试方法1: curl**
|
||||
```bash
|
||||
curl -X POST "http://119.45.30.82:12240/api/cattle-data/import" \
|
||||
-F "file=@/path/to/your/file.xlsx"
|
||||
```
|
||||
|
||||
**测试方法2: Postman**
|
||||
- Method: POST
|
||||
- URL: `http://119.45.30.82:12240/api/cattle-data/import`
|
||||
- Body: form-data
|
||||
- Key: `file` (类型选择 File)
|
||||
- Value: 选择你的 Excel 文件
|
||||
|
||||
**注意**: Excel 文件格式要求:
|
||||
- 第一行:表头(时间、产品/品种、所在产地、活牛价格(元/斤))
|
||||
- 从第二行开始:数据行
|
||||
|
||||
---
|
||||
|
||||
### 2.9 获取所有省份数据(15个省份)
|
||||
|
||||
**接口**: `GET /api/cattle-data/provinces`
|
||||
|
||||
**说明**: 返回所有省份的存栏出栏数据和省份均价
|
||||
|
||||
**测试方法1: 浏览器**
|
||||
```
|
||||
http://119.45.30.82:12240/api/cattle-data/provinces
|
||||
```
|
||||
|
||||
**测试方法2: curl**
|
||||
```bash
|
||||
curl -X GET "http://119.45.30.82:12240/api/cattle-data/provinces"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"province": "内蒙古",
|
||||
"provincePrice": 14.01,
|
||||
"inventory23th": 948,
|
||||
"slaughter23th": 464,
|
||||
"inventory24th": 938,
|
||||
"slaughter24th": 470,
|
||||
"inventory25th": 1032,
|
||||
"slaughter25th": 520,
|
||||
"createTime": "2025-11-27T10:00:00",
|
||||
"upTime": "2025-11-27T10:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.10 根据省份名称获取省份数据
|
||||
|
||||
**接口**: `GET /api/cattle-data/provinces/{province}`
|
||||
|
||||
**说明**: 根据省份名称查询单个省份的存栏出栏数据
|
||||
|
||||
**测试方法1: 浏览器**
|
||||
```
|
||||
http://119.45.30.82:12240/api/cattle-data/provinces/内蒙古
|
||||
```
|
||||
|
||||
**测试方法2: curl**
|
||||
```bash
|
||||
curl -X GET "http://119.45.30.82:12240/api/cattle-data/provinces/内蒙古"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.11 获取全国总量数据(全国存出栏)
|
||||
|
||||
**接口**: `GET /api/cattle-data/national`
|
||||
|
||||
**说明**: 自动从省份数据表计算并返回全国年份存栏和出栏总量数据
|
||||
|
||||
**测试方法1: 浏览器**
|
||||
```
|
||||
http://119.45.30.82:12240/api/cattle-data/national
|
||||
```
|
||||
|
||||
**测试方法2: curl**
|
||||
```bash
|
||||
curl -X GET "http://119.45.30.82:12240/api/cattle-data/national"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"nationalInventory23th": 3042,
|
||||
"nationalSlaughter23th": 7251,
|
||||
"nationalInventory24th": 2802,
|
||||
"nationalSlaughter24th": 7304,
|
||||
"nationalInventory25th": 2587,
|
||||
"nationalSlaughter25th": 0,
|
||||
"createTime": "2025-11-27T10:00:00",
|
||||
"upTime": "2025-11-27T10:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 如果数据不存在,会自动从 `cattleprovince` 表计算并创建
|
||||
- 全国总量 = 15个省份对应字段的总和
|
||||
|
||||
---
|
||||
|
||||
### 2.12 重新计算全国总量数据
|
||||
|
||||
**接口**: `POST /api/cattle-data/national/recalculate`
|
||||
|
||||
**说明**: 根据 `cattleprovince` 表中的所有省份数据重新计算全国总量
|
||||
|
||||
**测试方法: curl**
|
||||
```bash
|
||||
curl -X POST "http://119.45.30.82:12240/api/cattle-data/national/recalculate"
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- 直接修改了数据库中的省份数据后,需要重新计算全国总量
|
||||
- 确保全国总量数据与省份数据保持一致
|
||||
|
||||
---
|
||||
|
||||
## 3. 使用 Swagger UI 测试(推荐)
|
||||
|
||||
### 访问 Swagger UI
|
||||
```
|
||||
http://119.45.30.82:12240/swagger-ui.html
|
||||
```
|
||||
|
||||
在 Swagger UI 中:
|
||||
1. 可以看到所有接口的详细文档
|
||||
2. 可以直接在页面上测试接口
|
||||
3. 可以查看请求和响应示例
|
||||
4. 支持在线测试所有接口
|
||||
|
||||
---
|
||||
|
||||
## 4. 响应格式说明
|
||||
|
||||
所有接口返回统一的响应格式:
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
// 具体数据
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "错误信息",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 快速测试脚本
|
||||
|
||||
### 测试脚本 (test_api.sh)
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
BASE_URL="http://119.45.30.82:12240/api/cattle-data"
|
||||
|
||||
echo "=== 测试1: 获取所有数据 ==="
|
||||
curl -X GET "${BASE_URL}"
|
||||
|
||||
echo -e "\n\n=== 测试2: 按省份筛选 ==="
|
||||
curl -X GET "${BASE_URL}?province=安徽省"
|
||||
|
||||
echo -e "\n\n=== 测试3: 按品种筛选 ==="
|
||||
curl -X GET "${BASE_URL}?type=鲁西牛"
|
||||
|
||||
echo -e "\n\n=== 测试4: 创建数据 ==="
|
||||
curl -X POST "${BASE_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "测试品种",
|
||||
"province": "测试省",
|
||||
"location": "测试市",
|
||||
"price": 20.00
|
||||
}'
|
||||
|
||||
echo -e "\n\n=== 测试完成 ==="
|
||||
```
|
||||
|
||||
**使用方法**:
|
||||
```bash
|
||||
chmod +x test_api.sh
|
||||
./test_api.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 常见问题排查
|
||||
|
||||
### 6.1 连接超时
|
||||
- 检查服务器防火墙是否开放 8080 端口
|
||||
- 检查服务器是否正常运行
|
||||
- 检查网络连接
|
||||
|
||||
### 6.2 404 错误
|
||||
- 确认 URL 路径正确
|
||||
- 确认服务已启动
|
||||
- 检查 context-path 配置
|
||||
|
||||
### 6.3 500 错误
|
||||
- 查看服务器日志
|
||||
- 检查数据库连接
|
||||
- 检查请求参数格式
|
||||
|
||||
### 6.4 查看服务日志
|
||||
```bash
|
||||
# 如果使用启动脚本
|
||||
tail -f log.out
|
||||
|
||||
# 或查看 Spring Boot 日志
|
||||
tail -f logs/spring.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 接口列表汇总
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/cattle-data` | 获取所有数据(支持 province、type 参数筛选) |
|
||||
| GET | `/api/cattle-data/{id}` | 根据ID获取单条数据 |
|
||||
| POST | `/api/cattle-data` | 创建新数据 |
|
||||
| PUT | `/api/cattle-data/{id}` | 更新数据 |
|
||||
| DELETE | `/api/cattle-data/{id}` | 删除数据 |
|
||||
| POST | `/api/cattle-data/import` | 批量导入Excel文件 |
|
||||
| POST | `/api/cattle-data/import-province` | 批量导入省份存栏出栏及均价(Excel) |
|
||||
| GET | `/api/cattle-data/provinces` | 获取所有15个省份的存栏出栏数据和省份均价 |
|
||||
| GET | `/api/cattle-data/provinces/{province}` | 根据省份名称查询单个省份的存栏出栏数据 |
|
||||
| GET | `/api/cattle-data/national` | 获取全国总量数据(自动从省份数据表计算) |
|
||||
| POST | `/api/cattle-data/national/recalculate` | 重新计算全国总量数据 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试建议
|
||||
|
||||
1. **先测试 GET 接口**:确认服务正常运行
|
||||
2. **测试 POST 创建**:添加测试数据
|
||||
3. **测试 PUT 更新**:修改刚创建的数据
|
||||
4. **测试 DELETE 删除**:清理测试数据
|
||||
5. **测试导入功能**:使用真实的 Excel 文件
|
||||
|
||||
---
|
||||
|
||||
## 9. 性能测试
|
||||
|
||||
### 使用 Apache Bench (ab) 进行压力测试
|
||||
```bash
|
||||
# 安装 ab
|
||||
yum install httpd-tools -y # CentOS
|
||||
apt-get install apache2-utils -y # Ubuntu
|
||||
|
||||
# 测试 GET 接口
|
||||
ab -n 1000 -c 10 http://119.45.30.82:12240/api/cattle-data
|
||||
|
||||
# 参数说明:
|
||||
# -n: 总请求数
|
||||
# -c: 并发数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
祝测试顺利!如有问题,请查看服务器日志或联系技术支持。
|
||||
|
||||
133
backend/TEST_CONNECTION.md
Normal file
133
backend/TEST_CONNECTION.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# 连接测试指南
|
||||
|
||||
## 当前状态
|
||||
- ✅ 服务在服务器上正常运行
|
||||
- ✅ 本地访问成功 (localhost:12240)
|
||||
- ❌ 外部访问失败 (119.45.30.82:12240)
|
||||
|
||||
## 问题原因
|
||||
外部无法访问通常是以下原因之一:
|
||||
1. **服务器防火墙未开放端口**(最常见)
|
||||
2. **云服务器安全组未配置**
|
||||
3. **网络路由问题**
|
||||
|
||||
## 解决步骤
|
||||
|
||||
### 步骤1: 配置服务器防火墙
|
||||
|
||||
在服务器上执行:
|
||||
|
||||
```bash
|
||||
# 方法1: 使用 firewalld (CentOS 7+/RHEL 7+)
|
||||
sudo firewall-cmd --add-port=12240/tcp --permanent
|
||||
sudo firewall-cmd --reload
|
||||
sudo firewall-cmd --list-ports # 验证
|
||||
|
||||
# 方法2: 使用 iptables
|
||||
sudo iptables -A INPUT -p tcp --dport 12240 -j ACCEPT
|
||||
sudo service iptables save
|
||||
# 或
|
||||
sudo iptables-save > /etc/sysconfig/iptables
|
||||
|
||||
# 方法3: 使用提供的脚本
|
||||
chmod +x open_firewall.sh
|
||||
sudo ./open_firewall.sh
|
||||
```
|
||||
|
||||
### 步骤2: 配置云服务器安全组
|
||||
|
||||
#### 阿里云
|
||||
1. 登录阿里云控制台
|
||||
2. 进入 **ECS** → **网络与安全** → **安全组**
|
||||
3. 选择对应的安全组
|
||||
4. 点击 **配置规则** → **添加安全组规则**
|
||||
5. 配置:
|
||||
- 规则方向:入方向
|
||||
- 授权策略:允许
|
||||
- 协议类型:TCP
|
||||
- 端口范围:12240/12240
|
||||
- 授权对象:0.0.0.0/0(或指定IP)
|
||||
|
||||
#### 腾讯云
|
||||
1. 登录腾讯云控制台
|
||||
2. 进入 **云服务器** → **安全组**
|
||||
3. 选择对应的安全组 → **修改规则**
|
||||
4. 添加入站规则:
|
||||
- 类型:自定义
|
||||
- 来源:0.0.0.0/0
|
||||
- 协议端口:TCP:12240
|
||||
- 策略:允许
|
||||
|
||||
#### 其他云服务商
|
||||
类似操作,在安全组/防火墙规则中添加 TCP 12240 端口的入站规则。
|
||||
|
||||
### 步骤3: 验证配置
|
||||
|
||||
```bash
|
||||
# 1. 检查端口监听(应该显示 0.0.0.0:12240)
|
||||
netstat -tlnp | grep 12240
|
||||
# 或
|
||||
ss -tlnp | grep 12240
|
||||
|
||||
# 2. 检查防火墙规则
|
||||
firewall-cmd --list-ports # firewalld
|
||||
# 或
|
||||
iptables -L INPUT -n | grep 12240 # iptables
|
||||
|
||||
# 3. 从服务器测试外部IP
|
||||
curl http://119.45.30.82:12240/api/cattle-data
|
||||
|
||||
# 4. 从外部测试(在本地Windows PowerShell)
|
||||
curl http://119.45.30.82:12240/api/cattle-data
|
||||
```
|
||||
|
||||
## 临时测试方案(SSH隧道)
|
||||
|
||||
如果暂时无法配置防火墙,可以使用 SSH 隧道进行测试:
|
||||
|
||||
```bash
|
||||
# 在本地Windows PowerShell中执行
|
||||
ssh -L 12240:localhost:12240 root@119.45.30.82
|
||||
|
||||
# 然后在另一个终端测试
|
||||
curl http://localhost:12240/api/cattle-data
|
||||
```
|
||||
|
||||
## 诊断工具
|
||||
|
||||
使用提供的诊断脚本:
|
||||
|
||||
```bash
|
||||
chmod +x check_connection.sh
|
||||
./check_connection.sh
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 防火墙已配置但仍无法访问?
|
||||
A: 检查云服务器安全组,这是最常见的遗漏。
|
||||
|
||||
### Q: 如何确认端口是否开放?
|
||||
A: 使用在线端口检测工具,或从其他服务器测试。
|
||||
|
||||
### Q: 服务只监听 localhost?
|
||||
A: Spring Boot 默认监听 0.0.0.0,如需明确指定:
|
||||
```yaml
|
||||
server:
|
||||
address: 0.0.0.0
|
||||
port: 12240
|
||||
```
|
||||
|
||||
## 测试命令汇总
|
||||
|
||||
```bash
|
||||
# 服务器端
|
||||
netstat -tlnp | grep 12240 # 检查监听
|
||||
curl http://localhost:12240/api/cattle-data # 本地测试
|
||||
firewall-cmd --list-ports # 查看防火墙端口
|
||||
|
||||
# 客户端(Windows PowerShell)
|
||||
curl http://119.45.30.82:12240/api/cattle-data
|
||||
Invoke-WebRequest -Uri http://119.45.30.82:12240/api/cattle-data
|
||||
```
|
||||
|
||||
130
backend/TROUBLESHOOTING_500_ERROR.md
Normal file
130
backend/TROUBLESHOOTING_500_ERROR.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# 500 错误排查指南
|
||||
|
||||
## 问题描述
|
||||
导入省份数据时出现 500 Internal Server Error
|
||||
|
||||
## 可能的原因和解决方案
|
||||
|
||||
### 1. 数据库表未创建
|
||||
|
||||
**检查步骤:**
|
||||
```sql
|
||||
-- 连接到数据库
|
||||
USE cattleTends;
|
||||
|
||||
-- 检查表是否存在
|
||||
SHOW TABLES LIKE 'cattleprovince';
|
||||
SHOW TABLES LIKE 'cattlenational';
|
||||
```
|
||||
|
||||
**如果表不存在,执行以下 SQL:**
|
||||
|
||||
#### 创建 cattleprovince 表
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS cattleprovince (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
province VARCHAR(255) NOT NULL UNIQUE COMMENT '省份名称',
|
||||
province_price DECIMAL(10, 2) COMMENT '省份均价',
|
||||
inventory_23th INT COMMENT '23年存栏(万头)',
|
||||
slaughter_23th INT COMMENT '23年出栏(万头)',
|
||||
inventory_24th INT COMMENT '24年存栏(万头)',
|
||||
slaughter_24th INT COMMENT '24年出栏(万头)',
|
||||
inventory_25th INT COMMENT '25年存栏(万头)',
|
||||
slaughter_25th INT COMMENT '25年出栏(万头)',
|
||||
create_time DATETIME COMMENT '创建时间',
|
||||
up_time DATETIME NOT NULL COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='省份数据表';
|
||||
|
||||
-- 添加索引
|
||||
CREATE INDEX idx_province_name ON cattleprovince(province);
|
||||
CREATE INDEX idx_province_price ON cattleprovince(province_price);
|
||||
CREATE INDEX idx_inv_23 ON cattleprovince(inventory_23th);
|
||||
CREATE INDEX idx_sla_23 ON cattleprovince(slaughter_23th);
|
||||
CREATE INDEX idx_inv_24 ON cattleprovince(inventory_24th);
|
||||
CREATE INDEX idx_sla_24 ON cattleprovince(slaughter_24th);
|
||||
CREATE INDEX idx_inv_25 ON cattleprovince(inventory_25th);
|
||||
CREATE INDEX idx_sla_25 ON cattleprovince(slaughter_25th);
|
||||
```
|
||||
|
||||
#### 创建 cattlenational 表
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS cattlenational (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
national_inventory_23th INT COMMENT '23年全国存栏量(15个省份存栏量的总和)',
|
||||
national_slaughter_23th INT COMMENT '23年全国出栏量(15个省份出栏量的总和)',
|
||||
national_inventory_24th INT COMMENT '24年全国存栏量(15个省份存栏量的总和)',
|
||||
national_slaughter_24th INT COMMENT '24年全国出栏量(15个省份出栏量的总和)',
|
||||
national_inventory_25th INT COMMENT '25年全国存栏量(15个省份存栏量的总和)',
|
||||
national_slaughter_25th INT COMMENT '25年全国出栏量(15个省份出栏量的总和)',
|
||||
create_time DATETIME COMMENT '创建时间',
|
||||
up_time DATETIME NOT NULL COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全国总量数据表';
|
||||
|
||||
-- 添加索引
|
||||
CREATE INDEX idx_national_inv_23 ON cattlenational(national_inventory_23th);
|
||||
CREATE INDEX idx_national_sla_23 ON cattlenational(national_slaughter_23th);
|
||||
CREATE INDEX idx_national_inv_24 ON cattlenational(national_inventory_24th);
|
||||
CREATE INDEX idx_national_sla_24 ON cattlenational(national_slaughter_24th);
|
||||
CREATE INDEX idx_national_inv_25 ON cattlenational(national_inventory_25th);
|
||||
CREATE INDEX idx_national_sla_25 ON cattlenational(national_slaughter_25th);
|
||||
```
|
||||
|
||||
### 2. 查看后端日志
|
||||
|
||||
**检查后端日志文件:**
|
||||
```bash
|
||||
# 如果使用 start.sh 启动
|
||||
tail -f log.out
|
||||
|
||||
# 或者查看 Spring Boot 控制台输出
|
||||
```
|
||||
|
||||
**常见错误信息:**
|
||||
- `Table 'cattleTends.cattleprovince' doesn't exist` - 表不存在
|
||||
- `Table 'cattleTends.cattlenational' doesn't exist` - 表不存在
|
||||
- `Could not resolve placeholder` - 配置问题
|
||||
- `No bean found` - 依赖注入问题
|
||||
|
||||
### 3. 检查后端服务是否正常运行
|
||||
|
||||
```bash
|
||||
# 检查服务是否运行
|
||||
ps aux | grep java | grep cattletends
|
||||
|
||||
# 检查端口是否监听
|
||||
netstat -tlnp | grep 12240
|
||||
```
|
||||
|
||||
### 4. 重新编译和重启
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean package
|
||||
./start.sh restart
|
||||
```
|
||||
|
||||
### 5. 测试接口
|
||||
|
||||
```bash
|
||||
# 测试获取省份数据接口
|
||||
curl http://localhost:12240/api/cattle-data/provinces
|
||||
|
||||
# 测试获取全国总量接口
|
||||
curl http://localhost:12240/api/cattle-data/national
|
||||
```
|
||||
|
||||
## 快速修复步骤
|
||||
|
||||
1. **确保数据库表已创建**(执行上面的 SQL)
|
||||
2. **重新编译后端**:`cd backend && mvn clean package`
|
||||
3. **重启服务**:`./start.sh restart`
|
||||
4. **查看日志**:`tail -f log.out`
|
||||
5. **重新尝试导入**
|
||||
|
||||
## 如果问题仍然存在
|
||||
|
||||
请提供以下信息:
|
||||
1. 后端日志的完整错误信息
|
||||
2. 数据库表是否已创建(执行 `SHOW TABLES;`)
|
||||
3. Excel 文件的格式(列数、数据类型)
|
||||
|
||||
107
backend/check_connection.sh
Normal file
107
backend/check_connection.sh
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 检查服务连接性脚本
|
||||
|
||||
PORT=12240
|
||||
SERVER_IP="119.45.30.82"
|
||||
|
||||
echo "=== 检查服务连接性 ==="
|
||||
echo ""
|
||||
|
||||
# 1. 检查服务是否在运行
|
||||
echo "1. 检查服务进程..."
|
||||
PID=$(ps aux | grep cattletends | grep -v grep | awk '{print $2}')
|
||||
if [ -n "$PID" ]; then
|
||||
echo " ✓ 服务正在运行,PID: $PID"
|
||||
else
|
||||
echo " ✗ 服务未运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 2. 检查端口是否监听
|
||||
echo "2. 检查端口 $PORT 监听状态..."
|
||||
LISTENING=$(netstat -tlnp 2>/dev/null | grep ":$PORT " || ss -tlnp 2>/dev/null | grep ":$PORT ")
|
||||
if [ -n "$LISTENING" ]; then
|
||||
echo " ✓ 端口 $PORT 正在监听"
|
||||
echo " 详细信息:"
|
||||
echo "$LISTENING" | sed 's/^/ /'
|
||||
|
||||
# 检查监听地址
|
||||
if echo "$LISTENING" | grep -q "0.0.0.0:$PORT\|:::$PORT"; then
|
||||
echo " ✓ 服务监听在所有网络接口上(0.0.0.0),可以从外部访问"
|
||||
elif echo "$LISTENING" | grep -q "127.0.0.1:$PORT\|localhost:$PORT"; then
|
||||
echo " ⚠ 服务只监听在 localhost,无法从外部访问"
|
||||
echo " 需要修改配置监听 0.0.0.0"
|
||||
fi
|
||||
else
|
||||
echo " ✗ 端口 $PORT 未监听"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 3. 本地测试
|
||||
echo "3. 本地连接测试..."
|
||||
if curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT/api/cattle-data | grep -q "200\|404"; then
|
||||
echo " ✓ 本地连接成功"
|
||||
else
|
||||
echo " ✗ 本地连接失败"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 4. 检查防火墙
|
||||
echo "4. 检查防火墙状态..."
|
||||
if command -v firewall-cmd &> /dev/null; then
|
||||
FIREWALL_STATUS=$(firewall-cmd --state 2>/dev/null)
|
||||
if [ "$FIREWALL_STATUS" = "running" ]; then
|
||||
echo " ⚠ Firewalld 正在运行"
|
||||
PORT_OPEN=$(firewall-cmd --query-port=$PORT/tcp 2>/dev/null)
|
||||
if [ "$PORT_OPEN" = "yes" ]; then
|
||||
echo " ✓ 端口 $PORT 已在防火墙中开放"
|
||||
else
|
||||
echo " ✗ 端口 $PORT 未在防火墙中开放"
|
||||
echo ""
|
||||
echo " 执行以下命令开放端口:"
|
||||
echo " firewall-cmd --add-port=$PORT/tcp --permanent"
|
||||
echo " firewall-cmd --reload"
|
||||
fi
|
||||
else
|
||||
echo " ℹ Firewalld 未运行或未安装"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 5. 检查 iptables
|
||||
if command -v iptables &> /dev/null; then
|
||||
IPTABLES_RULE=$(iptables -L INPUT -n | grep "$PORT" || iptables -L INPUT -n | grep "ACCEPT.*tcp")
|
||||
if [ -n "$IPTABLES_RULE" ]; then
|
||||
echo " ℹ 检测到 iptables 规则"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 6. 网络接口检查
|
||||
echo "5. 网络接口信息..."
|
||||
IP_ADDR=$(hostname -I | awk '{print $1}' 2>/dev/null || ip addr show | grep "inet " | grep -v "127.0.0.1" | head -1 | awk '{print $2}' | cut -d'/' -f1)
|
||||
if [ -n "$IP_ADDR" ]; then
|
||||
echo " 服务器IP地址: $IP_ADDR"
|
||||
if [ "$IP_ADDR" = "$SERVER_IP" ]; then
|
||||
echo " ✓ IP地址匹配"
|
||||
else
|
||||
echo " ⚠ IP地址不匹配,请确认服务器IP是否正确"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 检查完成 ==="
|
||||
echo ""
|
||||
echo "如果本地测试成功但外部无法访问,请检查:"
|
||||
echo "1. 云服务器安全组是否开放 $PORT 端口"
|
||||
echo "2. 服务器防火墙是否开放 $PORT 端口"
|
||||
echo "3. 服务是否监听在 0.0.0.0 而不是 127.0.0.1"
|
||||
|
||||
46
backend/fix_port_conflict.sh
Normal file
46
backend/fix_port_conflict.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 修复端口冲突脚本
|
||||
|
||||
PORT=8080
|
||||
|
||||
echo "=== 检查端口 $PORT 占用情况 ==="
|
||||
|
||||
# 查找占用端口的进程
|
||||
PID=$(lsof -ti:$PORT 2>/dev/null || netstat -tlnp 2>/dev/null | grep ":$PORT " | awk '{print $7}' | cut -d'/' -f1 | head -1)
|
||||
|
||||
if [ -n "$PID" ]; then
|
||||
echo "找到占用端口 $PORT 的进程: PID=$PID"
|
||||
|
||||
# 显示进程信息
|
||||
ps aux | grep $PID | grep -v grep
|
||||
|
||||
echo ""
|
||||
read -p "是否要停止该进程? (y/n): " confirm
|
||||
|
||||
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
|
||||
echo "正在停止进程 $PID..."
|
||||
kill -15 $PID
|
||||
|
||||
# 等待进程结束
|
||||
sleep 2
|
||||
|
||||
# 如果还在运行,强制杀死
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
echo "强制停止进程..."
|
||||
kill -9 $PID
|
||||
fi
|
||||
|
||||
echo "进程已停止"
|
||||
else
|
||||
echo "取消操作"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "未找到占用端口 $PORT 的进程"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 现在可以重新启动应用 ==="
|
||||
echo "./start.sh start"
|
||||
|
||||
89
backend/open_firewall.sh
Normal file
89
backend/open_firewall.sh
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 开放防火墙端口脚本
|
||||
|
||||
PORT=12240
|
||||
|
||||
echo "=== 开放端口 $PORT 的防火墙配置 ==="
|
||||
echo ""
|
||||
|
||||
# 检查是否有 root 权限
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "⚠ 需要 root 权限执行此脚本"
|
||||
echo "请使用: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 方法1: 使用 firewalld (CentOS 7+/RHEL 7+)
|
||||
if command -v firewall-cmd &> /dev/null; then
|
||||
echo "检测到 firewalld,使用 firewalld 配置..."
|
||||
|
||||
# 检查防火墙状态
|
||||
if systemctl is-active --quiet firewalld; then
|
||||
echo "✓ Firewalld 正在运行"
|
||||
|
||||
# 添加端口
|
||||
echo "正在添加端口 $PORT/tcp..."
|
||||
firewall-cmd --add-port=$PORT/tcp --permanent
|
||||
|
||||
# 重新加载配置
|
||||
echo "重新加载防火墙配置..."
|
||||
firewall-cmd --reload
|
||||
|
||||
# 验证
|
||||
if firewall-cmd --query-port=$PORT/tcp | grep -q "yes"; then
|
||||
echo "✓ 端口 $PORT 已成功开放"
|
||||
else
|
||||
echo "✗ 端口开放失败"
|
||||
fi
|
||||
|
||||
# 显示当前开放的端口
|
||||
echo ""
|
||||
echo "当前开放的端口:"
|
||||
firewall-cmd --list-ports
|
||||
else
|
||||
echo "⚠ Firewalld 未运行,尝试启动..."
|
||||
systemctl start firewalld
|
||||
systemctl enable firewalld
|
||||
|
||||
firewall-cmd --add-port=$PORT/tcp --permanent
|
||||
firewall-cmd --reload
|
||||
firewall-cmd --query-port=$PORT/tcp
|
||||
fi
|
||||
fi
|
||||
|
||||
# 方法2: 使用 iptables (CentOS 6/其他系统)
|
||||
if command -v iptables &> /dev/null && ! command -v firewall-cmd &> /dev/null; then
|
||||
echo ""
|
||||
echo "检测到 iptables,使用 iptables 配置..."
|
||||
|
||||
# 检查规则是否已存在
|
||||
if iptables -C INPUT -p tcp --dport $PORT -j ACCEPT 2>/dev/null; then
|
||||
echo "✓ 端口 $PORT 的规则已存在"
|
||||
else
|
||||
echo "正在添加 iptables 规则..."
|
||||
iptables -A INPUT -p tcp --dport $PORT -j ACCEPT
|
||||
|
||||
# 保存规则
|
||||
if [ -f /etc/sysconfig/iptables ]; then
|
||||
iptables-save > /etc/sysconfig/iptables
|
||||
echo "✓ 规则已保存到 /etc/sysconfig/iptables"
|
||||
elif command -v netfilter-persistent &> /dev/null; then
|
||||
netfilter-persistent save
|
||||
echo "✓ 规则已保存"
|
||||
else
|
||||
echo "⚠ 请手动保存 iptables 规则"
|
||||
fi
|
||||
|
||||
echo "✓ 端口 $PORT 已开放"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 配置完成 ==="
|
||||
echo ""
|
||||
echo "请检查:"
|
||||
echo "1. 云服务器安全组是否开放 $PORT 端口"
|
||||
echo "2. 测试外部访问: curl http://119.45.30.82:$PORT/api/cattle-data"
|
||||
echo "3. 查看监听状态: netstat -tlnp | grep $PORT"
|
||||
|
||||
122
backend/pom.xml
Normal file
122
backend/pom.xml
Normal file
@@ -0,0 +1,122 @@
|
||||
<?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>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.18</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>cattletends</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>cattleTends</name>
|
||||
<description>牛只数据管理后端接口</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Data JPA -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL Driver -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger/OpenAPI -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok (可选,用于简化代码) -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache POI for Excel processing -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Maven 编译器插件,强制使用 Java 8 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<release>8</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.example.cattletends;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* SpringBoot主启动类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class CattleTendsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CattleTendsApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.example.cattletends.common;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结果封装类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 响应码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 成功响应(无数据)
|
||||
*/
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(200, "操作成功", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(有数据)
|
||||
*/
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(200, "操作成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(自定义消息,无数据)
|
||||
*/
|
||||
public static <T> Result<T> success(String message) {
|
||||
return new Result<>(200, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> success(String message, T data) {
|
||||
return new Result<>(200, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*/
|
||||
public static <T> Result<T> error(String message) {
|
||||
return new Result<>(500, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应(自定义错误码)
|
||||
*/
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
return new Result<>(code, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应(自定义错误码和数据)
|
||||
*/
|
||||
public static <T> Result<T> error(Integer code, String message, T data) {
|
||||
return new Result<>(code, message, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.example.cattletends.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
|
||||
/**
|
||||
* Swagger配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public Docket api() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.example.cattletends.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("牛只数据管理API")
|
||||
.version("1.0.0")
|
||||
.description("牛只数据管理系统的RESTful API接口文档")
|
||||
.contact(new Contact("CattleTends Team", "", "support@example.com"))
|
||||
.license("Apache 2.0")
|
||||
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0.html")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,749 @@
|
||||
package com.example.cattletends.controller;
|
||||
|
||||
import com.example.cattletends.common.Result;
|
||||
import com.example.cattletends.dto.CattleDataDTO;
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import com.example.cattletends.service.CattleDataService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import javax.validation.Valid;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 牛只数据控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/cattle-data")
|
||||
@Api(tags = "牛只数据管理", description = "牛只数据的CRUD接口")
|
||||
public class CattleDataController {
|
||||
|
||||
private final CattleDataService cattleDataService;
|
||||
private final com.example.cattletends.service.CattleProvinceService cattleProvinceService;
|
||||
private final com.example.cattletends.service.CattleNationalService cattleNationalService;
|
||||
private final com.example.cattletends.service.CattleBreedService cattleBreedService;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleDataController(CattleDataService cattleDataService,
|
||||
com.example.cattletends.service.CattleProvinceService cattleProvinceService,
|
||||
com.example.cattletends.service.CattleNationalService cattleNationalService,
|
||||
com.example.cattletends.service.CattleBreedService cattleBreedService) {
|
||||
this.cattleDataService = cattleDataService;
|
||||
this.cattleProvinceService = cattleProvinceService;
|
||||
this.cattleNationalService = cattleNationalService;
|
||||
this.cattleBreedService = cattleBreedService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有牛只数据(支持按省份或品种筛选)
|
||||
*/
|
||||
@GetMapping
|
||||
@ApiOperation(value = "获取所有牛只数据", notes = "返回所有牛只数据的列表,支持按省份或品种筛选,按价格升序排序")
|
||||
public Result<List<CattleData>> getAllCattleData(
|
||||
@ApiParam(value = "省份(可选,如果提供则只返回该省份的数据)")
|
||||
@RequestParam(required = false) String province,
|
||||
@ApiParam(value = "品种(可选,如果提供则只返回该品种的数据)")
|
||||
@RequestParam(required = false) String type) {
|
||||
List<CattleData> data;
|
||||
|
||||
// 优先按品种筛选
|
||||
if (type != null && !type.trim().isEmpty()) {
|
||||
data = cattleDataService.getCattleDataByType(type.trim());
|
||||
} else if (province != null && !province.trim().isEmpty()) {
|
||||
// 其次按省份筛选
|
||||
data = cattleDataService.getCattleDataByProvince(province.trim());
|
||||
} else {
|
||||
// 都不提供则返回所有数据
|
||||
data = cattleDataService.getAllCattleData();
|
||||
}
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿里云大屏接口 - 获取牛只数据(直接返回数据数组,不包含Result包装)
|
||||
*/
|
||||
@GetMapping("/screen")
|
||||
@ApiOperation(value = "阿里云大屏接口", notes = "返回牛只数据数组,直接返回data数据,不包含Result包装。支持按省份或品种筛选,按价格升序排序")
|
||||
public List<CattleData> getCattleDataForScreen(
|
||||
@ApiParam(value = "省份(可选,如果提供则只返回该省份的数据)")
|
||||
@RequestParam(required = false) String province,
|
||||
@ApiParam(value = "品种(可选,如果提供则只返回该品种的数据)")
|
||||
@RequestParam(required = false) String type) {
|
||||
// 优先按品种筛选
|
||||
if (type != null && !type.trim().isEmpty()) {
|
||||
return cattleDataService.getCattleDataByType(type.trim());
|
||||
} else if (province != null && !province.trim().isEmpty()) {
|
||||
// 其次按省份筛选
|
||||
return cattleDataService.getCattleDataByProvince(province.trim());
|
||||
} else {
|
||||
// 都不提供则返回所有数据
|
||||
return cattleDataService.getAllCattleData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取牛只数据
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@ApiOperation(value = "根据ID获取牛只数据", notes = "根据主键ID查询单个牛只数据")
|
||||
public Result<CattleData> getCattleDataById(
|
||||
@ApiParam(value = "主键ID", required = true)
|
||||
@PathVariable Integer id) {
|
||||
CattleData data = cattleDataService.getCattleDataById(id);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入牛只数据(Excel文件)
|
||||
* 注意:此接口必须在 @PostMapping 之前定义,避免路径冲突
|
||||
*/
|
||||
@PostMapping("/import")
|
||||
@ApiOperation(value = "批量导入牛只数据", notes = "通过Excel文件批量导入牛只数据,自动截取所在产地到市级别,并自动创建品种")
|
||||
public Result<List<CattleData>> importCattleData(
|
||||
@ApiParam(value = "Excel文件", required = true)
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
System.out.println("========== 收到导入牛只数据请求 ==========");
|
||||
System.out.println("文件名: " + file.getOriginalFilename());
|
||||
System.out.println("文件大小: " + file.getSize());
|
||||
|
||||
if (file.isEmpty()) {
|
||||
System.out.println("错误: 文件为空");
|
||||
return Result.error(400, "文件不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
System.out.println("开始解析Excel文件...");
|
||||
List<CattleDataDTO> dataList = parseExcelFile(file);
|
||||
System.out.println("解析完成,共 " + (dataList != null ? dataList.size() : 0) + " 条数据");
|
||||
|
||||
if (dataList == null || dataList.isEmpty()) {
|
||||
System.out.println("错误: Excel文件中没有有效数据");
|
||||
return Result.error(400, "Excel文件中没有有效数据,请检查文件格式");
|
||||
}
|
||||
|
||||
// 自动创建品种
|
||||
System.out.println("========== 开始自动创建品种 ==========");
|
||||
int newBreedCount = 0;
|
||||
int existingBreedCount = 0;
|
||||
java.util.Set<String> processedBreeds = new java.util.HashSet<>();
|
||||
|
||||
for (CattleDataDTO dto : dataList) {
|
||||
if (dto.getType() != null && !dto.getType().trim().isEmpty()) {
|
||||
String breedName = dto.getType().trim();
|
||||
// 避免重复处理相同品种
|
||||
if (!processedBreeds.contains(breedName)) {
|
||||
try {
|
||||
com.example.cattletends.entity.CattleBreed existingBreed = cattleBreedService.getBreedByName(breedName);
|
||||
if (existingBreed == null) {
|
||||
// 新品种,创建
|
||||
cattleBreedService.createOrGetBreed(breedName);
|
||||
newBreedCount++;
|
||||
System.out.println("✓ 创建新品种: " + breedName);
|
||||
} else {
|
||||
existingBreedCount++;
|
||||
System.out.println("○ 品种已存在: " + breedName);
|
||||
}
|
||||
processedBreeds.add(breedName);
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 创建品种失败: " + breedName + ", 错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("品种处理完成: 新增 " + newBreedCount + " 个,已存在 " + existingBreedCount + " 个,共处理 " + processedBreeds.size() + " 个品种");
|
||||
System.out.println("========== 品种创建完成 ==========");
|
||||
|
||||
System.out.println("开始导入数据到数据库...");
|
||||
com.example.cattletends.dto.ImportResult importResult = cattleDataService.batchImportCattleData(dataList);
|
||||
System.out.println("导入完成: 新增 " + importResult.getNewCount() + " 条,更新 " + importResult.getUpdateCount() + " 条");
|
||||
|
||||
String message = String.format("导入成功:新增 %d 条,更新 %d 条,共处理 %d 条数据;自动创建 %d 个新品种,%d 个品种已存在",
|
||||
importResult.getNewCount(), importResult.getUpdateCount(), importResult.getTotalCount(),
|
||||
newBreedCount, existingBreedCount);
|
||||
return Result.success(message, importResult.getDataList());
|
||||
} catch (Exception e) {
|
||||
System.err.println("========== 导入失败 ==========");
|
||||
System.err.println("异常类型: " + e.getClass().getName());
|
||||
System.err.println("异常信息: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
String errorMessage = e.getMessage();
|
||||
if (errorMessage == null || errorMessage.isEmpty()) {
|
||||
errorMessage = e.getClass().getSimpleName() + ": " + e.toString();
|
||||
}
|
||||
return Result.error(500, "导入失败:" + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有品种
|
||||
*/
|
||||
@GetMapping("/breeds")
|
||||
@ApiOperation(value = "获取所有品种", notes = "返回所有品种列表")
|
||||
public Result<List<com.example.cattletends.entity.CattleBreed>> getAllBreeds() {
|
||||
List<com.example.cattletends.entity.CattleBreed> breeds = cattleBreedService.getAllBreeds();
|
||||
return Result.success(breeds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有省份数据
|
||||
*/
|
||||
@GetMapping("/provinces")
|
||||
@ApiOperation(value = "获取省份数据", notes = "返回所有省份的存栏出栏数据和省份均价,按省份均价降序排序。支持通过province参数按省份查询")
|
||||
public Result<List<com.example.cattletends.entity.CattleProvince>> getAllProvinceData(
|
||||
@ApiParam(value = "省份名称(可选),如果提供则只返回该省份数据", required = false)
|
||||
@RequestParam(required = false) String province) {
|
||||
List<com.example.cattletends.entity.CattleProvince> data = cattleProvinceService.getAllProvinceData(province);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿里云大屏接口 - 获取省份数据(直接返回数据数组,不包含Result包装)
|
||||
*/
|
||||
@GetMapping("/provinces/screen")
|
||||
@ApiOperation(value = "阿里云大屏接口-省份数据", notes = "返回省份数据数组,直接返回data数据,不包含Result包装。按省份均价降序排序,支持通过province参数按省份查询")
|
||||
public List<com.example.cattletends.entity.CattleProvince> getProvinceDataForScreen(
|
||||
@ApiParam(value = "省份名称(可选),如果提供则只返回该省份数据", required = false)
|
||||
@RequestParam(required = false) String province) {
|
||||
return cattleProvinceService.getAllProvinceData(province);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据省份名称获取省份数据
|
||||
*/
|
||||
@GetMapping("/provinces/{province}")
|
||||
@ApiOperation(value = "根据省份名称获取省份数据", notes = "根据省份名称查询单个省份的存栏出栏数据")
|
||||
public Result<com.example.cattletends.entity.CattleProvince> getProvinceDataByName(
|
||||
@ApiParam(value = "省份名称", required = true)
|
||||
@PathVariable String province) {
|
||||
com.example.cattletends.entity.CattleProvince data = cattleProvinceService.getProvinceDataByName(province);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全国总量数据
|
||||
* 自动从 cattleprovince 表计算总和
|
||||
*/
|
||||
@GetMapping("/national")
|
||||
@ApiOperation(value = "获取全国总量数据", notes = "自动从省份数据表计算并返回全国年份存栏和出栏总量数据")
|
||||
public Result<com.example.cattletends.entity.CattleNational> getNationalData() {
|
||||
com.example.cattletends.entity.CattleNational data = cattleNationalService.getNationalData();
|
||||
if (data == null) {
|
||||
return Result.error(404, "全国总量数据不存在,请先导入省份数据");
|
||||
}
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算全国总量数据
|
||||
* 根据 cattleprovince 表中的所有省份数据重新计算总和
|
||||
*/
|
||||
@PostMapping("/national/recalculate")
|
||||
@ApiOperation(value = "重新计算全国总量数据", notes = "根据 cattleprovince 表中的所有省份数据重新计算全国总量")
|
||||
public Result<com.example.cattletends.entity.CattleNational> recalculateNationalData() {
|
||||
com.example.cattletends.entity.CattleNational data = cattleNationalService.calculateAndUpdateNationalData();
|
||||
return Result.success("重新计算成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入省份数据(Excel文件)
|
||||
* 导入15个省份的存栏出栏量和省份均价,并计算全国总量
|
||||
*/
|
||||
@PostMapping("/import-province")
|
||||
@ApiOperation(value = "导入省份数据", notes = "导入15个省份的存栏出栏量和省份均价,自动计算全国总量")
|
||||
public Result<Map<String, Object>> importProvinceData(
|
||||
@ApiParam(value = "Excel文件", required = true)
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
System.out.println("========== 收到导入省份数据请求 ==========");
|
||||
System.out.println("文件名: " + file.getOriginalFilename());
|
||||
System.out.println("文件大小: " + file.getSize());
|
||||
|
||||
if (file.isEmpty()) {
|
||||
System.out.println("错误: 文件为空");
|
||||
return Result.error(400, "文件不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
System.out.println("开始解析Excel文件...");
|
||||
List<com.example.cattletends.dto.ProvinceDataDTO> provinceDataList = parseProvinceExcelFile(file);
|
||||
System.out.println("解析完成,共 " + (provinceDataList != null ? provinceDataList.size() : 0) + " 条数据");
|
||||
|
||||
if (provinceDataList == null || provinceDataList.isEmpty()) {
|
||||
System.out.println("错误: Excel文件中没有有效数据");
|
||||
return Result.error(400, "Excel文件中没有有效数据,请检查文件格式");
|
||||
}
|
||||
|
||||
System.out.println("开始导入数据到数据库...");
|
||||
Map<String, Object> result = cattleProvinceService.importProvinceData(provinceDataList);
|
||||
System.out.println("导入完成: " + result);
|
||||
|
||||
String message = String.format("导入成功:新增 %d 条,更新 %d 条,共处理 %d 条省份数据",
|
||||
result.get("createCount"), result.get("updateCount"), result.get("totalCount"));
|
||||
return Result.success(message, result);
|
||||
} catch (Exception e) {
|
||||
System.err.println("========== 导入失败 ==========");
|
||||
System.err.println("异常类型: " + e.getClass().getName());
|
||||
System.err.println("异常信息: " + e.getMessage());
|
||||
e.printStackTrace(); // 打印完整堆栈信息
|
||||
|
||||
String errorMessage = e.getMessage();
|
||||
if (errorMessage == null || errorMessage.isEmpty()) {
|
||||
errorMessage = e.getClass().getSimpleName() + ": " + e.toString();
|
||||
}
|
||||
return Result.error(500, "导入失败:" + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建牛只数据
|
||||
*/
|
||||
@PostMapping
|
||||
@ApiOperation(value = "创建牛只数据", notes = "创建新的牛只数据记录")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Result<CattleData> createCattleData(
|
||||
@ApiParam(value = "牛只数据信息", required = true)
|
||||
@Valid @RequestBody CattleDataDTO dto) {
|
||||
CattleData data = cattleDataService.createCattleData(dto);
|
||||
return Result.success("创建成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新牛只数据
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@ApiOperation(value = "更新牛只数据", notes = "根据ID更新牛只数据信息")
|
||||
public Result<CattleData> updateCattleData(
|
||||
@ApiParam(value = "主键ID", required = true)
|
||||
@PathVariable Integer id,
|
||||
@ApiParam(value = "牛只数据信息", required = true)
|
||||
@Valid @RequestBody CattleDataDTO dto) {
|
||||
CattleData data = cattleDataService.updateCattleData(id, dto);
|
||||
return Result.success("更新成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除牛只数据
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@ApiOperation(value = "删除牛只数据", notes = "根据ID删除牛只数据")
|
||||
public Result<Void> deleteCattleData(
|
||||
@ApiParam(value = "主键ID", required = true)
|
||||
@PathVariable Integer id) {
|
||||
cattleDataService.deleteCattleData(id);
|
||||
return Result.success("删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Excel文件
|
||||
*/
|
||||
private List<CattleDataDTO> parseExcelFile(MultipartFile file) throws IOException {
|
||||
List<CattleDataDTO> dataList = new ArrayList<>();
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
|
||||
// 从第二行开始读取(第一行是表头)
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CattleDataDTO dto = new CattleDataDTO();
|
||||
|
||||
// Excel模板列顺序(根据图片):
|
||||
// 列1(索引0):日期
|
||||
// 列2(索引1):品种
|
||||
// 列3(索引2):省份
|
||||
// 列4(索引3):地市
|
||||
// 列5(索引4):价格(元/斤)
|
||||
|
||||
// 读取日期(第1列,索引0)
|
||||
Cell timeCell = row.getCell(0);
|
||||
if (timeCell != null) {
|
||||
LocalDateTime createTime = parseDateTime(timeCell);
|
||||
dto.setCreateTime(createTime);
|
||||
}
|
||||
|
||||
// 读取品种(第2列,索引1)
|
||||
Cell typeCell = row.getCell(1);
|
||||
if (typeCell != null) {
|
||||
String breedName = getCellValueAsString(typeCell).trim();
|
||||
dto.setType(breedName);
|
||||
}
|
||||
|
||||
// 读取省份(第3列,索引2),完全按照表格内容,不做截取
|
||||
Cell provinceCell = row.getCell(2);
|
||||
if (provinceCell != null) {
|
||||
String province = getCellValueAsString(provinceCell).trim();
|
||||
dto.setProvince(province);
|
||||
}
|
||||
|
||||
// 读取地市(第4列,索引3),截取到"市"
|
||||
Cell locationCell = row.getCell(3);
|
||||
if (locationCell != null) {
|
||||
String location = getCellValueAsString(locationCell).trim();
|
||||
// 截取到"市"、"盟"、"地区"、"州"等
|
||||
String cityLocation = extractCity(location);
|
||||
dto.setLocation(cityLocation);
|
||||
}
|
||||
|
||||
// 读取价格(第5列,索引4)
|
||||
Cell priceCell = row.getCell(4);
|
||||
if (priceCell != null) {
|
||||
BigDecimal price = parsePrice(priceCell, dto.getLocation() != null ? dto.getLocation() : "未知");
|
||||
dto.setPrice(price);
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (dto.getType() != null && !dto.getType().trim().isEmpty() &&
|
||||
dto.getProvince() != null && !dto.getProvince().trim().isEmpty() &&
|
||||
dto.getLocation() != null && !dto.getLocation().trim().isEmpty() &&
|
||||
dto.getPrice() != null) {
|
||||
dataList.add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格值(字符串)
|
||||
*/
|
||||
private String getCellValueAsString(Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue().trim();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
// 避免科学计数法
|
||||
double numericValue = cell.getNumericCellValue();
|
||||
if (numericValue == (long) numericValue) {
|
||||
return String.valueOf((long) numericValue);
|
||||
} else {
|
||||
return String.valueOf(numericValue);
|
||||
}
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
case FORMULA:
|
||||
return cell.getCellFormula();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析日期时间
|
||||
*/
|
||||
private LocalDateTime parseDateTime(Cell cell) {
|
||||
try {
|
||||
if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
|
||||
java.util.Date date = cell.getDateCellValue();
|
||||
return new java.sql.Timestamp(date.getTime()).toLocalDateTime();
|
||||
} else if (cell.getCellType() == CellType.STRING) {
|
||||
String dateStr = cell.getStringCellValue().trim();
|
||||
// 尝试解析 "2025/11/23" 格式
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/d");
|
||||
return java.time.LocalDate.parse(dateStr, formatter).atStartOfDay();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败,返回当前时间
|
||||
}
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析价格
|
||||
* 支持格式:
|
||||
* - 数字:13.5
|
||||
* - 价格范围:13.41-14.61(取平均值)
|
||||
* - 字符串数字:"13.5"
|
||||
* @param cell 价格单元格
|
||||
* @param location 位置信息(用于错误日志)
|
||||
*/
|
||||
private BigDecimal parsePrice(Cell cell, String location) {
|
||||
try {
|
||||
if (cell == null) {
|
||||
System.out.println("解析价格失败: " + location + ", 错误: 单元格为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cell.getCellType() == CellType.NUMERIC) {
|
||||
return BigDecimal.valueOf(cell.getNumericCellValue());
|
||||
} else if (cell.getCellType() == CellType.STRING) {
|
||||
String priceStr = cell.getStringCellValue().trim();
|
||||
if (priceStr == null || priceStr.isEmpty()) {
|
||||
System.out.println("解析价格失败: " + location + ", 错误: 价格字符串为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 处理价格范围格式,如 "13.41-14.61"
|
||||
if (priceStr.contains("-")) {
|
||||
String[] parts = priceStr.split("-");
|
||||
if (parts.length == 2) {
|
||||
try {
|
||||
BigDecimal min = new BigDecimal(parts[0].trim());
|
||||
BigDecimal max = new BigDecimal(parts[1].trim());
|
||||
// 计算平均值
|
||||
BigDecimal average = min.add(max).divide(BigDecimal.valueOf(2), 2, java.math.RoundingMode.HALF_UP);
|
||||
return average;
|
||||
} catch (Exception e) {
|
||||
// 如果解析范围失败,尝试解析第一个值
|
||||
try {
|
||||
return new BigDecimal(parts[0].trim());
|
||||
} catch (Exception e2) {
|
||||
System.out.println("解析价格失败: " + location + ", 价格字符串: " + priceStr + ", 错误: " + e2.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通数字字符串
|
||||
return new BigDecimal(priceStr);
|
||||
} else {
|
||||
System.out.println("解析价格失败: " + location + ", 错误: 单元格类型不支持, 类型: " + cell.getCellType());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败,返回null
|
||||
String cellValue = cell != null ? getCellValueAsString(cell) : "null";
|
||||
System.out.println("解析价格失败: " + location + ", 单元格值: " + cellValue + ", 错误: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取所在产地到"市"级别
|
||||
*/
|
||||
private String extractCity(String location) {
|
||||
if (location == null || location.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 查找"市"的位置
|
||||
int cityIndex = location.indexOf("市");
|
||||
if (cityIndex > 0) {
|
||||
// 包含"市"字符
|
||||
return location.substring(0, cityIndex + 1);
|
||||
}
|
||||
|
||||
// 如果没有找到"市",返回原字符串(可能是直辖市或其他格式)
|
||||
return location.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从location中提取省份
|
||||
* 例如:"安徽淮南市" -> "安徽"
|
||||
*/
|
||||
private String extractProvince(String location) {
|
||||
if (location == null || location.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String trimmed = location.trim();
|
||||
|
||||
// 中国省份列表(包括自治区、直辖市、特别行政区)
|
||||
String[] provinces = {
|
||||
"北京", "天津", "上海", "重庆",
|
||||
"河北", "山西", "辽宁", "吉林", "黑龙江",
|
||||
"江苏", "浙江", "安徽", "福建", "江西", "山东",
|
||||
"河南", "湖北", "湖南", "广东", "海南",
|
||||
"四川", "贵州", "云南", "陕西", "甘肃", "青海",
|
||||
"内蒙古", "广西", "西藏", "宁夏", "新疆",
|
||||
"香港", "澳门", "台湾"
|
||||
};
|
||||
|
||||
// 匹配省份(包括自治区、直辖市等)
|
||||
for (String province : provinces) {
|
||||
if (trimmed.startsWith(province)) {
|
||||
// 处理特殊情况:内蒙古、黑龙江等
|
||||
if (province.equals("内蒙古") && trimmed.startsWith("内蒙古自治区")) {
|
||||
return "内蒙古";
|
||||
}
|
||||
return province;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配到,尝试提取前2-3个字符作为省份
|
||||
// 大多数省份名称是2-3个字符
|
||||
if (trimmed.length() >= 2) {
|
||||
// 尝试2个字符
|
||||
String twoChar = trimmed.substring(0, 2);
|
||||
for (String province : provinces) {
|
||||
if (province.equals(twoChar)) {
|
||||
return province;
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试3个字符(如内蒙古)
|
||||
if (trimmed.length() >= 3) {
|
||||
String threeChar = trimmed.substring(0, 3);
|
||||
for (String province : provinces) {
|
||||
if (province.equals(threeChar)) {
|
||||
return province;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都匹配不到,返回空字符串
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析省份数据Excel文件
|
||||
* Excel格式(列顺序):
|
||||
* A列:省份
|
||||
* B列:2023存栏(万头) -> inventory_23th
|
||||
* C列:2023出栏(万头) -> slaughter_23th
|
||||
* D列:2024存栏(万头) -> inventory_24th
|
||||
* E列:2024出栏(万头) -> slaughter_24th
|
||||
* F列:2025存栏(万头) -> inventory_25th
|
||||
* G列:2025出栏(万头) -> slaughter_25th
|
||||
* H列:省份均价(元/斤) -> province_price
|
||||
*/
|
||||
private List<com.example.cattletends.dto.ProvinceDataDTO> parseProvinceExcelFile(MultipartFile file) throws IOException {
|
||||
List<com.example.cattletends.dto.ProvinceDataDTO> dataList = new ArrayList<>();
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
|
||||
// 从第二行开始读取(第一行是表头)
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
com.example.cattletends.dto.ProvinceDataDTO dto = new com.example.cattletends.dto.ProvinceDataDTO();
|
||||
|
||||
// Excel模板列顺序(根据实际模板):
|
||||
// A列(索引0):省份
|
||||
// B列(索引1):2023存栏(万头) -> inventory_23th
|
||||
// C列(索引2):2023出栏(万头) -> slaughter_23th
|
||||
// D列(索引3):2024存栏(万头) -> inventory_24th
|
||||
// E列(索引4):2024出栏(万头) -> slaughter_24th
|
||||
// F列(索引5):2025存栏(万头) -> inventory_25th
|
||||
// G列(索引6):2025出栏(万头) -> slaughter_25th
|
||||
// H列(索引7):省份均价(元/斤) -> province_price
|
||||
|
||||
// 读取省份(A列,索引0)
|
||||
Cell provinceCell = row.getCell(0);
|
||||
if (provinceCell != null) {
|
||||
String province = getCellValueAsString(provinceCell);
|
||||
// 处理可能包含"省"、"自治区"等后缀的情况
|
||||
province = province.replace("省", "").replace("自治区", "").replace("市", "").trim();
|
||||
dto.setProvince(province);
|
||||
}
|
||||
|
||||
// 读取2023存栏(万头)(B列,索引1)-> inventory_23th
|
||||
Cell inv23Cell = row.getCell(1);
|
||||
if (inv23Cell != null) {
|
||||
Integer value = parseInteger(inv23Cell);
|
||||
dto.setInventory23th(value);
|
||||
}
|
||||
|
||||
// 读取2023出栏(万头)(C列,索引2)-> slaughter_23th
|
||||
Cell sla23Cell = row.getCell(2);
|
||||
if (sla23Cell != null) {
|
||||
Integer value = parseInteger(sla23Cell);
|
||||
dto.setSlaughter23th(value);
|
||||
}
|
||||
|
||||
// 读取2024存栏(万头)(D列,索引3)-> inventory_24th
|
||||
Cell inv24Cell = row.getCell(3);
|
||||
if (inv24Cell != null) {
|
||||
Integer value = parseInteger(inv24Cell);
|
||||
dto.setInventory24th(value);
|
||||
}
|
||||
|
||||
// 读取2024出栏(万头)(E列,索引4)-> slaughter_24th
|
||||
Cell sla24Cell = row.getCell(4);
|
||||
if (sla24Cell != null) {
|
||||
Integer value = parseInteger(sla24Cell);
|
||||
dto.setSlaughter24th(value);
|
||||
}
|
||||
|
||||
// 读取2025存栏(万头)(F列,索引5)-> inventory_25th
|
||||
Cell inv25Cell = row.getCell(5);
|
||||
if (inv25Cell != null) {
|
||||
Integer value = parseInteger(inv25Cell);
|
||||
dto.setInventory25th(value);
|
||||
}
|
||||
|
||||
// 读取2025出栏(万头)(G列,索引6)-> slaughter_25th
|
||||
Cell sla25Cell = row.getCell(6);
|
||||
if (sla25Cell != null) {
|
||||
Integer value = parseInteger(sla25Cell);
|
||||
dto.setSlaughter25th(value);
|
||||
}
|
||||
|
||||
// 读取省份均价(元/斤)(H列,索引7)-> province_price
|
||||
Cell priceCell = row.getCell(7);
|
||||
if (priceCell != null) {
|
||||
String priceStr = getCellValueAsString(priceCell);
|
||||
System.out.println("省份: " + dto.getProvince() + ", 原始价格字符串: [" + priceStr + "]");
|
||||
BigDecimal price = parsePrice(priceCell, dto.getProvince() != null ? dto.getProvince() : "未知省份");
|
||||
dto.setProvincePrice(price);
|
||||
System.out.println("省份: " + dto.getProvince() + ", 解析后价格: " + price);
|
||||
} else {
|
||||
System.out.println("省份: " + dto.getProvince() + ", 价格单元格为空或不存在");
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (dto.getProvince() != null && !dto.getProvince().trim().isEmpty()) {
|
||||
dataList.add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析整数(处理可能包含±的情况,如"400±20"取400)
|
||||
*/
|
||||
private Integer parseInteger(Cell cell) {
|
||||
try {
|
||||
if (cell.getCellType() == CellType.NUMERIC) {
|
||||
return (int) cell.getNumericCellValue();
|
||||
} else if (cell.getCellType() == CellType.STRING) {
|
||||
String value = cell.getStringCellValue().trim();
|
||||
// 处理"400±20"格式,取前面的数字
|
||||
if (value.contains("±")) {
|
||||
value = value.substring(0, value.indexOf("±")).trim();
|
||||
}
|
||||
// 处理"万头"等后缀
|
||||
value = value.replace("万头", "").trim();
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.cattletends.controller;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Favicon 控制器
|
||||
* 处理浏览器自动请求的 favicon.ico,避免 404 错误
|
||||
*/
|
||||
@RestController
|
||||
public class FaviconController {
|
||||
|
||||
/**
|
||||
* 处理 favicon.ico 请求
|
||||
* 返回 204 No Content,表示请求成功但无内容返回
|
||||
*/
|
||||
@GetMapping("favicon.ico")
|
||||
public ResponseEntity<Void> favicon() {
|
||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.example.cattletends.dto;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 牛只数据传输对象
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleDataDTO {
|
||||
|
||||
/**
|
||||
* 主键ID(更新时使用)
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 品种
|
||||
*/
|
||||
@NotBlank(message = "品种不能为空")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
@NotBlank(message = "省份不能为空")
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 所在产地
|
||||
*/
|
||||
@NotBlank(message = "所在产地不能为空")
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 价格(元/斤)
|
||||
*/
|
||||
@NotNull(message = "价格不能为空")
|
||||
@DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0")
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 创建时间(只读)
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间(只读)
|
||||
*/
|
||||
private LocalDateTime upTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.example.cattletends.dto;
|
||||
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 导入结果封装类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ImportResult {
|
||||
|
||||
/**
|
||||
* 导入的数据列表
|
||||
*/
|
||||
private List<CattleData> dataList;
|
||||
|
||||
/**
|
||||
* 新增数量
|
||||
*/
|
||||
private int newCount;
|
||||
|
||||
/**
|
||||
* 更新数量
|
||||
*/
|
||||
private int updateCount;
|
||||
|
||||
/**
|
||||
* 总数量
|
||||
*/
|
||||
public int getTotalCount() {
|
||||
return newCount + updateCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.example.cattletends.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 省份数据传输对象(用于导入省份存栏出栏数据)
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ProvinceDataDTO {
|
||||
|
||||
/**
|
||||
* 省份名称
|
||||
*/
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 省份均价
|
||||
*/
|
||||
private BigDecimal provincePrice;
|
||||
|
||||
/**
|
||||
* 23年存栏(万头)
|
||||
*/
|
||||
private Integer inventory23th;
|
||||
|
||||
/**
|
||||
* 23年出栏(万头)
|
||||
*/
|
||||
private Integer slaughter23th;
|
||||
|
||||
/**
|
||||
* 24年存栏(万头)
|
||||
*/
|
||||
private Integer inventory24th;
|
||||
|
||||
/**
|
||||
* 24年出栏(万头)
|
||||
*/
|
||||
private Integer slaughter24th;
|
||||
|
||||
/**
|
||||
* 25年存栏(万头)
|
||||
*/
|
||||
private Integer inventory25th;
|
||||
|
||||
/**
|
||||
* 25年出栏(万头)
|
||||
*/
|
||||
private Integer slaughter25th;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 品种表实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattlebreed", indexes = {
|
||||
@Index(name = "idx_breed_name", columnList = "breed_name", unique = true),
|
||||
@Index(name = "idx_create_time", columnList = "create_time"),
|
||||
@Index(name = "idx_up_time", columnList = "up_time")
|
||||
})
|
||||
public class CattleBreed {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 品种名称(唯一)
|
||||
*/
|
||||
@Column(name = "breed_name", nullable = false, unique = true, length = 100)
|
||||
private String breedName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createTime = LocalDateTime.now();
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBreedName() {
|
||||
return breedName;
|
||||
}
|
||||
|
||||
public void setBreedName(String breedName) {
|
||||
this.breedName = breedName;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpTime() {
|
||||
return upTime;
|
||||
}
|
||||
|
||||
public void setUpTime(LocalDateTime upTime) {
|
||||
this.upTime = upTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 牛只数据实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattleData", indexes = {
|
||||
@Index(name = "idx_price", columnList = "price"),
|
||||
@Index(name = "idx_type", columnList = "type"),
|
||||
@Index(name = "idx_province", columnList = "province"),
|
||||
@Index(name = "idx_type_price", columnList = "type,price"),
|
||||
@Index(name = "idx_province_price", columnList = "province,price"),
|
||||
@Index(name = "idx_type_province_location_price", columnList = "type,province,location,price")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleData {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false, updatable = false)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 品种
|
||||
*/
|
||||
@Column(name = "type", length = 255)
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
@Column(name = "province", length = 255, nullable = false)
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 所在产地
|
||||
*/
|
||||
@Column(name = "location", length = 255)
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 价格(元/斤)
|
||||
*/
|
||||
@Column(name = "price", precision = 10, scale = 2)
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
/**
|
||||
* 保存前自动设置创建时间和更新时间
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (createTime == null) {
|
||||
createTime = now;
|
||||
}
|
||||
if (upTime == null) {
|
||||
upTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前自动设置更新时间
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 全国总量数据实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattlenational", indexes = {
|
||||
@Index(name = "idx_national_inv_23", columnList = "national_inventory_23th"),
|
||||
@Index(name = "idx_national_sla_23", columnList = "national_slaughter_23th"),
|
||||
@Index(name = "idx_national_inv_24", columnList = "national_inventory_24th"),
|
||||
@Index(name = "idx_national_sla_24", columnList = "national_slaughter_24th"),
|
||||
@Index(name = "idx_national_inv_25", columnList = "national_inventory_25th"),
|
||||
@Index(name = "idx_national_sla_25", columnList = "national_slaughter_25th")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleNational {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false, updatable = false)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 23年全国存栏量(15个省份存栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_inventory_23th")
|
||||
private Integer nationalInventory23th;
|
||||
|
||||
/**
|
||||
* 23年全国出栏量(15个省份出栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_slaughter_23th")
|
||||
private Integer nationalSlaughter23th;
|
||||
|
||||
/**
|
||||
* 24年全国存栏量(15个省份存栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_inventory_24th")
|
||||
private Integer nationalInventory24th;
|
||||
|
||||
/**
|
||||
* 24年全国出栏量(15个省份出栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_slaughter_24th")
|
||||
private Integer nationalSlaughter24th;
|
||||
|
||||
/**
|
||||
* 25年全国存栏量(15个省份存栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_inventory_25th")
|
||||
private Integer nationalInventory25th;
|
||||
|
||||
/**
|
||||
* 25年全国出栏量(15个省份出栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_slaughter_25th")
|
||||
private Integer nationalSlaughter25th;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
/**
|
||||
* 保存前自动设置创建时间和更新时间
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (createTime == null) {
|
||||
createTime = now;
|
||||
}
|
||||
if (upTime == null) {
|
||||
upTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前自动设置更新时间
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 省份数据实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattleprovince", indexes = {
|
||||
@Index(name = "idx_province_name", columnList = "province"),
|
||||
@Index(name = "idx_province_price", columnList = "province_price"),
|
||||
@Index(name = "idx_inv_23", columnList = "inventory_23th"),
|
||||
@Index(name = "idx_sla_23", columnList = "slaughter_23th"),
|
||||
@Index(name = "idx_inv_24", columnList = "inventory_24th"),
|
||||
@Index(name = "idx_sla_24", columnList = "slaughter_24th"),
|
||||
@Index(name = "idx_inv_25", columnList = "inventory_25th"),
|
||||
@Index(name = "idx_sla_25", columnList = "slaughter_25th")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleProvince {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false, updatable = false)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 省份名称
|
||||
*/
|
||||
@Column(name = "province", length = 255, nullable = false, unique = true)
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 省份均价
|
||||
*/
|
||||
@Column(name = "province_price", precision = 10, scale = 2)
|
||||
private BigDecimal provincePrice;
|
||||
|
||||
/**
|
||||
* 23年存栏(万头)
|
||||
*/
|
||||
@Column(name = "inventory_23th")
|
||||
private Integer inventory23th;
|
||||
|
||||
/**
|
||||
* 23年出栏(万头)
|
||||
*/
|
||||
@Column(name = "slaughter_23th")
|
||||
private Integer slaughter23th;
|
||||
|
||||
/**
|
||||
* 24年存栏(万头)
|
||||
*/
|
||||
@Column(name = "inventory_24th")
|
||||
private Integer inventory24th;
|
||||
|
||||
/**
|
||||
* 24年出栏(万头)
|
||||
*/
|
||||
@Column(name = "slaughter_24th")
|
||||
private Integer slaughter24th;
|
||||
|
||||
/**
|
||||
* 25年存栏(万头)
|
||||
*/
|
||||
@Column(name = "inventory_25th")
|
||||
private Integer inventory25th;
|
||||
|
||||
/**
|
||||
* 25年出栏(万头)
|
||||
*/
|
||||
@Column(name = "slaughter_25th")
|
||||
private Integer slaughter25th;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
/**
|
||||
* 保存前自动设置创建时间和更新时间
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (createTime == null) {
|
||||
createTime = now;
|
||||
}
|
||||
if (upTime == null) {
|
||||
upTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前自动设置更新时间
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.example.cattletends.exception;
|
||||
|
||||
import com.example.cattletends.common.Result;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理参数校验异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
logger.warn("参数校验失败: {}", errors);
|
||||
return Result.error(400, "参数校验失败", errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleRuntimeException(RuntimeException ex) {
|
||||
logger.error("业务异常: {}", ex.getMessage(), ex);
|
||||
return Result.error(400, ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleIllegalArgumentException(IllegalArgumentException ex) {
|
||||
logger.warn("非法参数: {}", ex.getMessage());
|
||||
return Result.error(400, ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleException(Exception ex) {
|
||||
logger.error("系统异常: {}", ex.getMessage(), ex);
|
||||
return Result.error(500, "系统内部错误,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleBreed;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 品种表Repository
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleBreedRepository extends JpaRepository<CattleBreed, Integer> {
|
||||
|
||||
/**
|
||||
* 根据品种名称查询
|
||||
*/
|
||||
Optional<CattleBreed> findByBreedName(String breedName);
|
||||
|
||||
/**
|
||||
* 检查品种名称是否存在
|
||||
*/
|
||||
boolean existsByBreedName(String breedName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 牛只数据Repository接口
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleDataRepository extends JpaRepository<CattleData, Integer> {
|
||||
|
||||
/**
|
||||
* 根据省份查询牛只数据,按价格升序排序
|
||||
*
|
||||
* @param province 省份
|
||||
* @param sort 排序规则
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> findByProvince(String province, Sort sort);
|
||||
|
||||
/**
|
||||
* 根据品种查询牛只数据,按价格升序排序
|
||||
*
|
||||
* @param type 品种
|
||||
* @param sort 排序规则
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> findByType(String type, Sort sort);
|
||||
|
||||
/**
|
||||
* 根据品种、省份、产地、价格查询牛只数据(用于判断重复)
|
||||
*
|
||||
* @param type 品种
|
||||
* @param province 省份
|
||||
* @param location 产地
|
||||
* @param price 价格
|
||||
* @return 牛只数据(如果存在)
|
||||
*/
|
||||
Optional<CattleData> findByTypeAndProvinceAndLocationAndPrice(
|
||||
String type, String province, String location, BigDecimal price);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleNational;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 全国总量数据Repository接口
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleNationalRepository extends JpaRepository<CattleNational, Integer> {
|
||||
|
||||
/**
|
||||
* 查找第一条记录(全国总量通常只有一条记录)
|
||||
*
|
||||
* @return 全国总量数据
|
||||
*/
|
||||
Optional<CattleNational> findFirstByOrderByIdAsc();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 省份数据Repository接口
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleProvinceRepository extends JpaRepository<CattleProvince, Integer> {
|
||||
|
||||
/**
|
||||
* 根据省份名称查询
|
||||
*
|
||||
* @param province 省份名称
|
||||
* @return 省份数据
|
||||
*/
|
||||
Optional<CattleProvince> findByProvince(String province);
|
||||
|
||||
/**
|
||||
* 根据省份名称查询(支持排序)
|
||||
*
|
||||
* @param province 省份名称
|
||||
* @param sort 排序方式
|
||||
* @return 省份数据列表
|
||||
*/
|
||||
List<CattleProvince> findByProvince(String province, Sort sort);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.entity.CattleBreed;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 品种服务接口
|
||||
*/
|
||||
public interface CattleBreedService {
|
||||
|
||||
/**
|
||||
* 创建或获取品种(如果已存在则返回,不存在则创建)
|
||||
* @param breedName 品种名称
|
||||
* @return 品种实体
|
||||
*/
|
||||
CattleBreed createOrGetBreed(String breedName);
|
||||
|
||||
/**
|
||||
* 获取所有品种
|
||||
* @return 品种列表
|
||||
*/
|
||||
List<CattleBreed> getAllBreeds();
|
||||
|
||||
/**
|
||||
* 根据品种名称获取品种
|
||||
* @param breedName 品种名称
|
||||
* @return 品种实体
|
||||
*/
|
||||
CattleBreed getBreedByName(String breedName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.dto.CattleDataDTO;
|
||||
import com.example.cattletends.dto.ImportResult;
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 牛只数据服务接口
|
||||
*/
|
||||
public interface CattleDataService {
|
||||
|
||||
/**
|
||||
* 获取所有牛只数据
|
||||
*
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getAllCattleData();
|
||||
|
||||
/**
|
||||
* 根据省份获取牛只数据
|
||||
*
|
||||
* @param province 省份
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getCattleDataByProvince(String province);
|
||||
|
||||
/**
|
||||
* 根据品种获取牛只数据
|
||||
*
|
||||
* @param type 品种
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getCattleDataByType(String type);
|
||||
|
||||
/**
|
||||
* 根据ID获取牛只数据
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @return 牛只数据
|
||||
*/
|
||||
CattleData getCattleDataById(Integer id);
|
||||
|
||||
/**
|
||||
* 创建牛只数据
|
||||
*
|
||||
* @param dto 牛只数据传输对象
|
||||
* @return 创建的牛只数据
|
||||
*/
|
||||
CattleData createCattleData(CattleDataDTO dto);
|
||||
|
||||
/**
|
||||
* 更新牛只数据
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @param dto 牛只数据传输对象
|
||||
* @return 更新后的牛只数据
|
||||
*/
|
||||
CattleData updateCattleData(Integer id, CattleDataDTO dto);
|
||||
|
||||
/**
|
||||
* 删除牛只数据
|
||||
*
|
||||
* @param id 主键ID
|
||||
*/
|
||||
void deleteCattleData(Integer id);
|
||||
|
||||
/**
|
||||
* 批量导入牛只数据
|
||||
*
|
||||
* @param dataList 牛只数据列表
|
||||
* @return 导入结果(包含数据列表、新增数量、更新数量)
|
||||
*/
|
||||
ImportResult batchImportCattleData(List<CattleDataDTO> dataList);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.entity.CattleNational;
|
||||
|
||||
/**
|
||||
* 全国总量数据服务接口
|
||||
*/
|
||||
public interface CattleNationalService {
|
||||
|
||||
/**
|
||||
* 获取全国总量数据
|
||||
* 如果不存在则自动从 cattleprovince 表计算并创建
|
||||
*
|
||||
* @return 全国总量数据
|
||||
*/
|
||||
CattleNational getNationalData();
|
||||
|
||||
/**
|
||||
* 根据 cattleprovince 表中的数据自动计算并更新全国总量
|
||||
* 计算所有省份的存栏和出栏总和
|
||||
*
|
||||
* @return 更新后的全国总量数据
|
||||
*/
|
||||
CattleNational calculateAndUpdateNationalData();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.dto.ProvinceDataDTO;
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 省份数据服务接口
|
||||
*/
|
||||
public interface CattleProvinceService {
|
||||
|
||||
/**
|
||||
* 导入省份数据
|
||||
* 更新或创建各省份的存栏出栏量和省份均价,并计算全国总量
|
||||
*
|
||||
* @param provinceDataList 省份数据列表
|
||||
* @return 导入结果(包含更新的数据数量和全国总量)
|
||||
*/
|
||||
Map<String, Object> importProvinceData(List<ProvinceDataDTO> provinceDataList);
|
||||
|
||||
/**
|
||||
* 获取所有省份数据(按省份均价降序排序)
|
||||
*
|
||||
* @param province 可选的省份名称,如果提供则只返回该省份数据
|
||||
* @return 省份数据列表
|
||||
*/
|
||||
List<CattleProvince> getAllProvinceData(String province);
|
||||
|
||||
/**
|
||||
* 根据省份名称获取数据
|
||||
*
|
||||
* @param province 省份名称
|
||||
* @return 省份数据
|
||||
*/
|
||||
CattleProvince getProvinceDataByName(String province);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.entity.CattleBreed;
|
||||
import com.example.cattletends.repository.CattleBreedRepository;
|
||||
import com.example.cattletends.service.CattleBreedService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 品种服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleBreedServiceImpl implements CattleBreedService {
|
||||
|
||||
@Autowired
|
||||
private CattleBreedRepository cattleBreedRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleBreed createOrGetBreed(String breedName) {
|
||||
if (breedName == null || breedName.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmedName = breedName.trim();
|
||||
|
||||
// 先查询是否已存在
|
||||
Optional<CattleBreed> existing = cattleBreedRepository.findByBreedName(trimmedName);
|
||||
if (existing.isPresent()) {
|
||||
System.out.println("品种已存在: " + trimmedName);
|
||||
return existing.get();
|
||||
}
|
||||
|
||||
// 不存在则创建
|
||||
CattleBreed breed = new CattleBreed();
|
||||
breed.setBreedName(trimmedName);
|
||||
CattleBreed saved = cattleBreedRepository.save(breed);
|
||||
System.out.println("创建新品种: " + trimmedName + " (ID: " + saved.getId() + ")");
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CattleBreed> getAllBreeds() {
|
||||
return cattleBreedRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CattleBreed getBreedByName(String breedName) {
|
||||
if (breedName == null || breedName.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return cattleBreedRepository.findByBreedName(breedName.trim())
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.dto.CattleDataDTO;
|
||||
import com.example.cattletends.dto.ImportResult;
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import com.example.cattletends.repository.CattleDataRepository;
|
||||
import com.example.cattletends.service.CattleDataService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 牛只数据服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleDataServiceImpl implements CattleDataService {
|
||||
|
||||
private final CattleDataRepository cattleDataRepository;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleDataServiceImpl(CattleDataRepository cattleDataRepository) {
|
||||
this.cattleDataRepository = cattleDataRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleData> getAllCattleData() {
|
||||
// 按价格升序排序
|
||||
return cattleDataRepository.findAll(Sort.by(Sort.Direction.ASC, "price"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleData> getCattleDataByProvince(String province) {
|
||||
// 按价格升序排序
|
||||
return cattleDataRepository.findByProvince(province, Sort.by(Sort.Direction.ASC, "price"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleData> getCattleDataByType(String type) {
|
||||
// 按价格升序排序
|
||||
return cattleDataRepository.findByType(type, Sort.by(Sort.Direction.ASC, "price"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CattleData getCattleDataById(Integer id) {
|
||||
return cattleDataRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("牛只数据不存在,ID: " + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleData createCattleData(CattleDataDTO dto) {
|
||||
CattleData cattleData = new CattleData();
|
||||
cattleData.setType(dto.getType());
|
||||
cattleData.setProvince(dto.getProvince());
|
||||
cattleData.setLocation(dto.getLocation());
|
||||
cattleData.setPrice(dto.getPrice());
|
||||
return cattleDataRepository.save(cattleData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleData updateCattleData(Integer id, CattleDataDTO dto) {
|
||||
CattleData cattleData = cattleDataRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("牛只数据不存在,ID: " + id));
|
||||
|
||||
cattleData.setType(dto.getType());
|
||||
cattleData.setProvince(dto.getProvince());
|
||||
cattleData.setLocation(dto.getLocation());
|
||||
cattleData.setPrice(dto.getPrice());
|
||||
|
||||
return cattleDataRepository.save(cattleData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteCattleData(Integer id) {
|
||||
if (!cattleDataRepository.existsById(id)) {
|
||||
throw new RuntimeException("牛只数据不存在,ID: " + id);
|
||||
}
|
||||
cattleDataRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ImportResult batchImportCattleData(List<CattleDataDTO> dataList) {
|
||||
List<CattleData> savedList = new ArrayList<>();
|
||||
int newCount = 0;
|
||||
int updateCount = 0;
|
||||
|
||||
for (CattleDataDTO dto : dataList) {
|
||||
// 检查是否存在重复数据(type + province + location + price)
|
||||
CattleData existingData = cattleDataRepository
|
||||
.findByTypeAndProvinceAndLocationAndPrice(
|
||||
dto.getType(),
|
||||
dto.getProvince(),
|
||||
dto.getLocation(),
|
||||
dto.getPrice()
|
||||
)
|
||||
.orElse(null);
|
||||
|
||||
if (existingData != null) {
|
||||
// 如果存在重复数据,只更新 up_time(保留原有的 create_time)
|
||||
existingData.setUpTime(LocalDateTime.now());
|
||||
savedList.add(cattleDataRepository.save(existingData));
|
||||
updateCount++;
|
||||
} else {
|
||||
// 如果不存在,创建新记录
|
||||
CattleData cattleData = new CattleData();
|
||||
cattleData.setType(dto.getType());
|
||||
cattleData.setProvince(dto.getProvince());
|
||||
cattleData.setLocation(dto.getLocation());
|
||||
cattleData.setPrice(dto.getPrice());
|
||||
if (dto.getCreateTime() != null) {
|
||||
cattleData.setCreateTime(dto.getCreateTime());
|
||||
}
|
||||
savedList.add(cattleDataRepository.save(cattleData));
|
||||
newCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportResult(savedList, newCount, updateCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.entity.CattleNational;
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
import com.example.cattletends.repository.CattleNationalRepository;
|
||||
import com.example.cattletends.repository.CattleProvinceRepository;
|
||||
import com.example.cattletends.service.CattleNationalService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 全国总量数据服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleNationalServiceImpl implements CattleNationalService {
|
||||
|
||||
private final CattleNationalRepository cattleNationalRepository;
|
||||
private final CattleProvinceRepository cattleProvinceRepository;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleNationalServiceImpl(CattleNationalRepository cattleNationalRepository,
|
||||
CattleProvinceRepository cattleProvinceRepository) {
|
||||
this.cattleNationalRepository = cattleNationalRepository;
|
||||
this.cattleProvinceRepository = cattleProvinceRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CattleNational getNationalData() {
|
||||
CattleNational nationalData = cattleNationalRepository.findFirstByOrderByIdAsc()
|
||||
.orElse(null);
|
||||
|
||||
// 如果不存在,自动计算并创建
|
||||
if (nationalData == null) {
|
||||
return calculateAndUpdateNationalData();
|
||||
}
|
||||
|
||||
return nationalData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleNational calculateAndUpdateNationalData() {
|
||||
// 从 cattleprovince 表中获取所有省份数据
|
||||
List<CattleProvince> allProvinces = cattleProvinceRepository.findAll();
|
||||
|
||||
// 初始化全国总量
|
||||
int nationalInventory23th = 0;
|
||||
int nationalSlaughter23th = 0;
|
||||
int nationalInventory24th = 0;
|
||||
int nationalSlaughter24th = 0;
|
||||
int nationalInventory25th = 0;
|
||||
int nationalSlaughter25th = 0;
|
||||
|
||||
// 累加所有省份的数据
|
||||
for (CattleProvince province : allProvinces) {
|
||||
if (province.getInventory23th() != null) {
|
||||
nationalInventory23th += province.getInventory23th();
|
||||
}
|
||||
if (province.getSlaughter23th() != null) {
|
||||
nationalSlaughter23th += province.getSlaughter23th();
|
||||
}
|
||||
if (province.getInventory24th() != null) {
|
||||
nationalInventory24th += province.getInventory24th();
|
||||
}
|
||||
if (province.getSlaughter24th() != null) {
|
||||
nationalSlaughter24th += province.getSlaughter24th();
|
||||
}
|
||||
if (province.getInventory25th() != null) {
|
||||
nationalInventory25th += province.getInventory25th();
|
||||
}
|
||||
if (province.getSlaughter25th() != null) {
|
||||
nationalSlaughter25th += province.getSlaughter25th();
|
||||
}
|
||||
}
|
||||
|
||||
// 查找是否已存在全国总量记录
|
||||
CattleNational nationalData = cattleNationalRepository.findFirstByOrderByIdAsc()
|
||||
.orElse(null);
|
||||
|
||||
if (nationalData != null) {
|
||||
// 更新现有记录
|
||||
nationalData.setNationalInventory23th(nationalInventory23th);
|
||||
nationalData.setNationalSlaughter23th(nationalSlaughter23th);
|
||||
nationalData.setNationalInventory24th(nationalInventory24th);
|
||||
nationalData.setNationalSlaughter24th(nationalSlaughter24th);
|
||||
nationalData.setNationalInventory25th(nationalInventory25th);
|
||||
nationalData.setNationalSlaughter25th(nationalSlaughter25th);
|
||||
return cattleNationalRepository.save(nationalData);
|
||||
} else {
|
||||
// 创建新记录
|
||||
nationalData = new CattleNational();
|
||||
nationalData.setNationalInventory23th(nationalInventory23th);
|
||||
nationalData.setNationalSlaughter23th(nationalSlaughter23th);
|
||||
nationalData.setNationalInventory24th(nationalInventory24th);
|
||||
nationalData.setNationalSlaughter24th(nationalSlaughter24th);
|
||||
nationalData.setNationalInventory25th(nationalInventory25th);
|
||||
nationalData.setNationalSlaughter25th(nationalSlaughter25th);
|
||||
return cattleNationalRepository.save(nationalData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.dto.ProvinceDataDTO;
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
import com.example.cattletends.repository.CattleProvinceRepository;
|
||||
import com.example.cattletends.service.CattleProvinceService;
|
||||
import com.example.cattletends.service.CattleNationalService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 省份数据服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleProvinceServiceImpl implements CattleProvinceService {
|
||||
|
||||
private final CattleProvinceRepository cattleProvinceRepository;
|
||||
private final CattleNationalService cattleNationalService;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleProvinceServiceImpl(CattleProvinceRepository cattleProvinceRepository,
|
||||
CattleNationalService cattleNationalService) {
|
||||
this.cattleProvinceRepository = cattleProvinceRepository;
|
||||
this.cattleNationalService = cattleNationalService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Map<String, Object> importProvinceData(List<ProvinceDataDTO> provinceDataList) {
|
||||
if (provinceDataList == null || provinceDataList.isEmpty()) {
|
||||
throw new RuntimeException("省份数据列表不能为空");
|
||||
}
|
||||
|
||||
int updateCount = 0;
|
||||
int createCount = 0;
|
||||
|
||||
// 更新或创建各省份数据
|
||||
for (ProvinceDataDTO dto : provinceDataList) {
|
||||
if (dto == null || dto.getProvince() == null || dto.getProvince().trim().isEmpty()) {
|
||||
continue; // 跳过无效数据
|
||||
}
|
||||
// 查找该省份是否已存在
|
||||
CattleProvince provinceData = cattleProvinceRepository.findByProvince(dto.getProvince())
|
||||
.orElse(null);
|
||||
|
||||
if (provinceData != null) {
|
||||
// 更新现有数据
|
||||
provinceData.setProvincePrice(dto.getProvincePrice());
|
||||
provinceData.setInventory23th(dto.getInventory23th());
|
||||
provinceData.setSlaughter23th(dto.getSlaughter23th());
|
||||
provinceData.setInventory24th(dto.getInventory24th());
|
||||
provinceData.setSlaughter24th(dto.getSlaughter24th());
|
||||
provinceData.setInventory25th(dto.getInventory25th());
|
||||
provinceData.setSlaughter25th(dto.getSlaughter25th());
|
||||
cattleProvinceRepository.save(provinceData);
|
||||
updateCount++;
|
||||
} else {
|
||||
// 创建新数据
|
||||
provinceData = new CattleProvince();
|
||||
provinceData.setProvince(dto.getProvince());
|
||||
provinceData.setProvincePrice(dto.getProvincePrice());
|
||||
provinceData.setInventory23th(dto.getInventory23th());
|
||||
provinceData.setSlaughter23th(dto.getSlaughter23th());
|
||||
provinceData.setInventory24th(dto.getInventory24th());
|
||||
provinceData.setSlaughter24th(dto.getSlaughter24th());
|
||||
provinceData.setInventory25th(dto.getInventory25th());
|
||||
provinceData.setSlaughter25th(dto.getSlaughter25th());
|
||||
cattleProvinceRepository.save(provinceData);
|
||||
createCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动从 cattleprovince 表计算并更新全国总量数据(从数据库表中读取所有省份数据并求和)
|
||||
// 只更新 cattlenational 表,不更新 cattleData 表
|
||||
com.example.cattletends.entity.CattleNational nationalData =
|
||||
cattleNationalService.calculateAndUpdateNationalData();
|
||||
|
||||
// 返回结果
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("createCount", createCount);
|
||||
result.put("updateCount", updateCount);
|
||||
result.put("totalCount", createCount + updateCount);
|
||||
|
||||
// 从全国总量数据中获取计算结果
|
||||
if (nationalData != null) {
|
||||
result.put("nationalInventory23th", nationalData.getNationalInventory23th());
|
||||
result.put("nationalSlaughter23th", nationalData.getNationalSlaughter23th());
|
||||
result.put("nationalInventory24th", nationalData.getNationalInventory24th());
|
||||
result.put("nationalSlaughter24th", nationalData.getNationalSlaughter24th());
|
||||
result.put("nationalInventory25th", nationalData.getNationalInventory25th());
|
||||
result.put("nationalSlaughter25th", nationalData.getNationalSlaughter25th());
|
||||
} else {
|
||||
result.put("nationalInventory23th", 0);
|
||||
result.put("nationalSlaughter23th", 0);
|
||||
result.put("nationalInventory24th", 0);
|
||||
result.put("nationalSlaughter24th", 0);
|
||||
result.put("nationalInventory25th", 0);
|
||||
result.put("nationalSlaughter25th", 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleProvince> getAllProvinceData(String province) {
|
||||
// 按省份均价降序排序
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "provincePrice");
|
||||
|
||||
// 如果提供了省份参数,则只查询该省份数据
|
||||
if (province != null && !province.trim().isEmpty()) {
|
||||
return cattleProvinceRepository.findByProvince(province.trim(), sort);
|
||||
}
|
||||
|
||||
// 否则返回所有省份数据,按省份均价降序排序
|
||||
return cattleProvinceRepository.findAll(sort);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CattleProvince getProvinceDataByName(String province) {
|
||||
return cattleProvinceRepository.findByProvince(province)
|
||||
.orElseThrow(() -> new RuntimeException("省份数据不存在:" + province));
|
||||
}
|
||||
}
|
||||
56
backend/src/main/resources/application.yml
Normal file
56
backend/src/main/resources/application.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
spring:
|
||||
application:
|
||||
name: cattleTends
|
||||
|
||||
# 数据库配置
|
||||
datasource:
|
||||
url: jdbc:mysql://129.211.213.226:3306/cattleTends?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: Aiotagro@741
|
||||
# 如果密码包含特殊字符导致连接失败,可以尝试在URL中编码密码
|
||||
# 或者检查数据库服务器是否允许当前IP访问
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
connection-timeout: 30000
|
||||
idle-timeout: 600000
|
||||
max-lifetime: 1800000
|
||||
|
||||
# JPA配置
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
naming:
|
||||
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
|
||||
show-sql: true
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.MySQLDialect
|
||||
format_sql: true
|
||||
open-in-view: false
|
||||
|
||||
# 服务器配置
|
||||
server:
|
||||
port: 12240
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
com.example.cattletends: DEBUG
|
||||
com.example.cattletends.controller: DEBUG
|
||||
com.example.cattletends.service: DEBUG
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.web.servlet.DispatcherServlet: DEBUG
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
# Swagger配置(Springfox 2.9.2)
|
||||
# 无需额外配置,使用默认路径 /swagger-ui.html
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# 创建品种表 (cattlebreed) 执行说明
|
||||
|
||||
## 问题
|
||||
错误信息:`Table 'cattletends.cattlebreed' doesn't exist`
|
||||
|
||||
## 解决方案
|
||||
需要执行 SQL 脚本创建 `cattlebreed` 表。
|
||||
|
||||
## 执行方法
|
||||
|
||||
### 方法1:使用 MySQL 命令行(推荐)
|
||||
|
||||
```bash
|
||||
# 连接到数据库
|
||||
mysql -h 129.211.213.226 -P 3306 -u root -p cattleTends
|
||||
|
||||
# 输入密码:Aiotagro@741
|
||||
|
||||
# 执行 SQL 脚本
|
||||
source /path/to/create_cattlebreed_table.sql;
|
||||
|
||||
# 或者直接复制 SQL 内容执行
|
||||
```
|
||||
|
||||
### 方法2:直接执行 SQL 语句
|
||||
|
||||
连接到数据库后,执行以下 SQL:
|
||||
|
||||
```sql
|
||||
USE cattleTends;
|
||||
|
||||
-- 创建品种表
|
||||
CREATE TABLE IF NOT EXISTS `cattlebreed` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
`breed_name` VARCHAR(100) NOT NULL UNIQUE COMMENT '品种名称',
|
||||
`create_time` DATETIME COMMENT '创建时间',
|
||||
`up_time` DATETIME NOT NULL COMMENT '更新时间',
|
||||
-- 索引定义
|
||||
INDEX `idx_breed_name` (`breed_name`),
|
||||
INDEX `idx_create_time` (`create_time`),
|
||||
INDEX `idx_up_time` (`up_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='品种表';
|
||||
```
|
||||
|
||||
### 方法3:使用 MySQL Workbench 或其他数据库工具
|
||||
|
||||
1. 连接到数据库服务器:`129.211.213.226:3306`
|
||||
2. 选择数据库:`cattleTends`
|
||||
3. 打开并执行 `create_cattlebreed_table.sql` 文件
|
||||
|
||||
## 验证表是否创建成功
|
||||
|
||||
执行以下 SQL 验证:
|
||||
|
||||
```sql
|
||||
USE cattleTends;
|
||||
|
||||
-- 查看表是否存在
|
||||
SHOW TABLES LIKE 'cattlebreed';
|
||||
|
||||
-- 查看表结构
|
||||
DESC cattlebreed;
|
||||
|
||||
-- 查看索引
|
||||
SHOW INDEX FROM cattlebreed;
|
||||
```
|
||||
|
||||
## 预期结果
|
||||
|
||||
执行成功后,应该看到:
|
||||
- 表 `cattlebreed` 已创建
|
||||
- 包含 4 个索引:
|
||||
- PRIMARY KEY (id)
|
||||
- idx_breed_name (breed_name, UNIQUE)
|
||||
- idx_create_time (create_time)
|
||||
- idx_up_time (up_time)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 如果表已存在,`CREATE TABLE IF NOT EXISTS` 不会报错,会跳过创建
|
||||
- 如果表已存在但结构不同,需要先删除表再创建:
|
||||
```sql
|
||||
DROP TABLE IF EXISTS cattlebreed;
|
||||
-- 然后执行创建表的 SQL
|
||||
```
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
-- 为 cattleData 表添加所有缺失的字段
|
||||
-- 请在数据库中执行此SQL语句
|
||||
-- 注意:MySQL 5.7 不支持 IF NOT EXISTS,如果字段已存在会报错,可以忽略
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 添加省份均价字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN province_price DECIMAL(10, 2) COMMENT '省份均价';
|
||||
|
||||
-- 添加23年存栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN inventory_23th INT COMMENT '23年存栏(万头)';
|
||||
|
||||
-- 添加23年出栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN slaughter_23th INT COMMENT '23年出栏(万头)';
|
||||
|
||||
-- 添加24年存栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN inventory_24th INT COMMENT '24年存栏(万头)';
|
||||
|
||||
-- 添加24年出栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN slaughter_24th INT COMMENT '24年出栏(万头)';
|
||||
|
||||
-- 添加25年存栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN inventory_25th INT COMMENT '25年存栏(万头)';
|
||||
|
||||
-- 添加25年出栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN slaughter_25th INT COMMENT '25年出栏(万头)';
|
||||
|
||||
-- 添加23年全国存栏量字段(如果 add_national_fields.sql 已执行,可以跳过)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_23th INT COMMENT '23年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加23年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_23th INT COMMENT '23年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
-- 添加24年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_24th INT COMMENT '24年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加24年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_24th INT COMMENT '24年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
-- 添加25年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_25th INT COMMENT '25年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加25年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_25th INT COMMENT '25年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
-- 为 cattleData 表添加所有缺失的字段
|
||||
-- 请在数据库中执行此SQL语句
|
||||
-- 注意:如果字段已存在会报错,可以忽略或先检查字段是否存在
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 检查并添加字段(MySQL 5.7+ 支持 IF NOT EXISTS,但 ALTER TABLE 不支持,所以需要逐个添加)
|
||||
-- 如果字段已存在,会报错,可以忽略
|
||||
|
||||
-- 添加省份均价字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN province_price DECIMAL(10, 2) COMMENT '省份均价';
|
||||
|
||||
-- 添加23年存栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN inventory_23th INT COMMENT '23年存栏(万头)';
|
||||
|
||||
-- 添加23年出栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN slaughter_23th INT COMMENT '23年出栏(万头)';
|
||||
|
||||
-- 添加24年存栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN inventory_24th INT COMMENT '24年存栏(万头)';
|
||||
|
||||
-- 添加24年出栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN slaughter_24th INT COMMENT '24年出栏(万头)';
|
||||
|
||||
-- 添加25年存栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN inventory_25th INT COMMENT '25年存栏(万头)';
|
||||
|
||||
-- 添加25年出栏字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN slaughter_25th INT COMMENT '25年出栏(万头)';
|
||||
|
||||
-- 添加23年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_23th INT COMMENT '23年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加23年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_23th INT COMMENT '23年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
-- 添加24年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_24th INT COMMENT '24年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加24年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_24th INT COMMENT '24年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
-- 添加25年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_25th INT COMMENT '25年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加25年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_25th INT COMMENT '25年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
-- 为 cattleData 表添加索引以优化查询速度
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 1. 为 price 字段添加索引(用于排序查询)
|
||||
CREATE INDEX idx_price ON cattleData(price);
|
||||
|
||||
-- 2. 为 type 字段添加索引(用于按品种筛选)
|
||||
CREATE INDEX idx_type ON cattleData(type);
|
||||
|
||||
-- 3. 为 province 字段添加索引(用于按省份筛选)
|
||||
CREATE INDEX idx_province ON cattleData(province);
|
||||
|
||||
-- 4. 为 type + price 创建复合索引(用于按品种筛选并按价格排序)
|
||||
CREATE INDEX idx_type_price ON cattleData(type, price);
|
||||
|
||||
-- 5. 为 province + price 创建复合索引(用于按省份筛选并按价格排序)
|
||||
CREATE INDEX idx_province_price ON cattleData(province, price);
|
||||
|
||||
-- 6. 为 type + province + location + price 创建复合索引(用于判断重复数据)
|
||||
CREATE INDEX idx_type_province_location_price ON cattleData(type, province, location, price);
|
||||
|
||||
-- 7. 为 province_price 字段添加索引(用于按省份均价查询或排序)
|
||||
CREATE INDEX idx_province_price_field ON cattleData(province_price);
|
||||
|
||||
-- 8. 为 inventory_23th 字段添加索引(用于按23年存栏查询或排序)
|
||||
CREATE INDEX idx_inventory_23th ON cattleData(inventory_23th);
|
||||
|
||||
-- 9. 为 slaughter_23th 字段添加索引(用于按23年出栏查询或排序)
|
||||
CREATE INDEX idx_slaughter_23th ON cattleData(slaughter_23th);
|
||||
|
||||
-- 10. 为 inventory_24th 字段添加索引(用于按24年存栏查询或排序)
|
||||
CREATE INDEX idx_inventory_24th ON cattleData(inventory_24th);
|
||||
|
||||
-- 11. 为 slaughter_24th 字段添加索引(用于按24年出栏查询或排序)
|
||||
CREATE INDEX idx_slaughter_24th ON cattleData(slaughter_24th);
|
||||
|
||||
-- 12. 为 inventory_25th 字段添加索引(用于按25年存栏查询或排序)
|
||||
CREATE INDEX idx_inventory_25th ON cattleData(inventory_25th);
|
||||
|
||||
-- 13. 为 slaughter_25th 字段添加索引(用于按25年出栏查询或排序)
|
||||
CREATE INDEX idx_slaughter_25th ON cattleData(slaughter_25th);
|
||||
|
||||
-- 14. 为 national_inventory_23th 字段添加索引(用于按23年全国存栏查询或排序)
|
||||
CREATE INDEX idx_national_inventory_23th ON cattleData(national_inventory_23th);
|
||||
|
||||
-- 15. 为 national_slaughter_23th 字段添加索引(用于按23年全国出栏查询或排序)
|
||||
CREATE INDEX idx_national_slaughter_23th ON cattleData(national_slaughter_23th);
|
||||
|
||||
-- 16. 为 national_inventory_24th 字段添加索引(用于按24年全国存栏查询或排序)
|
||||
CREATE INDEX idx_national_inventory_24th ON cattleData(national_inventory_24th);
|
||||
|
||||
-- 17. 为 national_slaughter_24th 字段添加索引(用于按24年全国出栏查询或排序)
|
||||
CREATE INDEX idx_national_slaughter_24th ON cattleData(national_slaughter_24th);
|
||||
|
||||
-- 18. 为 national_inventory_25th 字段添加索引(用于按25年全国存栏查询或排序)
|
||||
CREATE INDEX idx_national_inventory_25th ON cattleData(national_inventory_25th);
|
||||
|
||||
-- 19. 为 national_slaughter_25th 字段添加索引(用于按25年全国出栏查询或排序)
|
||||
CREATE INDEX idx_national_slaughter_25th ON cattleData(national_slaughter_25th);
|
||||
@@ -0,0 +1,29 @@
|
||||
-- 为 cattleData 表添加全国总量字段
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 添加23年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_23th INT COMMENT '23年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加23年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_23th INT COMMENT '23年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
-- 添加24年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_24th INT COMMENT '24年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加24年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_24th INT COMMENT '24年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
-- 添加25年全国存栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_inventory_25th INT COMMENT '25年全国存栏量(15个省份存栏量的总和)';
|
||||
|
||||
-- 添加25年全国出栏量字段
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN national_slaughter_25th INT COMMENT '25年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 为 cattleprovince 表添加全国总量字段
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 添加6个全国总量字段
|
||||
ALTER TABLE cattleprovince
|
||||
ADD COLUMN national_inventory_23th INT COMMENT '23年全国存栏量(15个省份存栏量的总和)',
|
||||
ADD COLUMN national_slaughter_23th INT COMMENT '23年全国出栏量(15个省份出栏量的总和)',
|
||||
ADD COLUMN national_inventory_24th INT COMMENT '24年全国存栏量(15个省份存栏量的总和)',
|
||||
ADD COLUMN national_slaughter_24th INT COMMENT '24年全国出栏量(15个省份出栏量的总和)',
|
||||
ADD COLUMN national_inventory_25th INT COMMENT '25年全国存栏量(15个省份存栏量的总和)',
|
||||
ADD COLUMN national_slaughter_25th INT COMMENT '25年全国出栏量(15个省份出栏量的总和)';
|
||||
|
||||
-- 为全国总量字段添加索引
|
||||
CREATE INDEX idx_national_inv_23 ON cattleprovince(national_inventory_23th);
|
||||
CREATE INDEX idx_national_sla_23 ON cattleprovince(national_slaughter_23th);
|
||||
CREATE INDEX idx_national_inv_24 ON cattleprovince(national_inventory_24th);
|
||||
CREATE INDEX idx_national_sla_24 ON cattleprovince(national_slaughter_24th);
|
||||
CREATE INDEX idx_national_inv_25 ON cattleprovince(national_inventory_25th);
|
||||
CREATE INDEX idx_national_sla_25 ON cattleprovince(national_slaughter_25th);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
-- 为 cattleData 表添加省份相关字段
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 添加省份均价字段(如果不存在)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN IF NOT EXISTS province_price DECIMAL(10, 2) COMMENT '省份均价';
|
||||
|
||||
-- 添加23年存栏字段(如果不存在)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN IF NOT EXISTS inventory_23th INT COMMENT '23年存栏(万头)';
|
||||
|
||||
-- 添加23年出栏字段(如果不存在)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN IF NOT EXISTS slaughter_23th INT COMMENT '23年出栏(万头)';
|
||||
|
||||
-- 添加24年存栏字段(如果不存在)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN IF NOT EXISTS inventory_24th INT COMMENT '24年存栏(万头)';
|
||||
|
||||
-- 添加24年出栏字段(如果不存在)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN IF NOT EXISTS slaughter_24th INT COMMENT '24年出栏(万头)';
|
||||
|
||||
-- 添加25年存栏字段(如果不存在)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN IF NOT EXISTS inventory_25th INT COMMENT '25年存栏(万头)';
|
||||
|
||||
-- 添加25年出栏字段(如果不存在)
|
||||
ALTER TABLE cattleData
|
||||
ADD COLUMN IF NOT EXISTS slaughter_25th INT COMMENT '25年出栏(万头)';
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
-- 创建品种表
|
||||
CREATE TABLE IF NOT EXISTS `cattlebreed` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
`breed_name` VARCHAR(100) NOT NULL UNIQUE COMMENT '品种名称',
|
||||
`create_time` DATETIME COMMENT '创建时间',
|
||||
`up_time` DATETIME NOT NULL COMMENT '更新时间',
|
||||
-- 索引定义
|
||||
INDEX `idx_breed_name` (`breed_name`),
|
||||
INDEX `idx_create_time` (`create_time`),
|
||||
INDEX `idx_up_time` (`up_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='品种表';
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
-- 创建 cattlenational 表(全国总量数据表)
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 创建全国总量数据表
|
||||
CREATE TABLE IF NOT EXISTS cattlenational (
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
national_inventory_23th INT COMMENT '23年全国存栏量(15个省份存栏量的总和)',
|
||||
national_slaughter_23th INT COMMENT '23年全国出栏量(15个省份出栏量的总和)',
|
||||
national_inventory_24th INT COMMENT '24年全国存栏量(15个省份存栏量的总和)',
|
||||
national_slaughter_24th INT COMMENT '24年全国出栏量(15个省份出栏量的总和)',
|
||||
national_inventory_25th INT COMMENT '25年全国存栏量(15个省份存栏量的总和)',
|
||||
national_slaughter_25th INT COMMENT '25年全国出栏量(15个省份出栏量的总和)',
|
||||
create_time DATETIME COMMENT '创建时间',
|
||||
up_time DATETIME NOT NULL COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全国总量数据表';
|
||||
|
||||
-- 为 cattlenational 表添加索引
|
||||
CREATE INDEX idx_national_inv_23 ON cattlenational(national_inventory_23th);
|
||||
CREATE INDEX idx_national_sla_23 ON cattlenational(national_slaughter_23th);
|
||||
CREATE INDEX idx_national_inv_24 ON cattlenational(national_inventory_24th);
|
||||
CREATE INDEX idx_national_sla_24 ON cattlenational(national_slaughter_24th);
|
||||
CREATE INDEX idx_national_inv_25 ON cattlenational(national_inventory_25th);
|
||||
CREATE INDEX idx_national_sla_25 ON cattlenational(national_slaughter_25th);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 创建 cattleprovince 表
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 创建省份数据表
|
||||
CREATE TABLE IF NOT EXISTS cattleprovince (
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
province VARCHAR(255) NOT NULL UNIQUE COMMENT '省份名称',
|
||||
province_price DECIMAL(10, 2) COMMENT '省份均价',
|
||||
inventory_23th INT COMMENT '23年存栏(万头)',
|
||||
slaughter_23th INT COMMENT '23年出栏(万头)',
|
||||
inventory_24th INT COMMENT '24年存栏(万头)',
|
||||
slaughter_24th INT COMMENT '24年出栏(万头)',
|
||||
inventory_25th INT COMMENT '25年存栏(万头)',
|
||||
slaughter_25th INT COMMENT '25年出栏(万头)',
|
||||
create_time DATETIME COMMENT '创建时间',
|
||||
up_time DATETIME NOT NULL COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='省份数据表';
|
||||
|
||||
-- 为 cattleprovince 表添加索引
|
||||
CREATE INDEX idx_province_name ON cattleprovince(province);
|
||||
CREATE INDEX idx_province_price ON cattleprovince(province_price);
|
||||
CREATE INDEX idx_inv_23 ON cattleprovince(inventory_23th);
|
||||
CREATE INDEX idx_sla_23 ON cattleprovince(slaughter_23th);
|
||||
CREATE INDEX idx_inv_24 ON cattleprovince(inventory_24th);
|
||||
CREATE INDEX idx_sla_24 ON cattleprovince(slaughter_24th);
|
||||
CREATE INDEX idx_inv_25 ON cattleprovince(inventory_25th);
|
||||
CREATE INDEX idx_sla_25 ON cattleprovince(slaughter_25th);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- 修复 cattleData 表,将 id 字段设置为自增
|
||||
ALTER TABLE cattleData MODIFY COLUMN id INT AUTO_INCREMENT;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 修复 cattlenational 表的 id 字段,设置为 AUTO_INCREMENT
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 修改 id 字段为 AUTO_INCREMENT
|
||||
ALTER TABLE cattlenational
|
||||
MODIFY COLUMN id INT AUTO_INCREMENT;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 修复 cattleprovince 表的 id 字段,设置为 AUTO_INCREMENT
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 修改 id 字段为 AUTO_INCREMENT
|
||||
ALTER TABLE cattleprovince
|
||||
MODIFY COLUMN id INT AUTO_INCREMENT;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 从 cattleprovince 表中移除全国总量字段(如果已添加)
|
||||
-- 请在数据库中执行此SQL语句
|
||||
|
||||
USE cattleTends;
|
||||
|
||||
-- 检查并删除索引(如果存在)
|
||||
DROP INDEX IF EXISTS idx_national_inv_23 ON cattleprovince;
|
||||
DROP INDEX IF EXISTS idx_national_sla_23 ON cattleprovince;
|
||||
DROP INDEX IF EXISTS idx_national_inv_24 ON cattleprovince;
|
||||
DROP INDEX IF EXISTS idx_national_sla_24 ON cattleprovince;
|
||||
DROP INDEX IF EXISTS idx_national_inv_25 ON cattleprovince;
|
||||
DROP INDEX IF EXISTS idx_national_sla_25 ON cattleprovince;
|
||||
|
||||
-- 删除字段(如果存在)
|
||||
ALTER TABLE cattleprovince
|
||||
DROP COLUMN IF EXISTS national_inventory_23th,
|
||||
DROP COLUMN IF EXISTS national_slaughter_23th,
|
||||
DROP COLUMN IF EXISTS national_inventory_24th,
|
||||
DROP COLUMN IF EXISTS national_slaughter_24th,
|
||||
DROP COLUMN IF EXISTS national_inventory_25th,
|
||||
DROP COLUMN IF EXISTS national_slaughter_25th;
|
||||
|
||||
100
backend/start.bat
Normal file
100
backend/start.bat
Normal file
@@ -0,0 +1,100 @@
|
||||
@echo off
|
||||
REM 牛只数据管理后端启动脚本 (Windows版本)
|
||||
REM 使用方法: start.bat [start|stop|restart|status]
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set APP_NAME=cattletends
|
||||
set JAR_NAME=cattletends-1.0.0.jar
|
||||
set APP_PORT=8080
|
||||
set JAVA_OPTS=-Xms512m -Xmx1024m
|
||||
set SPRING_PROFILE=prod
|
||||
set LOG_FILE=log.out
|
||||
set PID_FILE=pid.%APP_NAME%.txt
|
||||
|
||||
REM 获取脚本所在目录
|
||||
set SCRIPT_DIR=%~dp0
|
||||
set JAR_PATH=%SCRIPT_DIR%target\%JAR_NAME%
|
||||
|
||||
REM 检查jar文件是否存在
|
||||
if not exist "%JAR_PATH%" (
|
||||
echo 错误: 找不到jar文件: %JAR_PATH%
|
||||
echo 请先执行 mvn clean package 打包项目
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1"=="start" goto start
|
||||
if "%1"=="stop" goto stop
|
||||
if "%1"=="restart" goto restart
|
||||
if "%1"=="status" goto status
|
||||
goto usage
|
||||
|
||||
:start
|
||||
echo 正在启动应用: %APP_NAME%
|
||||
|
||||
REM 检查是否已经运行
|
||||
for /f "tokens=2" %%a in ('jps -l ^| findstr "%JAR_NAME%"') do (
|
||||
echo 应用已经在运行中,PID: %%a
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 启动应用
|
||||
start /b java %JAVA_OPTS% -Dspring.profiles.active=%SPRING_PROFILE% -Dserver.port=%APP_PORT% -jar "%JAR_PATH%" > "%LOG_FILE%" 2>&1
|
||||
|
||||
timeout /t 3 /nobreak >nul
|
||||
|
||||
REM 查找进程
|
||||
for /f "tokens=2" %%a in ('jps -l ^| findstr "%JAR_NAME%"') do (
|
||||
echo 应用启动成功,PID: %%a
|
||||
echo %%a > %PID_FILE%
|
||||
echo 日志文件: %LOG_FILE%
|
||||
echo 查看日志: type %LOG_FILE%
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo 应用启动失败,请查看日志: %LOG_FILE%
|
||||
exit /b 1
|
||||
|
||||
:stop
|
||||
echo 正在停止应用: %APP_NAME%
|
||||
|
||||
REM 查找进程并停止
|
||||
for /f "tokens=2" %%a in ('jps -l ^| findstr "%JAR_NAME%"') do (
|
||||
echo 找到进程 PID: %%a
|
||||
taskkill /F /PID %%a >nul 2>&1
|
||||
echo 应用已停止
|
||||
if exist %PID_FILE% del %PID_FILE%
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo 应用未运行
|
||||
if exist %PID_FILE% del %PID_FILE%
|
||||
exit /b 0
|
||||
|
||||
:restart
|
||||
echo ========== 重启应用: %APP_NAME% ==========
|
||||
call :stop
|
||||
timeout /t 2 /nobreak >nul
|
||||
call :start
|
||||
exit /b %errorlevel%
|
||||
|
||||
:status
|
||||
for /f "tokens=2" %%a in ('jps -l ^| findstr "%JAR_NAME%"') do (
|
||||
echo 应用运行中,PID: %%a
|
||||
echo 端口: %APP_PORT%
|
||||
echo 日志: %LOG_FILE%
|
||||
exit /b 0
|
||||
)
|
||||
echo 应用未运行
|
||||
exit /b 0
|
||||
|
||||
:usage
|
||||
echo 使用方法: %0 {start^|stop^|restart^|status}
|
||||
echo.
|
||||
echo 参数说明:
|
||||
echo start - 启动应用
|
||||
echo stop - 停止应用
|
||||
echo restart - 重启应用
|
||||
echo status - 查看应用状态
|
||||
exit /b 1
|
||||
|
||||
140
backend/start.sh
Normal file
140
backend/start.sh
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 牛只数据管理后端启动脚本
|
||||
# 使用方法: ./start.sh [start|stop|restart]
|
||||
|
||||
APP_NAME="cattletends"
|
||||
JAR_NAME="cattletends-1.0.0.jar"
|
||||
APP_PORT=12240
|
||||
JAVA_OPTS="-Xms512m -Xmx1024m"
|
||||
SPRING_PROFILE="prod"
|
||||
LOG_FILE="log.out"
|
||||
PID_FILE="pid.${APP_NAME}"
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
|
||||
# 检查jar文件是否存在
|
||||
if [ ! -f "$JAR_PATH" ]; then
|
||||
echo "错误: 找不到jar文件: $JAR_PATH"
|
||||
echo "请先执行 mvn clean package 打包项目"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 停止应用
|
||||
function stopApp() {
|
||||
echo "正在停止应用: $APP_NAME"
|
||||
|
||||
# 查找进程
|
||||
PID=$(ps -ef | grep java | grep "$JAR_NAME" | grep -v grep | awk '{print $2}')
|
||||
|
||||
if [ -n "$PID" ]; then
|
||||
echo "找到进程 PID: $PID"
|
||||
kill -15 $PID
|
||||
|
||||
# 等待进程结束,最多等待10秒
|
||||
for i in {1..10}; do
|
||||
if ! ps -p $PID > /dev/null 2>&1; then
|
||||
echo "应用已停止"
|
||||
rm -f $PID_FILE
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 如果还在运行,强制杀死
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
echo "强制停止进程..."
|
||||
kill -9 $PID
|
||||
rm -f $PID_FILE
|
||||
fi
|
||||
else
|
||||
echo "应用未运行"
|
||||
rm -f $PID_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动应用
|
||||
function startApp() {
|
||||
echo "正在启动应用: $APP_NAME"
|
||||
|
||||
# 检查是否已经运行
|
||||
PID=$(ps -ef | grep java | grep "$JAR_NAME" | grep -v grep | awk '{print $2}')
|
||||
if [ -n "$PID" ]; then
|
||||
echo "应用已经在运行中,PID: $PID"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 启动应用
|
||||
nohup java $JAVA_OPTS \
|
||||
-Dspring.profiles.active=$SPRING_PROFILE \
|
||||
-Dserver.port=$APP_PORT \
|
||||
-jar "$JAR_PATH" > "$LOG_FILE" 2>&1 &
|
||||
|
||||
# 等待启动
|
||||
sleep 3s
|
||||
|
||||
# 检查进程
|
||||
PID=$(ps -ef | grep java | grep "$JAR_NAME" | grep -v grep | awk '{print $2}')
|
||||
|
||||
if [ -n "$PID" ]; then
|
||||
echo "应用启动成功,PID: $PID"
|
||||
echo $PID > $PID_FILE
|
||||
echo "日志文件: $LOG_FILE"
|
||||
echo "查看日志: tail -f $LOG_FILE"
|
||||
return 0
|
||||
else
|
||||
echo "应用启动失败,请查看日志: $LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 重启应用
|
||||
function restartApp() {
|
||||
echo "========== 重启应用: $APP_NAME =========="
|
||||
stopApp
|
||||
sleep 2s
|
||||
startApp
|
||||
}
|
||||
|
||||
# 查看状态
|
||||
function statusApp() {
|
||||
PID=$(ps -ef | grep java | grep "$JAR_NAME" | grep -v grep | awk '{print $2}')
|
||||
if [ -n "$PID" ]; then
|
||||
echo "应用运行中,PID: $PID"
|
||||
echo "端口: $APP_PORT"
|
||||
echo "日志: $LOG_FILE"
|
||||
else
|
||||
echo "应用未运行"
|
||||
fi
|
||||
}
|
||||
|
||||
# 主逻辑
|
||||
case "$1" in
|
||||
start)
|
||||
startApp
|
||||
;;
|
||||
stop)
|
||||
stopApp
|
||||
;;
|
||||
restart)
|
||||
restartApp
|
||||
;;
|
||||
status)
|
||||
statusApp
|
||||
;;
|
||||
*)
|
||||
echo "使用方法: $0 {start|stop|restart|status}"
|
||||
echo ""
|
||||
echo "参数说明:"
|
||||
echo " start - 启动应用"
|
||||
echo " stop - 停止应用"
|
||||
echo " restart - 重启应用"
|
||||
echo " status - 查看应用状态"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $?
|
||||
|
||||
Reference in New Issue
Block a user