鍒濆鎻愪氦锛氱墰鍙暟鎹鐞嗙郴缁?- 鍖呭惈鍚庣Spring Boot鍜屽墠绔疺ue3椤圭洰
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package com.example.cattletends;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* SpringBoot主启动类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class CattleTendsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CattleTendsApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.example.cattletends.common;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结果封装类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 响应码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 成功响应(无数据)
|
||||
*/
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(200, "操作成功", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(有数据)
|
||||
*/
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(200, "操作成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(自定义消息,无数据)
|
||||
*/
|
||||
public static <T> Result<T> success(String message) {
|
||||
return new Result<>(200, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> success(String message, T data) {
|
||||
return new Result<>(200, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*/
|
||||
public static <T> Result<T> error(String message) {
|
||||
return new Result<>(500, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应(自定义错误码)
|
||||
*/
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
return new Result<>(code, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应(自定义错误码和数据)
|
||||
*/
|
||||
public static <T> Result<T> error(Integer code, String message, T data) {
|
||||
return new Result<>(code, message, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.example.cattletends.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
|
||||
/**
|
||||
* Swagger配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public Docket api() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.example.cattletends.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("牛只数据管理API")
|
||||
.version("1.0.0")
|
||||
.description("牛只数据管理系统的RESTful API接口文档")
|
||||
.contact(new Contact("CattleTends Team", "", "support@example.com"))
|
||||
.license("Apache 2.0")
|
||||
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0.html")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,749 @@
|
||||
package com.example.cattletends.controller;
|
||||
|
||||
import com.example.cattletends.common.Result;
|
||||
import com.example.cattletends.dto.CattleDataDTO;
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import com.example.cattletends.service.CattleDataService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import javax.validation.Valid;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 牛只数据控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/cattle-data")
|
||||
@Api(tags = "牛只数据管理", description = "牛只数据的CRUD接口")
|
||||
public class CattleDataController {
|
||||
|
||||
private final CattleDataService cattleDataService;
|
||||
private final com.example.cattletends.service.CattleProvinceService cattleProvinceService;
|
||||
private final com.example.cattletends.service.CattleNationalService cattleNationalService;
|
||||
private final com.example.cattletends.service.CattleBreedService cattleBreedService;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleDataController(CattleDataService cattleDataService,
|
||||
com.example.cattletends.service.CattleProvinceService cattleProvinceService,
|
||||
com.example.cattletends.service.CattleNationalService cattleNationalService,
|
||||
com.example.cattletends.service.CattleBreedService cattleBreedService) {
|
||||
this.cattleDataService = cattleDataService;
|
||||
this.cattleProvinceService = cattleProvinceService;
|
||||
this.cattleNationalService = cattleNationalService;
|
||||
this.cattleBreedService = cattleBreedService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有牛只数据(支持按省份或品种筛选)
|
||||
*/
|
||||
@GetMapping
|
||||
@ApiOperation(value = "获取所有牛只数据", notes = "返回所有牛只数据的列表,支持按省份或品种筛选,按价格升序排序")
|
||||
public Result<List<CattleData>> getAllCattleData(
|
||||
@ApiParam(value = "省份(可选,如果提供则只返回该省份的数据)")
|
||||
@RequestParam(required = false) String province,
|
||||
@ApiParam(value = "品种(可选,如果提供则只返回该品种的数据)")
|
||||
@RequestParam(required = false) String type) {
|
||||
List<CattleData> data;
|
||||
|
||||
// 优先按品种筛选
|
||||
if (type != null && !type.trim().isEmpty()) {
|
||||
data = cattleDataService.getCattleDataByType(type.trim());
|
||||
} else if (province != null && !province.trim().isEmpty()) {
|
||||
// 其次按省份筛选
|
||||
data = cattleDataService.getCattleDataByProvince(province.trim());
|
||||
} else {
|
||||
// 都不提供则返回所有数据
|
||||
data = cattleDataService.getAllCattleData();
|
||||
}
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿里云大屏接口 - 获取牛只数据(直接返回数据数组,不包含Result包装)
|
||||
*/
|
||||
@GetMapping("/screen")
|
||||
@ApiOperation(value = "阿里云大屏接口", notes = "返回牛只数据数组,直接返回data数据,不包含Result包装。支持按省份或品种筛选,按价格升序排序")
|
||||
public List<CattleData> getCattleDataForScreen(
|
||||
@ApiParam(value = "省份(可选,如果提供则只返回该省份的数据)")
|
||||
@RequestParam(required = false) String province,
|
||||
@ApiParam(value = "品种(可选,如果提供则只返回该品种的数据)")
|
||||
@RequestParam(required = false) String type) {
|
||||
// 优先按品种筛选
|
||||
if (type != null && !type.trim().isEmpty()) {
|
||||
return cattleDataService.getCattleDataByType(type.trim());
|
||||
} else if (province != null && !province.trim().isEmpty()) {
|
||||
// 其次按省份筛选
|
||||
return cattleDataService.getCattleDataByProvince(province.trim());
|
||||
} else {
|
||||
// 都不提供则返回所有数据
|
||||
return cattleDataService.getAllCattleData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取牛只数据
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@ApiOperation(value = "根据ID获取牛只数据", notes = "根据主键ID查询单个牛只数据")
|
||||
public Result<CattleData> getCattleDataById(
|
||||
@ApiParam(value = "主键ID", required = true)
|
||||
@PathVariable Integer id) {
|
||||
CattleData data = cattleDataService.getCattleDataById(id);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入牛只数据(Excel文件)
|
||||
* 注意:此接口必须在 @PostMapping 之前定义,避免路径冲突
|
||||
*/
|
||||
@PostMapping("/import")
|
||||
@ApiOperation(value = "批量导入牛只数据", notes = "通过Excel文件批量导入牛只数据,自动截取所在产地到市级别,并自动创建品种")
|
||||
public Result<List<CattleData>> importCattleData(
|
||||
@ApiParam(value = "Excel文件", required = true)
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
System.out.println("========== 收到导入牛只数据请求 ==========");
|
||||
System.out.println("文件名: " + file.getOriginalFilename());
|
||||
System.out.println("文件大小: " + file.getSize());
|
||||
|
||||
if (file.isEmpty()) {
|
||||
System.out.println("错误: 文件为空");
|
||||
return Result.error(400, "文件不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
System.out.println("开始解析Excel文件...");
|
||||
List<CattleDataDTO> dataList = parseExcelFile(file);
|
||||
System.out.println("解析完成,共 " + (dataList != null ? dataList.size() : 0) + " 条数据");
|
||||
|
||||
if (dataList == null || dataList.isEmpty()) {
|
||||
System.out.println("错误: Excel文件中没有有效数据");
|
||||
return Result.error(400, "Excel文件中没有有效数据,请检查文件格式");
|
||||
}
|
||||
|
||||
// 自动创建品种
|
||||
System.out.println("========== 开始自动创建品种 ==========");
|
||||
int newBreedCount = 0;
|
||||
int existingBreedCount = 0;
|
||||
java.util.Set<String> processedBreeds = new java.util.HashSet<>();
|
||||
|
||||
for (CattleDataDTO dto : dataList) {
|
||||
if (dto.getType() != null && !dto.getType().trim().isEmpty()) {
|
||||
String breedName = dto.getType().trim();
|
||||
// 避免重复处理相同品种
|
||||
if (!processedBreeds.contains(breedName)) {
|
||||
try {
|
||||
com.example.cattletends.entity.CattleBreed existingBreed = cattleBreedService.getBreedByName(breedName);
|
||||
if (existingBreed == null) {
|
||||
// 新品种,创建
|
||||
cattleBreedService.createOrGetBreed(breedName);
|
||||
newBreedCount++;
|
||||
System.out.println("✓ 创建新品种: " + breedName);
|
||||
} else {
|
||||
existingBreedCount++;
|
||||
System.out.println("○ 品种已存在: " + breedName);
|
||||
}
|
||||
processedBreeds.add(breedName);
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 创建品种失败: " + breedName + ", 错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("品种处理完成: 新增 " + newBreedCount + " 个,已存在 " + existingBreedCount + " 个,共处理 " + processedBreeds.size() + " 个品种");
|
||||
System.out.println("========== 品种创建完成 ==========");
|
||||
|
||||
System.out.println("开始导入数据到数据库...");
|
||||
com.example.cattletends.dto.ImportResult importResult = cattleDataService.batchImportCattleData(dataList);
|
||||
System.out.println("导入完成: 新增 " + importResult.getNewCount() + " 条,更新 " + importResult.getUpdateCount() + " 条");
|
||||
|
||||
String message = String.format("导入成功:新增 %d 条,更新 %d 条,共处理 %d 条数据;自动创建 %d 个新品种,%d 个品种已存在",
|
||||
importResult.getNewCount(), importResult.getUpdateCount(), importResult.getTotalCount(),
|
||||
newBreedCount, existingBreedCount);
|
||||
return Result.success(message, importResult.getDataList());
|
||||
} catch (Exception e) {
|
||||
System.err.println("========== 导入失败 ==========");
|
||||
System.err.println("异常类型: " + e.getClass().getName());
|
||||
System.err.println("异常信息: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
String errorMessage = e.getMessage();
|
||||
if (errorMessage == null || errorMessage.isEmpty()) {
|
||||
errorMessage = e.getClass().getSimpleName() + ": " + e.toString();
|
||||
}
|
||||
return Result.error(500, "导入失败:" + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有品种
|
||||
*/
|
||||
@GetMapping("/breeds")
|
||||
@ApiOperation(value = "获取所有品种", notes = "返回所有品种列表")
|
||||
public Result<List<com.example.cattletends.entity.CattleBreed>> getAllBreeds() {
|
||||
List<com.example.cattletends.entity.CattleBreed> breeds = cattleBreedService.getAllBreeds();
|
||||
return Result.success(breeds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有省份数据
|
||||
*/
|
||||
@GetMapping("/provinces")
|
||||
@ApiOperation(value = "获取省份数据", notes = "返回所有省份的存栏出栏数据和省份均价,按省份均价降序排序。支持通过province参数按省份查询")
|
||||
public Result<List<com.example.cattletends.entity.CattleProvince>> getAllProvinceData(
|
||||
@ApiParam(value = "省份名称(可选),如果提供则只返回该省份数据", required = false)
|
||||
@RequestParam(required = false) String province) {
|
||||
List<com.example.cattletends.entity.CattleProvince> data = cattleProvinceService.getAllProvinceData(province);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿里云大屏接口 - 获取省份数据(直接返回数据数组,不包含Result包装)
|
||||
*/
|
||||
@GetMapping("/provinces/screen")
|
||||
@ApiOperation(value = "阿里云大屏接口-省份数据", notes = "返回省份数据数组,直接返回data数据,不包含Result包装。按省份均价降序排序,支持通过province参数按省份查询")
|
||||
public List<com.example.cattletends.entity.CattleProvince> getProvinceDataForScreen(
|
||||
@ApiParam(value = "省份名称(可选),如果提供则只返回该省份数据", required = false)
|
||||
@RequestParam(required = false) String province) {
|
||||
return cattleProvinceService.getAllProvinceData(province);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据省份名称获取省份数据
|
||||
*/
|
||||
@GetMapping("/provinces/{province}")
|
||||
@ApiOperation(value = "根据省份名称获取省份数据", notes = "根据省份名称查询单个省份的存栏出栏数据")
|
||||
public Result<com.example.cattletends.entity.CattleProvince> getProvinceDataByName(
|
||||
@ApiParam(value = "省份名称", required = true)
|
||||
@PathVariable String province) {
|
||||
com.example.cattletends.entity.CattleProvince data = cattleProvinceService.getProvinceDataByName(province);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全国总量数据
|
||||
* 自动从 cattleprovince 表计算总和
|
||||
*/
|
||||
@GetMapping("/national")
|
||||
@ApiOperation(value = "获取全国总量数据", notes = "自动从省份数据表计算并返回全国年份存栏和出栏总量数据")
|
||||
public Result<com.example.cattletends.entity.CattleNational> getNationalData() {
|
||||
com.example.cattletends.entity.CattleNational data = cattleNationalService.getNationalData();
|
||||
if (data == null) {
|
||||
return Result.error(404, "全国总量数据不存在,请先导入省份数据");
|
||||
}
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算全国总量数据
|
||||
* 根据 cattleprovince 表中的所有省份数据重新计算总和
|
||||
*/
|
||||
@PostMapping("/national/recalculate")
|
||||
@ApiOperation(value = "重新计算全国总量数据", notes = "根据 cattleprovince 表中的所有省份数据重新计算全国总量")
|
||||
public Result<com.example.cattletends.entity.CattleNational> recalculateNationalData() {
|
||||
com.example.cattletends.entity.CattleNational data = cattleNationalService.calculateAndUpdateNationalData();
|
||||
return Result.success("重新计算成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入省份数据(Excel文件)
|
||||
* 导入15个省份的存栏出栏量和省份均价,并计算全国总量
|
||||
*/
|
||||
@PostMapping("/import-province")
|
||||
@ApiOperation(value = "导入省份数据", notes = "导入15个省份的存栏出栏量和省份均价,自动计算全国总量")
|
||||
public Result<Map<String, Object>> importProvinceData(
|
||||
@ApiParam(value = "Excel文件", required = true)
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
System.out.println("========== 收到导入省份数据请求 ==========");
|
||||
System.out.println("文件名: " + file.getOriginalFilename());
|
||||
System.out.println("文件大小: " + file.getSize());
|
||||
|
||||
if (file.isEmpty()) {
|
||||
System.out.println("错误: 文件为空");
|
||||
return Result.error(400, "文件不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
System.out.println("开始解析Excel文件...");
|
||||
List<com.example.cattletends.dto.ProvinceDataDTO> provinceDataList = parseProvinceExcelFile(file);
|
||||
System.out.println("解析完成,共 " + (provinceDataList != null ? provinceDataList.size() : 0) + " 条数据");
|
||||
|
||||
if (provinceDataList == null || provinceDataList.isEmpty()) {
|
||||
System.out.println("错误: Excel文件中没有有效数据");
|
||||
return Result.error(400, "Excel文件中没有有效数据,请检查文件格式");
|
||||
}
|
||||
|
||||
System.out.println("开始导入数据到数据库...");
|
||||
Map<String, Object> result = cattleProvinceService.importProvinceData(provinceDataList);
|
||||
System.out.println("导入完成: " + result);
|
||||
|
||||
String message = String.format("导入成功:新增 %d 条,更新 %d 条,共处理 %d 条省份数据",
|
||||
result.get("createCount"), result.get("updateCount"), result.get("totalCount"));
|
||||
return Result.success(message, result);
|
||||
} catch (Exception e) {
|
||||
System.err.println("========== 导入失败 ==========");
|
||||
System.err.println("异常类型: " + e.getClass().getName());
|
||||
System.err.println("异常信息: " + e.getMessage());
|
||||
e.printStackTrace(); // 打印完整堆栈信息
|
||||
|
||||
String errorMessage = e.getMessage();
|
||||
if (errorMessage == null || errorMessage.isEmpty()) {
|
||||
errorMessage = e.getClass().getSimpleName() + ": " + e.toString();
|
||||
}
|
||||
return Result.error(500, "导入失败:" + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建牛只数据
|
||||
*/
|
||||
@PostMapping
|
||||
@ApiOperation(value = "创建牛只数据", notes = "创建新的牛只数据记录")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Result<CattleData> createCattleData(
|
||||
@ApiParam(value = "牛只数据信息", required = true)
|
||||
@Valid @RequestBody CattleDataDTO dto) {
|
||||
CattleData data = cattleDataService.createCattleData(dto);
|
||||
return Result.success("创建成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新牛只数据
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@ApiOperation(value = "更新牛只数据", notes = "根据ID更新牛只数据信息")
|
||||
public Result<CattleData> updateCattleData(
|
||||
@ApiParam(value = "主键ID", required = true)
|
||||
@PathVariable Integer id,
|
||||
@ApiParam(value = "牛只数据信息", required = true)
|
||||
@Valid @RequestBody CattleDataDTO dto) {
|
||||
CattleData data = cattleDataService.updateCattleData(id, dto);
|
||||
return Result.success("更新成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除牛只数据
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@ApiOperation(value = "删除牛只数据", notes = "根据ID删除牛只数据")
|
||||
public Result<Void> deleteCattleData(
|
||||
@ApiParam(value = "主键ID", required = true)
|
||||
@PathVariable Integer id) {
|
||||
cattleDataService.deleteCattleData(id);
|
||||
return Result.success("删除成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Excel文件
|
||||
*/
|
||||
private List<CattleDataDTO> parseExcelFile(MultipartFile file) throws IOException {
|
||||
List<CattleDataDTO> dataList = new ArrayList<>();
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
|
||||
// 从第二行开始读取(第一行是表头)
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CattleDataDTO dto = new CattleDataDTO();
|
||||
|
||||
// Excel模板列顺序(根据图片):
|
||||
// 列1(索引0):日期
|
||||
// 列2(索引1):品种
|
||||
// 列3(索引2):省份
|
||||
// 列4(索引3):地市
|
||||
// 列5(索引4):价格(元/斤)
|
||||
|
||||
// 读取日期(第1列,索引0)
|
||||
Cell timeCell = row.getCell(0);
|
||||
if (timeCell != null) {
|
||||
LocalDateTime createTime = parseDateTime(timeCell);
|
||||
dto.setCreateTime(createTime);
|
||||
}
|
||||
|
||||
// 读取品种(第2列,索引1)
|
||||
Cell typeCell = row.getCell(1);
|
||||
if (typeCell != null) {
|
||||
String breedName = getCellValueAsString(typeCell).trim();
|
||||
dto.setType(breedName);
|
||||
}
|
||||
|
||||
// 读取省份(第3列,索引2),完全按照表格内容,不做截取
|
||||
Cell provinceCell = row.getCell(2);
|
||||
if (provinceCell != null) {
|
||||
String province = getCellValueAsString(provinceCell).trim();
|
||||
dto.setProvince(province);
|
||||
}
|
||||
|
||||
// 读取地市(第4列,索引3),截取到"市"
|
||||
Cell locationCell = row.getCell(3);
|
||||
if (locationCell != null) {
|
||||
String location = getCellValueAsString(locationCell).trim();
|
||||
// 截取到"市"、"盟"、"地区"、"州"等
|
||||
String cityLocation = extractCity(location);
|
||||
dto.setLocation(cityLocation);
|
||||
}
|
||||
|
||||
// 读取价格(第5列,索引4)
|
||||
Cell priceCell = row.getCell(4);
|
||||
if (priceCell != null) {
|
||||
BigDecimal price = parsePrice(priceCell, dto.getLocation() != null ? dto.getLocation() : "未知");
|
||||
dto.setPrice(price);
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (dto.getType() != null && !dto.getType().trim().isEmpty() &&
|
||||
dto.getProvince() != null && !dto.getProvince().trim().isEmpty() &&
|
||||
dto.getLocation() != null && !dto.getLocation().trim().isEmpty() &&
|
||||
dto.getPrice() != null) {
|
||||
dataList.add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格值(字符串)
|
||||
*/
|
||||
private String getCellValueAsString(Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue().trim();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
// 避免科学计数法
|
||||
double numericValue = cell.getNumericCellValue();
|
||||
if (numericValue == (long) numericValue) {
|
||||
return String.valueOf((long) numericValue);
|
||||
} else {
|
||||
return String.valueOf(numericValue);
|
||||
}
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
case FORMULA:
|
||||
return cell.getCellFormula();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析日期时间
|
||||
*/
|
||||
private LocalDateTime parseDateTime(Cell cell) {
|
||||
try {
|
||||
if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
|
||||
java.util.Date date = cell.getDateCellValue();
|
||||
return new java.sql.Timestamp(date.getTime()).toLocalDateTime();
|
||||
} else if (cell.getCellType() == CellType.STRING) {
|
||||
String dateStr = cell.getStringCellValue().trim();
|
||||
// 尝试解析 "2025/11/23" 格式
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/d");
|
||||
return java.time.LocalDate.parse(dateStr, formatter).atStartOfDay();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败,返回当前时间
|
||||
}
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析价格
|
||||
* 支持格式:
|
||||
* - 数字:13.5
|
||||
* - 价格范围:13.41-14.61(取平均值)
|
||||
* - 字符串数字:"13.5"
|
||||
* @param cell 价格单元格
|
||||
* @param location 位置信息(用于错误日志)
|
||||
*/
|
||||
private BigDecimal parsePrice(Cell cell, String location) {
|
||||
try {
|
||||
if (cell == null) {
|
||||
System.out.println("解析价格失败: " + location + ", 错误: 单元格为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cell.getCellType() == CellType.NUMERIC) {
|
||||
return BigDecimal.valueOf(cell.getNumericCellValue());
|
||||
} else if (cell.getCellType() == CellType.STRING) {
|
||||
String priceStr = cell.getStringCellValue().trim();
|
||||
if (priceStr == null || priceStr.isEmpty()) {
|
||||
System.out.println("解析价格失败: " + location + ", 错误: 价格字符串为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 处理价格范围格式,如 "13.41-14.61"
|
||||
if (priceStr.contains("-")) {
|
||||
String[] parts = priceStr.split("-");
|
||||
if (parts.length == 2) {
|
||||
try {
|
||||
BigDecimal min = new BigDecimal(parts[0].trim());
|
||||
BigDecimal max = new BigDecimal(parts[1].trim());
|
||||
// 计算平均值
|
||||
BigDecimal average = min.add(max).divide(BigDecimal.valueOf(2), 2, java.math.RoundingMode.HALF_UP);
|
||||
return average;
|
||||
} catch (Exception e) {
|
||||
// 如果解析范围失败,尝试解析第一个值
|
||||
try {
|
||||
return new BigDecimal(parts[0].trim());
|
||||
} catch (Exception e2) {
|
||||
System.out.println("解析价格失败: " + location + ", 价格字符串: " + priceStr + ", 错误: " + e2.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通数字字符串
|
||||
return new BigDecimal(priceStr);
|
||||
} else {
|
||||
System.out.println("解析价格失败: " + location + ", 错误: 单元格类型不支持, 类型: " + cell.getCellType());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败,返回null
|
||||
String cellValue = cell != null ? getCellValueAsString(cell) : "null";
|
||||
System.out.println("解析价格失败: " + location + ", 单元格值: " + cellValue + ", 错误: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取所在产地到"市"级别
|
||||
*/
|
||||
private String extractCity(String location) {
|
||||
if (location == null || location.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 查找"市"的位置
|
||||
int cityIndex = location.indexOf("市");
|
||||
if (cityIndex > 0) {
|
||||
// 包含"市"字符
|
||||
return location.substring(0, cityIndex + 1);
|
||||
}
|
||||
|
||||
// 如果没有找到"市",返回原字符串(可能是直辖市或其他格式)
|
||||
return location.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从location中提取省份
|
||||
* 例如:"安徽淮南市" -> "安徽"
|
||||
*/
|
||||
private String extractProvince(String location) {
|
||||
if (location == null || location.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String trimmed = location.trim();
|
||||
|
||||
// 中国省份列表(包括自治区、直辖市、特别行政区)
|
||||
String[] provinces = {
|
||||
"北京", "天津", "上海", "重庆",
|
||||
"河北", "山西", "辽宁", "吉林", "黑龙江",
|
||||
"江苏", "浙江", "安徽", "福建", "江西", "山东",
|
||||
"河南", "湖北", "湖南", "广东", "海南",
|
||||
"四川", "贵州", "云南", "陕西", "甘肃", "青海",
|
||||
"内蒙古", "广西", "西藏", "宁夏", "新疆",
|
||||
"香港", "澳门", "台湾"
|
||||
};
|
||||
|
||||
// 匹配省份(包括自治区、直辖市等)
|
||||
for (String province : provinces) {
|
||||
if (trimmed.startsWith(province)) {
|
||||
// 处理特殊情况:内蒙古、黑龙江等
|
||||
if (province.equals("内蒙古") && trimmed.startsWith("内蒙古自治区")) {
|
||||
return "内蒙古";
|
||||
}
|
||||
return province;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配到,尝试提取前2-3个字符作为省份
|
||||
// 大多数省份名称是2-3个字符
|
||||
if (trimmed.length() >= 2) {
|
||||
// 尝试2个字符
|
||||
String twoChar = trimmed.substring(0, 2);
|
||||
for (String province : provinces) {
|
||||
if (province.equals(twoChar)) {
|
||||
return province;
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试3个字符(如内蒙古)
|
||||
if (trimmed.length() >= 3) {
|
||||
String threeChar = trimmed.substring(0, 3);
|
||||
for (String province : provinces) {
|
||||
if (province.equals(threeChar)) {
|
||||
return province;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都匹配不到,返回空字符串
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析省份数据Excel文件
|
||||
* Excel格式(列顺序):
|
||||
* A列:省份
|
||||
* B列:2023存栏(万头) -> inventory_23th
|
||||
* C列:2023出栏(万头) -> slaughter_23th
|
||||
* D列:2024存栏(万头) -> inventory_24th
|
||||
* E列:2024出栏(万头) -> slaughter_24th
|
||||
* F列:2025存栏(万头) -> inventory_25th
|
||||
* G列:2025出栏(万头) -> slaughter_25th
|
||||
* H列:省份均价(元/斤) -> province_price
|
||||
*/
|
||||
private List<com.example.cattletends.dto.ProvinceDataDTO> parseProvinceExcelFile(MultipartFile file) throws IOException {
|
||||
List<com.example.cattletends.dto.ProvinceDataDTO> dataList = new ArrayList<>();
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) {
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
|
||||
// 从第二行开始读取(第一行是表头)
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
com.example.cattletends.dto.ProvinceDataDTO dto = new com.example.cattletends.dto.ProvinceDataDTO();
|
||||
|
||||
// Excel模板列顺序(根据实际模板):
|
||||
// A列(索引0):省份
|
||||
// B列(索引1):2023存栏(万头) -> inventory_23th
|
||||
// C列(索引2):2023出栏(万头) -> slaughter_23th
|
||||
// D列(索引3):2024存栏(万头) -> inventory_24th
|
||||
// E列(索引4):2024出栏(万头) -> slaughter_24th
|
||||
// F列(索引5):2025存栏(万头) -> inventory_25th
|
||||
// G列(索引6):2025出栏(万头) -> slaughter_25th
|
||||
// H列(索引7):省份均价(元/斤) -> province_price
|
||||
|
||||
// 读取省份(A列,索引0)
|
||||
Cell provinceCell = row.getCell(0);
|
||||
if (provinceCell != null) {
|
||||
String province = getCellValueAsString(provinceCell);
|
||||
// 处理可能包含"省"、"自治区"等后缀的情况
|
||||
province = province.replace("省", "").replace("自治区", "").replace("市", "").trim();
|
||||
dto.setProvince(province);
|
||||
}
|
||||
|
||||
// 读取2023存栏(万头)(B列,索引1)-> inventory_23th
|
||||
Cell inv23Cell = row.getCell(1);
|
||||
if (inv23Cell != null) {
|
||||
Integer value = parseInteger(inv23Cell);
|
||||
dto.setInventory23th(value);
|
||||
}
|
||||
|
||||
// 读取2023出栏(万头)(C列,索引2)-> slaughter_23th
|
||||
Cell sla23Cell = row.getCell(2);
|
||||
if (sla23Cell != null) {
|
||||
Integer value = parseInteger(sla23Cell);
|
||||
dto.setSlaughter23th(value);
|
||||
}
|
||||
|
||||
// 读取2024存栏(万头)(D列,索引3)-> inventory_24th
|
||||
Cell inv24Cell = row.getCell(3);
|
||||
if (inv24Cell != null) {
|
||||
Integer value = parseInteger(inv24Cell);
|
||||
dto.setInventory24th(value);
|
||||
}
|
||||
|
||||
// 读取2024出栏(万头)(E列,索引4)-> slaughter_24th
|
||||
Cell sla24Cell = row.getCell(4);
|
||||
if (sla24Cell != null) {
|
||||
Integer value = parseInteger(sla24Cell);
|
||||
dto.setSlaughter24th(value);
|
||||
}
|
||||
|
||||
// 读取2025存栏(万头)(F列,索引5)-> inventory_25th
|
||||
Cell inv25Cell = row.getCell(5);
|
||||
if (inv25Cell != null) {
|
||||
Integer value = parseInteger(inv25Cell);
|
||||
dto.setInventory25th(value);
|
||||
}
|
||||
|
||||
// 读取2025出栏(万头)(G列,索引6)-> slaughter_25th
|
||||
Cell sla25Cell = row.getCell(6);
|
||||
if (sla25Cell != null) {
|
||||
Integer value = parseInteger(sla25Cell);
|
||||
dto.setSlaughter25th(value);
|
||||
}
|
||||
|
||||
// 读取省份均价(元/斤)(H列,索引7)-> province_price
|
||||
Cell priceCell = row.getCell(7);
|
||||
if (priceCell != null) {
|
||||
String priceStr = getCellValueAsString(priceCell);
|
||||
System.out.println("省份: " + dto.getProvince() + ", 原始价格字符串: [" + priceStr + "]");
|
||||
BigDecimal price = parsePrice(priceCell, dto.getProvince() != null ? dto.getProvince() : "未知省份");
|
||||
dto.setProvincePrice(price);
|
||||
System.out.println("省份: " + dto.getProvince() + ", 解析后价格: " + price);
|
||||
} else {
|
||||
System.out.println("省份: " + dto.getProvince() + ", 价格单元格为空或不存在");
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (dto.getProvince() != null && !dto.getProvince().trim().isEmpty()) {
|
||||
dataList.add(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析整数(处理可能包含±的情况,如"400±20"取400)
|
||||
*/
|
||||
private Integer parseInteger(Cell cell) {
|
||||
try {
|
||||
if (cell.getCellType() == CellType.NUMERIC) {
|
||||
return (int) cell.getNumericCellValue();
|
||||
} else if (cell.getCellType() == CellType.STRING) {
|
||||
String value = cell.getStringCellValue().trim();
|
||||
// 处理"400±20"格式,取前面的数字
|
||||
if (value.contains("±")) {
|
||||
value = value.substring(0, value.indexOf("±")).trim();
|
||||
}
|
||||
// 处理"万头"等后缀
|
||||
value = value.replace("万头", "").trim();
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.cattletends.controller;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Favicon 控制器
|
||||
* 处理浏览器自动请求的 favicon.ico,避免 404 错误
|
||||
*/
|
||||
@RestController
|
||||
public class FaviconController {
|
||||
|
||||
/**
|
||||
* 处理 favicon.ico 请求
|
||||
* 返回 204 No Content,表示请求成功但无内容返回
|
||||
*/
|
||||
@GetMapping("favicon.ico")
|
||||
public ResponseEntity<Void> favicon() {
|
||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.example.cattletends.dto;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 牛只数据传输对象
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleDataDTO {
|
||||
|
||||
/**
|
||||
* 主键ID(更新时使用)
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 品种
|
||||
*/
|
||||
@NotBlank(message = "品种不能为空")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
@NotBlank(message = "省份不能为空")
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 所在产地
|
||||
*/
|
||||
@NotBlank(message = "所在产地不能为空")
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 价格(元/斤)
|
||||
*/
|
||||
@NotNull(message = "价格不能为空")
|
||||
@DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0")
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 创建时间(只读)
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间(只读)
|
||||
*/
|
||||
private LocalDateTime upTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.example.cattletends.dto;
|
||||
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 导入结果封装类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ImportResult {
|
||||
|
||||
/**
|
||||
* 导入的数据列表
|
||||
*/
|
||||
private List<CattleData> dataList;
|
||||
|
||||
/**
|
||||
* 新增数量
|
||||
*/
|
||||
private int newCount;
|
||||
|
||||
/**
|
||||
* 更新数量
|
||||
*/
|
||||
private int updateCount;
|
||||
|
||||
/**
|
||||
* 总数量
|
||||
*/
|
||||
public int getTotalCount() {
|
||||
return newCount + updateCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.example.cattletends.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 省份数据传输对象(用于导入省份存栏出栏数据)
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ProvinceDataDTO {
|
||||
|
||||
/**
|
||||
* 省份名称
|
||||
*/
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 省份均价
|
||||
*/
|
||||
private BigDecimal provincePrice;
|
||||
|
||||
/**
|
||||
* 23年存栏(万头)
|
||||
*/
|
||||
private Integer inventory23th;
|
||||
|
||||
/**
|
||||
* 23年出栏(万头)
|
||||
*/
|
||||
private Integer slaughter23th;
|
||||
|
||||
/**
|
||||
* 24年存栏(万头)
|
||||
*/
|
||||
private Integer inventory24th;
|
||||
|
||||
/**
|
||||
* 24年出栏(万头)
|
||||
*/
|
||||
private Integer slaughter24th;
|
||||
|
||||
/**
|
||||
* 25年存栏(万头)
|
||||
*/
|
||||
private Integer inventory25th;
|
||||
|
||||
/**
|
||||
* 25年出栏(万头)
|
||||
*/
|
||||
private Integer slaughter25th;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 品种表实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattlebreed", indexes = {
|
||||
@Index(name = "idx_breed_name", columnList = "breed_name", unique = true),
|
||||
@Index(name = "idx_create_time", columnList = "create_time"),
|
||||
@Index(name = "idx_up_time", columnList = "up_time")
|
||||
})
|
||||
public class CattleBreed {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 品种名称(唯一)
|
||||
*/
|
||||
@Column(name = "breed_name", nullable = false, unique = true, length = 100)
|
||||
private String breedName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createTime = LocalDateTime.now();
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBreedName() {
|
||||
return breedName;
|
||||
}
|
||||
|
||||
public void setBreedName(String breedName) {
|
||||
this.breedName = breedName;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpTime() {
|
||||
return upTime;
|
||||
}
|
||||
|
||||
public void setUpTime(LocalDateTime upTime) {
|
||||
this.upTime = upTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 牛只数据实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattleData", indexes = {
|
||||
@Index(name = "idx_price", columnList = "price"),
|
||||
@Index(name = "idx_type", columnList = "type"),
|
||||
@Index(name = "idx_province", columnList = "province"),
|
||||
@Index(name = "idx_type_price", columnList = "type,price"),
|
||||
@Index(name = "idx_province_price", columnList = "province,price"),
|
||||
@Index(name = "idx_type_province_location_price", columnList = "type,province,location,price")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleData {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false, updatable = false)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 品种
|
||||
*/
|
||||
@Column(name = "type", length = 255)
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 省份
|
||||
*/
|
||||
@Column(name = "province", length = 255, nullable = false)
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 所在产地
|
||||
*/
|
||||
@Column(name = "location", length = 255)
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 价格(元/斤)
|
||||
*/
|
||||
@Column(name = "price", precision = 10, scale = 2)
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
/**
|
||||
* 保存前自动设置创建时间和更新时间
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (createTime == null) {
|
||||
createTime = now;
|
||||
}
|
||||
if (upTime == null) {
|
||||
upTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前自动设置更新时间
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 全国总量数据实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattlenational", indexes = {
|
||||
@Index(name = "idx_national_inv_23", columnList = "national_inventory_23th"),
|
||||
@Index(name = "idx_national_sla_23", columnList = "national_slaughter_23th"),
|
||||
@Index(name = "idx_national_inv_24", columnList = "national_inventory_24th"),
|
||||
@Index(name = "idx_national_sla_24", columnList = "national_slaughter_24th"),
|
||||
@Index(name = "idx_national_inv_25", columnList = "national_inventory_25th"),
|
||||
@Index(name = "idx_national_sla_25", columnList = "national_slaughter_25th")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleNational {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false, updatable = false)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 23年全国存栏量(15个省份存栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_inventory_23th")
|
||||
private Integer nationalInventory23th;
|
||||
|
||||
/**
|
||||
* 23年全国出栏量(15个省份出栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_slaughter_23th")
|
||||
private Integer nationalSlaughter23th;
|
||||
|
||||
/**
|
||||
* 24年全国存栏量(15个省份存栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_inventory_24th")
|
||||
private Integer nationalInventory24th;
|
||||
|
||||
/**
|
||||
* 24年全国出栏量(15个省份出栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_slaughter_24th")
|
||||
private Integer nationalSlaughter24th;
|
||||
|
||||
/**
|
||||
* 25年全国存栏量(15个省份存栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_inventory_25th")
|
||||
private Integer nationalInventory25th;
|
||||
|
||||
/**
|
||||
* 25年全国出栏量(15个省份出栏量的总和)
|
||||
*/
|
||||
@Column(name = "national_slaughter_25th")
|
||||
private Integer nationalSlaughter25th;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
/**
|
||||
* 保存前自动设置创建时间和更新时间
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (createTime == null) {
|
||||
createTime = now;
|
||||
}
|
||||
if (upTime == null) {
|
||||
upTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前自动设置更新时间
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.example.cattletends.entity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 省份数据实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "cattleprovince", indexes = {
|
||||
@Index(name = "idx_province_name", columnList = "province"),
|
||||
@Index(name = "idx_province_price", columnList = "province_price"),
|
||||
@Index(name = "idx_inv_23", columnList = "inventory_23th"),
|
||||
@Index(name = "idx_sla_23", columnList = "slaughter_23th"),
|
||||
@Index(name = "idx_inv_24", columnList = "inventory_24th"),
|
||||
@Index(name = "idx_sla_24", columnList = "slaughter_24th"),
|
||||
@Index(name = "idx_inv_25", columnList = "inventory_25th"),
|
||||
@Index(name = "idx_sla_25", columnList = "slaughter_25th")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CattleProvince {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false, updatable = false)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 省份名称
|
||||
*/
|
||||
@Column(name = "province", length = 255, nullable = false, unique = true)
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 省份均价
|
||||
*/
|
||||
@Column(name = "province_price", precision = 10, scale = 2)
|
||||
private BigDecimal provincePrice;
|
||||
|
||||
/**
|
||||
* 23年存栏(万头)
|
||||
*/
|
||||
@Column(name = "inventory_23th")
|
||||
private Integer inventory23th;
|
||||
|
||||
/**
|
||||
* 23年出栏(万头)
|
||||
*/
|
||||
@Column(name = "slaughter_23th")
|
||||
private Integer slaughter23th;
|
||||
|
||||
/**
|
||||
* 24年存栏(万头)
|
||||
*/
|
||||
@Column(name = "inventory_24th")
|
||||
private Integer inventory24th;
|
||||
|
||||
/**
|
||||
* 24年出栏(万头)
|
||||
*/
|
||||
@Column(name = "slaughter_24th")
|
||||
private Integer slaughter24th;
|
||||
|
||||
/**
|
||||
* 25年存栏(万头)
|
||||
*/
|
||||
@Column(name = "inventory_25th")
|
||||
private Integer inventory25th;
|
||||
|
||||
/**
|
||||
* 25年出栏(万头)
|
||||
*/
|
||||
@Column(name = "slaughter_25th")
|
||||
private Integer slaughter25th;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "up_time", nullable = false)
|
||||
private LocalDateTime upTime;
|
||||
|
||||
/**
|
||||
* 保存前自动设置创建时间和更新时间
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (createTime == null) {
|
||||
createTime = now;
|
||||
}
|
||||
if (upTime == null) {
|
||||
upTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前自动设置更新时间
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
upTime = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.example.cattletends.exception;
|
||||
|
||||
import com.example.cattletends.common.Result;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理参数校验异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
logger.warn("参数校验失败: {}", errors);
|
||||
return Result.error(400, "参数校验失败", errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleRuntimeException(RuntimeException ex) {
|
||||
logger.error("业务异常: {}", ex.getMessage(), ex);
|
||||
return Result.error(400, ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleIllegalArgumentException(IllegalArgumentException ex) {
|
||||
logger.warn("非法参数: {}", ex.getMessage());
|
||||
return Result.error(400, ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleException(Exception ex) {
|
||||
logger.error("系统异常: {}", ex.getMessage(), ex);
|
||||
return Result.error(500, "系统内部错误,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleBreed;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 品种表Repository
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleBreedRepository extends JpaRepository<CattleBreed, Integer> {
|
||||
|
||||
/**
|
||||
* 根据品种名称查询
|
||||
*/
|
||||
Optional<CattleBreed> findByBreedName(String breedName);
|
||||
|
||||
/**
|
||||
* 检查品种名称是否存在
|
||||
*/
|
||||
boolean existsByBreedName(String breedName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 牛只数据Repository接口
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleDataRepository extends JpaRepository<CattleData, Integer> {
|
||||
|
||||
/**
|
||||
* 根据省份查询牛只数据,按价格升序排序
|
||||
*
|
||||
* @param province 省份
|
||||
* @param sort 排序规则
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> findByProvince(String province, Sort sort);
|
||||
|
||||
/**
|
||||
* 根据品种查询牛只数据,按价格升序排序
|
||||
*
|
||||
* @param type 品种
|
||||
* @param sort 排序规则
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> findByType(String type, Sort sort);
|
||||
|
||||
/**
|
||||
* 根据品种、省份、产地、价格查询牛只数据(用于判断重复)
|
||||
*
|
||||
* @param type 品种
|
||||
* @param province 省份
|
||||
* @param location 产地
|
||||
* @param price 价格
|
||||
* @return 牛只数据(如果存在)
|
||||
*/
|
||||
Optional<CattleData> findByTypeAndProvinceAndLocationAndPrice(
|
||||
String type, String province, String location, BigDecimal price);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleNational;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 全国总量数据Repository接口
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleNationalRepository extends JpaRepository<CattleNational, Integer> {
|
||||
|
||||
/**
|
||||
* 查找第一条记录(全国总量通常只有一条记录)
|
||||
*
|
||||
* @return 全国总量数据
|
||||
*/
|
||||
Optional<CattleNational> findFirstByOrderByIdAsc();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.example.cattletends.repository;
|
||||
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 省份数据Repository接口
|
||||
*/
|
||||
@Repository
|
||||
public interface CattleProvinceRepository extends JpaRepository<CattleProvince, Integer> {
|
||||
|
||||
/**
|
||||
* 根据省份名称查询
|
||||
*
|
||||
* @param province 省份名称
|
||||
* @return 省份数据
|
||||
*/
|
||||
Optional<CattleProvince> findByProvince(String province);
|
||||
|
||||
/**
|
||||
* 根据省份名称查询(支持排序)
|
||||
*
|
||||
* @param province 省份名称
|
||||
* @param sort 排序方式
|
||||
* @return 省份数据列表
|
||||
*/
|
||||
List<CattleProvince> findByProvince(String province, Sort sort);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.entity.CattleBreed;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 品种服务接口
|
||||
*/
|
||||
public interface CattleBreedService {
|
||||
|
||||
/**
|
||||
* 创建或获取品种(如果已存在则返回,不存在则创建)
|
||||
* @param breedName 品种名称
|
||||
* @return 品种实体
|
||||
*/
|
||||
CattleBreed createOrGetBreed(String breedName);
|
||||
|
||||
/**
|
||||
* 获取所有品种
|
||||
* @return 品种列表
|
||||
*/
|
||||
List<CattleBreed> getAllBreeds();
|
||||
|
||||
/**
|
||||
* 根据品种名称获取品种
|
||||
* @param breedName 品种名称
|
||||
* @return 品种实体
|
||||
*/
|
||||
CattleBreed getBreedByName(String breedName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.dto.CattleDataDTO;
|
||||
import com.example.cattletends.dto.ImportResult;
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 牛只数据服务接口
|
||||
*/
|
||||
public interface CattleDataService {
|
||||
|
||||
/**
|
||||
* 获取所有牛只数据
|
||||
*
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getAllCattleData();
|
||||
|
||||
/**
|
||||
* 根据省份获取牛只数据
|
||||
*
|
||||
* @param province 省份
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getCattleDataByProvince(String province);
|
||||
|
||||
/**
|
||||
* 根据品种获取牛只数据
|
||||
*
|
||||
* @param type 品种
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getCattleDataByType(String type);
|
||||
|
||||
/**
|
||||
* 根据ID获取牛只数据
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @return 牛只数据
|
||||
*/
|
||||
CattleData getCattleDataById(Integer id);
|
||||
|
||||
/**
|
||||
* 创建牛只数据
|
||||
*
|
||||
* @param dto 牛只数据传输对象
|
||||
* @return 创建的牛只数据
|
||||
*/
|
||||
CattleData createCattleData(CattleDataDTO dto);
|
||||
|
||||
/**
|
||||
* 更新牛只数据
|
||||
*
|
||||
* @param id 主键ID
|
||||
* @param dto 牛只数据传输对象
|
||||
* @return 更新后的牛只数据
|
||||
*/
|
||||
CattleData updateCattleData(Integer id, CattleDataDTO dto);
|
||||
|
||||
/**
|
||||
* 删除牛只数据
|
||||
*
|
||||
* @param id 主键ID
|
||||
*/
|
||||
void deleteCattleData(Integer id);
|
||||
|
||||
/**
|
||||
* 批量导入牛只数据
|
||||
*
|
||||
* @param dataList 牛只数据列表
|
||||
* @return 导入结果(包含数据列表、新增数量、更新数量)
|
||||
*/
|
||||
ImportResult batchImportCattleData(List<CattleDataDTO> dataList);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.entity.CattleNational;
|
||||
|
||||
/**
|
||||
* 全国总量数据服务接口
|
||||
*/
|
||||
public interface CattleNationalService {
|
||||
|
||||
/**
|
||||
* 获取全国总量数据
|
||||
* 如果不存在则自动从 cattleprovince 表计算并创建
|
||||
*
|
||||
* @return 全国总量数据
|
||||
*/
|
||||
CattleNational getNationalData();
|
||||
|
||||
/**
|
||||
* 根据 cattleprovince 表中的数据自动计算并更新全国总量
|
||||
* 计算所有省份的存栏和出栏总和
|
||||
*
|
||||
* @return 更新后的全国总量数据
|
||||
*/
|
||||
CattleNational calculateAndUpdateNationalData();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.example.cattletends.service;
|
||||
|
||||
import com.example.cattletends.dto.ProvinceDataDTO;
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 省份数据服务接口
|
||||
*/
|
||||
public interface CattleProvinceService {
|
||||
|
||||
/**
|
||||
* 导入省份数据
|
||||
* 更新或创建各省份的存栏出栏量和省份均价,并计算全国总量
|
||||
*
|
||||
* @param provinceDataList 省份数据列表
|
||||
* @return 导入结果(包含更新的数据数量和全国总量)
|
||||
*/
|
||||
Map<String, Object> importProvinceData(List<ProvinceDataDTO> provinceDataList);
|
||||
|
||||
/**
|
||||
* 获取所有省份数据(按省份均价降序排序)
|
||||
*
|
||||
* @param province 可选的省份名称,如果提供则只返回该省份数据
|
||||
* @return 省份数据列表
|
||||
*/
|
||||
List<CattleProvince> getAllProvinceData(String province);
|
||||
|
||||
/**
|
||||
* 根据省份名称获取数据
|
||||
*
|
||||
* @param province 省份名称
|
||||
* @return 省份数据
|
||||
*/
|
||||
CattleProvince getProvinceDataByName(String province);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.entity.CattleBreed;
|
||||
import com.example.cattletends.repository.CattleBreedRepository;
|
||||
import com.example.cattletends.service.CattleBreedService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 品种服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleBreedServiceImpl implements CattleBreedService {
|
||||
|
||||
@Autowired
|
||||
private CattleBreedRepository cattleBreedRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleBreed createOrGetBreed(String breedName) {
|
||||
if (breedName == null || breedName.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmedName = breedName.trim();
|
||||
|
||||
// 先查询是否已存在
|
||||
Optional<CattleBreed> existing = cattleBreedRepository.findByBreedName(trimmedName);
|
||||
if (existing.isPresent()) {
|
||||
System.out.println("品种已存在: " + trimmedName);
|
||||
return existing.get();
|
||||
}
|
||||
|
||||
// 不存在则创建
|
||||
CattleBreed breed = new CattleBreed();
|
||||
breed.setBreedName(trimmedName);
|
||||
CattleBreed saved = cattleBreedRepository.save(breed);
|
||||
System.out.println("创建新品种: " + trimmedName + " (ID: " + saved.getId() + ")");
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CattleBreed> getAllBreeds() {
|
||||
return cattleBreedRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CattleBreed getBreedByName(String breedName) {
|
||||
if (breedName == null || breedName.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return cattleBreedRepository.findByBreedName(breedName.trim())
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.dto.CattleDataDTO;
|
||||
import com.example.cattletends.dto.ImportResult;
|
||||
import com.example.cattletends.entity.CattleData;
|
||||
import com.example.cattletends.repository.CattleDataRepository;
|
||||
import com.example.cattletends.service.CattleDataService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 牛只数据服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleDataServiceImpl implements CattleDataService {
|
||||
|
||||
private final CattleDataRepository cattleDataRepository;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleDataServiceImpl(CattleDataRepository cattleDataRepository) {
|
||||
this.cattleDataRepository = cattleDataRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleData> getAllCattleData() {
|
||||
// 按价格升序排序
|
||||
return cattleDataRepository.findAll(Sort.by(Sort.Direction.ASC, "price"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleData> getCattleDataByProvince(String province) {
|
||||
// 按价格升序排序
|
||||
return cattleDataRepository.findByProvince(province, Sort.by(Sort.Direction.ASC, "price"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleData> getCattleDataByType(String type) {
|
||||
// 按价格升序排序
|
||||
return cattleDataRepository.findByType(type, Sort.by(Sort.Direction.ASC, "price"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CattleData getCattleDataById(Integer id) {
|
||||
return cattleDataRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("牛只数据不存在,ID: " + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleData createCattleData(CattleDataDTO dto) {
|
||||
CattleData cattleData = new CattleData();
|
||||
cattleData.setType(dto.getType());
|
||||
cattleData.setProvince(dto.getProvince());
|
||||
cattleData.setLocation(dto.getLocation());
|
||||
cattleData.setPrice(dto.getPrice());
|
||||
return cattleDataRepository.save(cattleData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleData updateCattleData(Integer id, CattleDataDTO dto) {
|
||||
CattleData cattleData = cattleDataRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("牛只数据不存在,ID: " + id));
|
||||
|
||||
cattleData.setType(dto.getType());
|
||||
cattleData.setProvince(dto.getProvince());
|
||||
cattleData.setLocation(dto.getLocation());
|
||||
cattleData.setPrice(dto.getPrice());
|
||||
|
||||
return cattleDataRepository.save(cattleData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteCattleData(Integer id) {
|
||||
if (!cattleDataRepository.existsById(id)) {
|
||||
throw new RuntimeException("牛只数据不存在,ID: " + id);
|
||||
}
|
||||
cattleDataRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ImportResult batchImportCattleData(List<CattleDataDTO> dataList) {
|
||||
List<CattleData> savedList = new ArrayList<>();
|
||||
int newCount = 0;
|
||||
int updateCount = 0;
|
||||
|
||||
for (CattleDataDTO dto : dataList) {
|
||||
// 检查是否存在重复数据(type + province + location + price)
|
||||
CattleData existingData = cattleDataRepository
|
||||
.findByTypeAndProvinceAndLocationAndPrice(
|
||||
dto.getType(),
|
||||
dto.getProvince(),
|
||||
dto.getLocation(),
|
||||
dto.getPrice()
|
||||
)
|
||||
.orElse(null);
|
||||
|
||||
if (existingData != null) {
|
||||
// 如果存在重复数据,只更新 up_time(保留原有的 create_time)
|
||||
existingData.setUpTime(LocalDateTime.now());
|
||||
savedList.add(cattleDataRepository.save(existingData));
|
||||
updateCount++;
|
||||
} else {
|
||||
// 如果不存在,创建新记录
|
||||
CattleData cattleData = new CattleData();
|
||||
cattleData.setType(dto.getType());
|
||||
cattleData.setProvince(dto.getProvince());
|
||||
cattleData.setLocation(dto.getLocation());
|
||||
cattleData.setPrice(dto.getPrice());
|
||||
if (dto.getCreateTime() != null) {
|
||||
cattleData.setCreateTime(dto.getCreateTime());
|
||||
}
|
||||
savedList.add(cattleDataRepository.save(cattleData));
|
||||
newCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportResult(savedList, newCount, updateCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.entity.CattleNational;
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
import com.example.cattletends.repository.CattleNationalRepository;
|
||||
import com.example.cattletends.repository.CattleProvinceRepository;
|
||||
import com.example.cattletends.service.CattleNationalService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 全国总量数据服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleNationalServiceImpl implements CattleNationalService {
|
||||
|
||||
private final CattleNationalRepository cattleNationalRepository;
|
||||
private final CattleProvinceRepository cattleProvinceRepository;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleNationalServiceImpl(CattleNationalRepository cattleNationalRepository,
|
||||
CattleProvinceRepository cattleProvinceRepository) {
|
||||
this.cattleNationalRepository = cattleNationalRepository;
|
||||
this.cattleProvinceRepository = cattleProvinceRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CattleNational getNationalData() {
|
||||
CattleNational nationalData = cattleNationalRepository.findFirstByOrderByIdAsc()
|
||||
.orElse(null);
|
||||
|
||||
// 如果不存在,自动计算并创建
|
||||
if (nationalData == null) {
|
||||
return calculateAndUpdateNationalData();
|
||||
}
|
||||
|
||||
return nationalData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CattleNational calculateAndUpdateNationalData() {
|
||||
// 从 cattleprovince 表中获取所有省份数据
|
||||
List<CattleProvince> allProvinces = cattleProvinceRepository.findAll();
|
||||
|
||||
// 初始化全国总量
|
||||
int nationalInventory23th = 0;
|
||||
int nationalSlaughter23th = 0;
|
||||
int nationalInventory24th = 0;
|
||||
int nationalSlaughter24th = 0;
|
||||
int nationalInventory25th = 0;
|
||||
int nationalSlaughter25th = 0;
|
||||
|
||||
// 累加所有省份的数据
|
||||
for (CattleProvince province : allProvinces) {
|
||||
if (province.getInventory23th() != null) {
|
||||
nationalInventory23th += province.getInventory23th();
|
||||
}
|
||||
if (province.getSlaughter23th() != null) {
|
||||
nationalSlaughter23th += province.getSlaughter23th();
|
||||
}
|
||||
if (province.getInventory24th() != null) {
|
||||
nationalInventory24th += province.getInventory24th();
|
||||
}
|
||||
if (province.getSlaughter24th() != null) {
|
||||
nationalSlaughter24th += province.getSlaughter24th();
|
||||
}
|
||||
if (province.getInventory25th() != null) {
|
||||
nationalInventory25th += province.getInventory25th();
|
||||
}
|
||||
if (province.getSlaughter25th() != null) {
|
||||
nationalSlaughter25th += province.getSlaughter25th();
|
||||
}
|
||||
}
|
||||
|
||||
// 查找是否已存在全国总量记录
|
||||
CattleNational nationalData = cattleNationalRepository.findFirstByOrderByIdAsc()
|
||||
.orElse(null);
|
||||
|
||||
if (nationalData != null) {
|
||||
// 更新现有记录
|
||||
nationalData.setNationalInventory23th(nationalInventory23th);
|
||||
nationalData.setNationalSlaughter23th(nationalSlaughter23th);
|
||||
nationalData.setNationalInventory24th(nationalInventory24th);
|
||||
nationalData.setNationalSlaughter24th(nationalSlaughter24th);
|
||||
nationalData.setNationalInventory25th(nationalInventory25th);
|
||||
nationalData.setNationalSlaughter25th(nationalSlaughter25th);
|
||||
return cattleNationalRepository.save(nationalData);
|
||||
} else {
|
||||
// 创建新记录
|
||||
nationalData = new CattleNational();
|
||||
nationalData.setNationalInventory23th(nationalInventory23th);
|
||||
nationalData.setNationalSlaughter23th(nationalSlaughter23th);
|
||||
nationalData.setNationalInventory24th(nationalInventory24th);
|
||||
nationalData.setNationalSlaughter24th(nationalSlaughter24th);
|
||||
nationalData.setNationalInventory25th(nationalInventory25th);
|
||||
nationalData.setNationalSlaughter25th(nationalSlaughter25th);
|
||||
return cattleNationalRepository.save(nationalData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.example.cattletends.service.impl;
|
||||
|
||||
import com.example.cattletends.dto.ProvinceDataDTO;
|
||||
import com.example.cattletends.entity.CattleProvince;
|
||||
import com.example.cattletends.repository.CattleProvinceRepository;
|
||||
import com.example.cattletends.service.CattleProvinceService;
|
||||
import com.example.cattletends.service.CattleNationalService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 省份数据服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CattleProvinceServiceImpl implements CattleProvinceService {
|
||||
|
||||
private final CattleProvinceRepository cattleProvinceRepository;
|
||||
private final CattleNationalService cattleNationalService;
|
||||
|
||||
/**
|
||||
* 构造器注入
|
||||
*/
|
||||
public CattleProvinceServiceImpl(CattleProvinceRepository cattleProvinceRepository,
|
||||
CattleNationalService cattleNationalService) {
|
||||
this.cattleProvinceRepository = cattleProvinceRepository;
|
||||
this.cattleNationalService = cattleNationalService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Map<String, Object> importProvinceData(List<ProvinceDataDTO> provinceDataList) {
|
||||
if (provinceDataList == null || provinceDataList.isEmpty()) {
|
||||
throw new RuntimeException("省份数据列表不能为空");
|
||||
}
|
||||
|
||||
int updateCount = 0;
|
||||
int createCount = 0;
|
||||
|
||||
// 更新或创建各省份数据
|
||||
for (ProvinceDataDTO dto : provinceDataList) {
|
||||
if (dto == null || dto.getProvince() == null || dto.getProvince().trim().isEmpty()) {
|
||||
continue; // 跳过无效数据
|
||||
}
|
||||
// 查找该省份是否已存在
|
||||
CattleProvince provinceData = cattleProvinceRepository.findByProvince(dto.getProvince())
|
||||
.orElse(null);
|
||||
|
||||
if (provinceData != null) {
|
||||
// 更新现有数据
|
||||
provinceData.setProvincePrice(dto.getProvincePrice());
|
||||
provinceData.setInventory23th(dto.getInventory23th());
|
||||
provinceData.setSlaughter23th(dto.getSlaughter23th());
|
||||
provinceData.setInventory24th(dto.getInventory24th());
|
||||
provinceData.setSlaughter24th(dto.getSlaughter24th());
|
||||
provinceData.setInventory25th(dto.getInventory25th());
|
||||
provinceData.setSlaughter25th(dto.getSlaughter25th());
|
||||
cattleProvinceRepository.save(provinceData);
|
||||
updateCount++;
|
||||
} else {
|
||||
// 创建新数据
|
||||
provinceData = new CattleProvince();
|
||||
provinceData.setProvince(dto.getProvince());
|
||||
provinceData.setProvincePrice(dto.getProvincePrice());
|
||||
provinceData.setInventory23th(dto.getInventory23th());
|
||||
provinceData.setSlaughter23th(dto.getSlaughter23th());
|
||||
provinceData.setInventory24th(dto.getInventory24th());
|
||||
provinceData.setSlaughter24th(dto.getSlaughter24th());
|
||||
provinceData.setInventory25th(dto.getInventory25th());
|
||||
provinceData.setSlaughter25th(dto.getSlaughter25th());
|
||||
cattleProvinceRepository.save(provinceData);
|
||||
createCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动从 cattleprovince 表计算并更新全国总量数据(从数据库表中读取所有省份数据并求和)
|
||||
// 只更新 cattlenational 表,不更新 cattleData 表
|
||||
com.example.cattletends.entity.CattleNational nationalData =
|
||||
cattleNationalService.calculateAndUpdateNationalData();
|
||||
|
||||
// 返回结果
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("createCount", createCount);
|
||||
result.put("updateCount", updateCount);
|
||||
result.put("totalCount", createCount + updateCount);
|
||||
|
||||
// 从全国总量数据中获取计算结果
|
||||
if (nationalData != null) {
|
||||
result.put("nationalInventory23th", nationalData.getNationalInventory23th());
|
||||
result.put("nationalSlaughter23th", nationalData.getNationalSlaughter23th());
|
||||
result.put("nationalInventory24th", nationalData.getNationalInventory24th());
|
||||
result.put("nationalSlaughter24th", nationalData.getNationalSlaughter24th());
|
||||
result.put("nationalInventory25th", nationalData.getNationalInventory25th());
|
||||
result.put("nationalSlaughter25th", nationalData.getNationalSlaughter25th());
|
||||
} else {
|
||||
result.put("nationalInventory23th", 0);
|
||||
result.put("nationalSlaughter23th", 0);
|
||||
result.put("nationalInventory24th", 0);
|
||||
result.put("nationalSlaughter24th", 0);
|
||||
result.put("nationalInventory25th", 0);
|
||||
result.put("nationalSlaughter25th", 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CattleProvince> getAllProvinceData(String province) {
|
||||
// 按省份均价降序排序
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "provincePrice");
|
||||
|
||||
// 如果提供了省份参数,则只查询该省份数据
|
||||
if (province != null && !province.trim().isEmpty()) {
|
||||
return cattleProvinceRepository.findByProvince(province.trim(), sort);
|
||||
}
|
||||
|
||||
// 否则返回所有省份数据,按省份均价降序排序
|
||||
return cattleProvinceRepository.findAll(sort);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CattleProvince getProvinceDataByName(String province) {
|
||||
return cattleProvinceRepository.findByProvince(province)
|
||||
.orElseThrow(() -> new RuntimeException("省份数据不存在:" + province));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user