鍒濆鎻愪氦锛氱墰鍙暟鎹鐞嗙郴缁?- 鍖呭惈鍚庣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

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