鍒濆鎻愪氦锛氱墰鍙暟鎹鐞嗙郴缁?- 鍖呭惈鍚庣Spring Boot鍜屽墠绔疺ue3椤圭洰

This commit is contained in:
shenquanyi
2025-11-28 17:19:49 +08:00
commit 4de35a7e5b
9890 changed files with 1020261 additions and 0 deletions

483
backend/API_TEST.md Normal file
View 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
View 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
```

View 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
View 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"

View 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
View 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
View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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列索引12023存栏(万头) -> inventory_23th
// C列索引22023出栏(万头) -> slaughter_23th
// D列索引32024存栏(万头) -> inventory_24th
// E列索引42024出栏(万头) -> slaughter_24th
// F列索引52025存栏(万头) -> inventory_25th
// G列索引62025出栏(万头) -> 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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, "系统内部错误,请联系管理员");
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View 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

View File

@@ -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
```

View File

@@ -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个省份出栏量的总和';

View File

@@ -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个省份出栏量的总和';

View File

@@ -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);

View File

@@ -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个省份出栏量的总和';

View File

@@ -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);

View File

@@ -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年出栏万头';

View File

@@ -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='品种表';

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,3 @@
-- 修复 cattleData 表,将 id 字段设置为自增
ALTER TABLE cattleData MODIFY COLUMN id INT AUTO_INCREMENT;

View File

@@ -0,0 +1,9 @@
-- 修复 cattlenational 表的 id 字段,设置为 AUTO_INCREMENT
-- 请在数据库中执行此SQL语句
USE cattleTends;
-- 修改 id 字段为 AUTO_INCREMENT
ALTER TABLE cattlenational
MODIFY COLUMN id INT AUTO_INCREMENT;

View File

@@ -0,0 +1,9 @@
-- 修复 cattleprovince 表的 id 字段,设置为 AUTO_INCREMENT
-- 请在数据库中执行此SQL语句
USE cattleTends;
-- 修改 id 字段为 AUTO_INCREMENT
ALTER TABLE cattleprovince
MODIFY COLUMN id INT AUTO_INCREMENT;

View File

@@ -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
View 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
View 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 $?