From 5c8e828c6052454756092f4ce9bca6e6adaca66b Mon Sep 17 00:00:00 2001 From: shenquanyi Date: Fri, 5 Dec 2025 18:15:08 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=98=E5=B7=AE=E5=9B=9E=E9=80=80=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CattleDataController.java | 2 +- .../cattletends/entity/CattleData.java | 188 ++-- .../exception/GlobalExceptionHandler.java | 17 + .../repository/CattleDataRepository.java | 246 ++--- .../service/CattleDataService.java | 164 +-- .../service/impl/CattleDataServiceImpl.java | 946 +++++++++--------- 6 files changed, 790 insertions(+), 773 deletions(-) diff --git a/backend/src/main/java/com/example/cattletends/controller/CattleDataController.java b/backend/src/main/java/com/example/cattletends/controller/CattleDataController.java index fecac68..f790419 100644 --- a/backend/src/main/java/com/example/cattletends/controller/CattleDataController.java +++ b/backend/src/main/java/com/example/cattletends/controller/CattleDataController.java @@ -392,7 +392,7 @@ public class CattleDataController { @ResponseStatus(HttpStatus.CREATED) public Result createCattleData( @ApiParam(value = "牛只数据信息", required = true) - @Valid @RequestBody CattleDataDTO dto) { + @Valid @ModelAttribute CattleDataDTO dto) { CattleData data = cattleDataService.createCattleData(dto); return Result.success("创建成功", data); } diff --git a/backend/src/main/java/com/example/cattletends/entity/CattleData.java b/backend/src/main/java/com/example/cattletends/entity/CattleData.java index 17bedf5..1bff7bb 100644 --- a/backend/src/main/java/com/example/cattletends/entity/CattleData.java +++ b/backend/src/main/java/com/example/cattletends/entity/CattleData.java @@ -1,94 +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(); - } -} - +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(); + } +} + diff --git a/backend/src/main/java/com/example/cattletends/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/example/cattletends/exception/GlobalExceptionHandler.java index 61efcc2..1a541cb 100644 --- a/backend/src/main/java/com/example/cattletends/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/example/cattletends/exception/GlobalExceptionHandler.java @@ -4,6 +4,7 @@ import com.example.cattletends.common.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -37,6 +38,22 @@ public class GlobalExceptionHandler { return Result.error(400, "参数校验失败", errors); } + /** + * 处理请求体缺失或格式错误异常 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) { + String message = ex.getMessage(); + if (message != null && message.contains("Required request body is missing")) { + logger.warn("请求体缺失: {}", ex.getMessage()); + return Result.error(400, "请求体不能为空,请提供JSON格式的请求数据"); + } else { + logger.warn("请求体格式错误: {}", ex.getMessage()); + return Result.error(400, "请求体格式错误,请检查JSON格式是否正确"); + } + } + /** * 处理业务异常 */ diff --git a/backend/src/main/java/com/example/cattletends/repository/CattleDataRepository.java b/backend/src/main/java/com/example/cattletends/repository/CattleDataRepository.java index 2f70c41..eaf33ec 100644 --- a/backend/src/main/java/com/example/cattletends/repository/CattleDataRepository.java +++ b/backend/src/main/java/com/example/cattletends/repository/CattleDataRepository.java @@ -1,123 +1,123 @@ -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.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -/** - * 牛只数据Repository接口 - */ -@Repository -public interface CattleDataRepository extends JpaRepository { - - /** - * 根据省份查询牛只数据,按价格升序排序 - * - * @param province 省份 - * @param sort 排序规则 - * @return 牛只数据列表 - */ - List findByProvince(String province, Sort sort); - - /** - * 根据品种查询牛只数据,按价格升序排序 - * - * @param type 品种 - * @param sort 排序规则 - * @return 牛只数据列表 - */ - List findByType(String type, Sort sort); - - /** - * 根据品种、省份、产地、价格查询牛只数据(用于判断重复) - * 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException - * - * @param type 品种 - * @param province 省份 - * @param location 产地 - * @param price 价格 - * @return 第一条匹配的牛只数据(如果存在) - */ - Optional findFirstByTypeAndProvinceAndLocationAndPrice( - String type, String province, String location, BigDecimal price); - - /** - * 根据品种和产地查询牛只数据(用于判断重复) - * 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException - * - * @param type 品种 - * @param location 产地 - * @return 第一条匹配的牛只数据(如果存在) - */ - Optional findFirstByTypeAndLocation(String type, String location); - - /** - * 根据品种和产地查询所有匹配的牛只数据(用于删除重复记录) - * 使用 TRIM 函数来匹配,忽略前后空格 - * - * @param type 品种 - * @param location 产地 - * @return 所有匹配的牛只数据列表 - */ - @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE TRIM(c.type) = TRIM(?1) AND TRIM(c.location) = TRIM(?2)") - List findByTypeAndLocation(String type, String location); - - /** - * 获取所有不重复的省份列表 - * - * @return 省份列表 - */ - @org.springframework.data.jpa.repository.Query("SELECT DISTINCT c.province FROM CattleData c WHERE c.province IS NOT NULL AND c.province != ''") - List findAllDistinctProvinces(); - - /** - * 根据日期范围查询牛只数据(用于删除当天数据) - * - * @param startTime 开始时间(当天00:00:00) - * @param endTime 结束时间(当天23:59:59) - * @return 该日期范围内的所有数据 - */ - @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE c.createTime >= ?1 AND c.createTime <= ?2") - List findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime); - - /** - * 根据日期查询当天的牛只数据(用于查询接口) - * 使用DATE()函数比较日期部分,忽略时间部分 - * - * @param date 日期(只比较日期部分,忽略时间) - * @param sort 排序规则 - * @return 当天的数据列表 - */ - @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE DATE(c.createTime) = DATE(?1)") - List findByCreateTimeDate(LocalDate date, Sort sort); - - /** - * 根据省份和日期查询当天的牛只数据 - * - * @param province 省份 - * @param date 日期 - * @param sort 排序规则 - * @return 该省份当天的数据列表 - */ - @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE c.province = ?1 AND DATE(c.createTime) = DATE(?2)") - List findByProvinceAndCreateTimeDate(String province, LocalDate date, Sort sort); - - /** - * 根据品种和日期查询当天的牛只数据 - * - * @param type 品种 - * @param date 日期 - * @param sort 排序规则 - * @return 该品种当天的数据列表 - */ - @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE c.type = ?1 AND DATE(c.createTime) = DATE(?2)") - List findByTypeAndCreateTimeDate(String type, LocalDate date, Sort sort); -} - +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.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * 牛只数据Repository接口 + */ +@Repository +public interface CattleDataRepository extends JpaRepository { + + /** + * 根据省份查询牛只数据,按价格升序排序 + * + * @param province 省份 + * @param sort 排序规则 + * @return 牛只数据列表 + */ + List findByProvince(String province, Sort sort); + + /** + * 根据品种查询牛只数据,按价格升序排序 + * + * @param type 品种 + * @param sort 排序规则 + * @return 牛只数据列表 + */ + List findByType(String type, Sort sort); + + /** + * 根据品种、省份、产地、价格查询牛只数据(用于判断重复) + * 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException + * + * @param type 品种 + * @param province 省份 + * @param location 产地 + * @param price 价格 + * @return 第一条匹配的牛只数据(如果存在) + */ + Optional findFirstByTypeAndProvinceAndLocationAndPrice( + String type, String province, String location, BigDecimal price); + + /** + * 根据品种和产地查询牛只数据(用于判断重复) + * 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException + * + * @param type 品种 + * @param location 产地 + * @return 第一条匹配的牛只数据(如果存在) + */ + Optional findFirstByTypeAndLocation(String type, String location); + + /** + * 根据品种和产地查询所有匹配的牛只数据(用于删除重复记录) + * 使用 TRIM 函数来匹配,忽略前后空格 + * + * @param type 品种 + * @param location 产地 + * @return 所有匹配的牛只数据列表 + */ + @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE TRIM(c.type) = TRIM(?1) AND TRIM(c.location) = TRIM(?2)") + List findByTypeAndLocation(String type, String location); + + /** + * 获取所有不重复的省份列表 + * + * @return 省份列表 + */ + @org.springframework.data.jpa.repository.Query("SELECT DISTINCT c.province FROM CattleData c WHERE c.province IS NOT NULL AND c.province != ''") + List findAllDistinctProvinces(); + + /** + * 根据日期范围查询牛只数据(用于删除当天数据) + * + * @param startTime 开始时间(当天00:00:00) + * @param endTime 结束时间(当天23:59:59) + * @return 该日期范围内的所有数据 + */ + @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE c.createTime >= ?1 AND c.createTime <= ?2") + List findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime); + + /** + * 根据日期查询当天的牛只数据(用于查询接口) + * 使用DATE()函数比较日期部分,忽略时间部分 + * + * @param date 日期(只比较日期部分,忽略时间) + * @param sort 排序规则 + * @return 当天的数据列表 + */ + @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE DATE(c.createTime) = DATE(?1)") + List findByCreateTimeDate(LocalDate date, Sort sort); + + /** + * 根据省份和日期查询当天的牛只数据 + * + * @param province 省份 + * @param date 日期 + * @param sort 排序规则 + * @return 该省份当天的数据列表 + */ + @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE c.province = ?1 AND DATE(c.createTime) = DATE(?2)") + List findByProvinceAndCreateTimeDate(String province, LocalDate date, Sort sort); + + /** + * 根据品种和日期查询当天的牛只数据 + * + * @param type 品种 + * @param date 日期 + * @param sort 排序规则 + * @return 该品种当天的数据列表 + */ + @org.springframework.data.jpa.repository.Query("SELECT c FROM CattleData c WHERE c.type = ?1 AND DATE(c.createTime) = DATE(?2)") + List findByTypeAndCreateTimeDate(String type, LocalDate date, Sort sort); +} + diff --git a/backend/src/main/java/com/example/cattletends/service/CattleDataService.java b/backend/src/main/java/com/example/cattletends/service/CattleDataService.java index 5dd26ca..e9e055a 100644 --- a/backend/src/main/java/com/example/cattletends/service/CattleDataService.java +++ b/backend/src/main/java/com/example/cattletends/service/CattleDataService.java @@ -1,82 +1,82 @@ -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.time.LocalDate; -import java.util.List; -import java.util.Map; - -/** - * 牛只数据服务接口 - */ -public interface CattleDataService { - - /** - * 获取所有牛只数据 - * - * @param date 日期(可选,如果为null则查询今天的数据) - * @return 牛只数据列表 - */ - List getAllCattleData(LocalDate date); - - /** - * 根据省份获取牛只数据 - * - * @param province 省份 - * @param date 日期(可选,如果为null则查询今天的数据) - * @return 牛只数据列表 - */ - List getCattleDataByProvince(String province, LocalDate date); - - /** - * 根据品种获取牛只数据 - * - * @param type 品种 - * @param date 日期(可选,如果为null则查询今天的数据) - * @return 牛只数据列表 - */ - List getCattleDataByType(String type, LocalDate date); - - /** - * 根据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 dataList); -} - +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.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * 牛只数据服务接口 + */ +public interface CattleDataService { + + /** + * 获取所有牛只数据 + * + * @param date 日期(可选,如果为null则查询今天的数据) + * @return 牛只数据列表 + */ + List getAllCattleData(LocalDate date); + + /** + * 根据省份获取牛只数据 + * + * @param province 省份 + * @param date 日期(可选,如果为null则查询今天的数据) + * @return 牛只数据列表 + */ + List getCattleDataByProvince(String province, LocalDate date); + + /** + * 根据品种获取牛只数据 + * + * @param type 品种 + * @param date 日期(可选,如果为null则查询今天的数据) + * @return 牛只数据列表 + */ + List getCattleDataByType(String type, LocalDate date); + + /** + * 根据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 dataList); +} + diff --git a/backend/src/main/java/com/example/cattletends/service/impl/CattleDataServiceImpl.java b/backend/src/main/java/com/example/cattletends/service/impl/CattleDataServiceImpl.java index 3c3c180..b6b44f6 100644 --- a/backend/src/main/java/com/example/cattletends/service/impl/CattleDataServiceImpl.java +++ b/backend/src/main/java/com/example/cattletends/service/impl/CattleDataServiceImpl.java @@ -1,473 +1,473 @@ -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 com.example.cattletends.service.ProvinceDailyPriceService; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDate; -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; - private final ProvinceDailyPriceService provinceDailyPriceService; - private final com.example.cattletends.service.CattleProvinceService cattleProvinceService; - private final com.example.cattletends.repository.CattleProvinceRepository cattleProvinceRepository; - - /** - * 构造器注入 - */ - public CattleDataServiceImpl(CattleDataRepository cattleDataRepository, - ProvinceDailyPriceService provinceDailyPriceService, - com.example.cattletends.service.CattleProvinceService cattleProvinceService, - com.example.cattletends.repository.CattleProvinceRepository cattleProvinceRepository) { - this.cattleDataRepository = cattleDataRepository; - this.provinceDailyPriceService = provinceDailyPriceService; - this.cattleProvinceService = cattleProvinceService; - this.cattleProvinceRepository = cattleProvinceRepository; - } - - @Override - @Transactional - public List getAllCattleData(LocalDate date) { - // 如果date为null,使用今天 - LocalDate queryDate = date != null ? date : LocalDate.now(); - Sort sort = Sort.by(Sort.Direction.ASC, "price"); - - // 查询指定日期的数据 - List dataList = cattleDataRepository.findByCreateTimeDate(queryDate, sort); - return dataList; - } - - @Override - @Transactional - public List getCattleDataByProvince(String province, LocalDate date) { - // 如果date为null,使用今天 - LocalDate queryDate = date != null ? date : LocalDate.now(); - Sort sort = Sort.by(Sort.Direction.ASC, "price"); - - // 查询指定日期和省份的数据 - List dataList = cattleDataRepository.findByProvinceAndCreateTimeDate(province.trim(), queryDate, sort); - return dataList; - } - - @Override - @Transactional - public List getCattleDataByType(String type, LocalDate date) { - // 如果date为null,使用今天 - LocalDate queryDate = date != null ? date : LocalDate.now(); - Sort sort = Sort.by(Sort.Direction.ASC, "price"); - - // 查询指定日期和品种的数据 - List dataList = cattleDataRepository.findByTypeAndCreateTimeDate(type.trim(), queryDate, sort); - return dataList; - } - - /** - * 按 location 字段去重处理 - * 如果同一个 location 有多条记录,保留最新的一条(up_time最大),更新其他记录 - * @param dataList 原始数据列表 - * @param province 省份筛选条件(可选,用于重新查询时保持筛选) - * @param type 品种筛选条件(可选,用于重新查询时保持筛选) - */ - private List deduplicateByLocation(List dataList, String province, String type) { - if (dataList == null || dataList.isEmpty()) { - return dataList; - } - - System.out.println("[查询去重] 开始按 type + location 去重处理"); - System.out.println("[查询去重] 去重前记录数: " + dataList.size()); - - // 按 type + location 分组,找出每个 type + location 组合的最新记录 - java.util.Map typeLocationMap = new java.util.HashMap<>(); - java.util.List duplicatesToUpdate = new java.util.ArrayList<>(); - - for (CattleData item : dataList) { - String itemType = item.getType(); - String location = item.getLocation(); - if ((itemType == null || itemType.trim().isEmpty()) || - (location == null || location.trim().isEmpty())) { - continue; - } - - String trimmedType = itemType.trim(); - String trimmedLocation = location.trim(); - String key = trimmedType + "|" + trimmedLocation; // 使用 | 作为分隔符 - - if (!typeLocationMap.containsKey(key)) { - // 第一次遇到该 type + location 组合,直接添加 - typeLocationMap.put(key, item); - } else { - // 已存在该 type + location 组合,比较更新时间,保留最新的 - CattleData existing = typeLocationMap.get(key); - if (item.getUpTime() != null && existing.getUpTime() != null) { - if (item.getUpTime().isAfter(existing.getUpTime())) { - // 当前记录更新,标记旧记录需要更新 - duplicatesToUpdate.add(existing); - typeLocationMap.put(key, item); - } else { - // 现有记录更新,标记当前记录需要更新 - duplicatesToUpdate.add(item); - } - } else if (item.getUpTime() != null) { - // 当前记录有更新时间,保留它 - duplicatesToUpdate.add(existing); - typeLocationMap.put(key, item); - } else if (existing.getUpTime() != null) { - // 现有记录有更新时间,保留现有记录 - duplicatesToUpdate.add(item); - } else { - // 都没有更新时间,保留ID较小的(通常是最早创建的) - if (item.getId() < existing.getId()) { - duplicatesToUpdate.add(existing); - typeLocationMap.put(key, item); - } else { - duplicatesToUpdate.add(item); - } - } - } - } - - // 删除重复记录:只保留最新的记录,删除其他所有重复记录 - int deletedCount = 0; - for (CattleData duplicate : duplicatesToUpdate) { - String duplicateType = duplicate.getType() != null ? duplicate.getType().trim() : ""; - String duplicateLocation = duplicate.getLocation() != null ? duplicate.getLocation().trim() : ""; - String key = duplicateType + "|" + duplicateLocation; - CattleData latest = typeLocationMap.get(key); - if (latest != null && !latest.getId().equals(duplicate.getId())) { - // 删除重复记录,只保留最新的 - System.out.println("[查询去重] 删除重复记录: ID=" + duplicate.getId() + ", type=[" + duplicateType + "], location=[" + duplicateLocation + "]"); - cattleDataRepository.delete(duplicate); - deletedCount++; - } - } - - // 如果删除了重复记录,重新查询(保持原有的筛选条件) - if (deletedCount > 0) { - System.out.println("[查询去重] 删除了 " + deletedCount + " 条重复记录"); - // 重新查询数据(保持原有排序和筛选条件) - Sort sort = Sort.by(Sort.Direction.ASC, "price"); - if (type != null && !type.trim().isEmpty()) { - dataList = cattleDataRepository.findByType(type.trim(), sort); - } else if (province != null && !province.trim().isEmpty()) { - dataList = cattleDataRepository.findByProvince(province.trim(), sort); - } else { - dataList = cattleDataRepository.findAll(sort); - } - } - - // 最终去重:按 type + location 去重,每个 type + location 组合只保留一条(保留最新的) - java.util.Map finalMap = new java.util.LinkedHashMap<>(); - for (CattleData item : dataList) { - String itemType = item.getType(); - String location = item.getLocation(); - if ((itemType == null || itemType.trim().isEmpty()) || - (location == null || location.trim().isEmpty())) { - continue; - } - - String trimmedType = itemType.trim(); - String trimmedLocation = location.trim(); - String key = trimmedType + "|" + trimmedLocation; - - if (!finalMap.containsKey(key)) { - finalMap.put(key, item); - } else { - CattleData existing = finalMap.get(key); - if (item.getUpTime() != null && existing.getUpTime() != null) { - if (item.getUpTime().isAfter(existing.getUpTime())) { - finalMap.put(key, item); - } - } else if (item.getUpTime() != null) { - finalMap.put(key, item); - } else if (existing.getUpTime() != null) { - // 保持现有记录 - } else { - // 都没有更新时间,保留ID较小的 - if (item.getId() < existing.getId()) { - finalMap.put(key, item); - } - } - } - } - - dataList = new java.util.ArrayList<>(finalMap.values()); - System.out.println("[查询去重] 去重后记录数: " + dataList.size()); - System.out.println("[查询去重] 去重处理完成"); - - return dataList; - } - - @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 dataList) { - List savedList = new ArrayList<>(); - int newCount = 0; - int updateCount = 0; // 保留字段以兼容返回类型,但始终为0 - - // 确定导入日期:统一使用导入时的当前日期 - LocalDate importDate = LocalDate.now(); - LocalDateTime startOfDay = importDate.atStartOfDay(); // 当天 00:00:00 - LocalDateTime endOfDay = importDate.atTime(23, 59, 59, 999999999); // 当天 23:59:59.999999999 - - System.out.println("========== 开始导入数据,导入日期: " + importDate + " =========="); - - // 删除当天所有已存在的数据 - List todayData = cattleDataRepository.findByCreateTimeBetween(startOfDay, endOfDay); - if (todayData != null && !todayData.isEmpty()) { - System.out.println("========== 删除当天已存在的数据: " + todayData.size() + " 条 =========="); - cattleDataRepository.deleteAll(todayData); - } - - // 只新增,不做去重和更新 - LocalDateTime importDateTime = LocalDateTime.now(); // 统一使用导入时的当前时间 - - for (CattleDataDTO dto : dataList) { - // 验证必填字段 - String type = dto.getType() != null ? dto.getType().trim() : null; - String location = dto.getLocation() != null ? dto.getLocation().trim() : null; - - if (type == null || type.isEmpty() || location == null || location.isEmpty()) { - continue; // 跳过无效数据 - } - - // 创建新记录 - CattleData cattleData = new CattleData(); - cattleData.setType(type); - cattleData.setProvince(dto.getProvince() != null ? dto.getProvince().trim() : null); - cattleData.setLocation(location); - cattleData.setPrice(dto.getPrice()); - - // 统一设置create_time为导入时的当前时间 - cattleData.setCreateTime(importDateTime); - - CattleData saved = cattleDataRepository.save(cattleData); - savedList.add(saved); - newCount++; - System.out.println("[新增] 创建新记录: ID=" + saved.getId() + ", type=[" + type + "], location=[" + location + "], price=" + dto.getPrice()); - } - - System.out.println("========== 导入完成: 新增 " + newCount + " 条数据 =========="); - - // 导入完成后,计算当天省份的平均价格并更新省份每日均价 - System.out.println("========== 开始计算当天省份平均价格并更新每日均价 =========="); - calculateAndUpdateProvinceDailyPrices(importDate); - System.out.println("========== 省份平均价格计算完成 =========="); - - return new ImportResult(savedList, newCount, updateCount); - } - - /** - * 计算当天省份的平均价格并更新省份每日均价 - * 只计算当天导入的数据 - * - * @param importDate 导入日期 - */ - private void calculateAndUpdateProvinceDailyPrices(LocalDate importDate) { - try { - System.out.println("导入日期: " + importDate); - - // 获取所有15个省份(从 cattleprovince 表) - List allProvinces = cattleProvinceService.getAllProvinceData(null); - System.out.println("获取到 " + (allProvinces != null ? allProvinces.size() : 0) + " 个省份"); - - if (allProvinces == null || allProvinces.isEmpty()) { - System.out.println("没有找到省份数据,跳过省份平均价格计算"); - return; - } - - // 只查询当天导入的数据 - Sort sort = Sort.by(Sort.Direction.ASC, "price"); - List todayDataList = cattleDataRepository.findByCreateTimeDate(importDate, sort); - - if (todayDataList == null || todayDataList.isEmpty()) { - System.out.println("当天没有导入数据,跳过省份平均价格计算"); - return; - } - - System.out.println("当天导入的数据条数: " + todayDataList.size()); - - int updatedCount = 0; - int createdCount = 0; - - // 按省份分组,计算每个省份的平均价格 - java.util.Map> provinceStats = new java.util.HashMap<>(); - - for (CattleData data : todayDataList) { - if (data.getProvince() == null || data.getProvince().trim().isEmpty() || data.getPrice() == null) { - continue; - } - - String originalProvince = data.getProvince().trim(); - String canonicalProvince = resolveProvinceNameForMatch(originalProvince); - - if (provinceStats.containsKey(canonicalProvince)) { - java.util.Map existing = provinceStats.get(canonicalProvince); - java.math.BigDecimal existingSum = (java.math.BigDecimal) existing.get("sum"); - int existingCount = (Integer) existing.get("count"); - existingSum = existingSum.add(data.getPrice()); - existingCount++; - existing.put("sum", existingSum); - existing.put("count", existingCount); - } else { - java.util.Map stats = new java.util.HashMap<>(); - stats.put("sum", data.getPrice()); - stats.put("count", 1); - provinceStats.put(canonicalProvince, stats); - } - } - - // 计算每个省份的平均价格并更新 - for (java.util.Map.Entry> entry : provinceStats.entrySet()) { - String province = entry.getKey(); - java.util.Map stats = entry.getValue(); - java.math.BigDecimal totalSum = (java.math.BigDecimal) stats.get("sum"); - int totalCount = (Integer) stats.get("count"); - - // 计算平均值 - java.math.BigDecimal averagePrice = totalSum.divide( - java.math.BigDecimal.valueOf(totalCount), - 2, - java.math.RoundingMode.HALF_UP - ); - - System.out.println("省份: " + province + ", 当天数据条数: " + totalCount + ", 平均价格: " + averagePrice); - - // 更新 cattleprovince 表的 province_price 字段 - java.util.Optional cattleProvinceOpt = - cattleProvinceRepository.findFirstByProvince(province); - if (cattleProvinceOpt.isPresent()) { - com.example.cattletends.entity.CattleProvince cattleProvince = cattleProvinceOpt.get(); - cattleProvince.setProvincePrice(averagePrice); - cattleProvinceRepository.save(cattleProvince); - System.out.println("✓ 更新 cattleprovince 表的 province_price: " + province + ", 价格: " + averagePrice); - } - - // 更新或创建省份每日均价记录 - provinceDailyPriceService.saveOrUpdate(province, averagePrice, importDate); - updatedCount++; - System.out.println("✓ 更新省份每日均价: " + province + ", 日期: " + importDate + ", 价格: " + averagePrice); - } - - // 对于没有数据的省份,使用省份表中的省份均价(如果有) - for (com.example.cattletends.entity.CattleProvince province : allProvinces) { - String provinceName = province.getProvince(); - if (provinceName == null || provinceName.trim().isEmpty()) { - continue; - } - - String trimmedProvince = provinceName.trim(); - if (!provinceStats.containsKey(trimmedProvince)) { - // 没有当天数据,使用省份表中的省份均价 - if (province.getProvincePrice() != null) { - provinceDailyPriceService.saveOrUpdate(trimmedProvince, province.getProvincePrice(), importDate); - createdCount++; - System.out.println("✓ 创建省份每日均价(使用省份均价): " + trimmedProvince + ", 日期: " + importDate + ", 价格: " + province.getProvincePrice()); - } - } - } - - System.out.println("省份每日均价处理完成: 更新 " + updatedCount + " 条,创建 " + createdCount + " 条"); - - } catch (Exception e) { - System.err.println("计算省份平均价格失败: " + e.getMessage()); - e.printStackTrace(); - } - } - - /** - * 将牛只数据中的省份名称,转换为与 cattleprovince / province_daily_price 表中一致的标准省份名称 - * 例如:把“安徽省”“河北省”“新疆维吾尔自治区”等映射为“安徽”“河北”“新疆”等 - */ - private String resolveProvinceNameForMatch(String province) { - if (province == null) { - return null; - } - String trimmed = province.trim(); - - // 1. 先按原样查找 - java.util.Optional exact = - cattleProvinceRepository.findFirstByProvince(trimmed); - if (exact.isPresent()) { - return exact.get().getProvince(); - } - - // 2. 尝试去掉常见后缀(省、市、自治区、回族、壮族、维吾尔) - String shortName = trimmed - .replace("维吾尔", "") - .replace("回族", "") - .replace("壮族", "") - .replace("自治区", "") - .replace("省", "") - .replace("市", "") - .trim(); - - if (!shortName.equals(trimmed)) { - java.util.Optional shortMatch = - cattleProvinceRepository.findFirstByProvince(shortName); - if (shortMatch.isPresent()) { - return shortMatch.get().getProvince(); - } - } - - // 3. 找不到匹配时,退回原始名称(保证不会中断流程) - return trimmed; - } -} - +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 com.example.cattletends.service.ProvinceDailyPriceService; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +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; + private final ProvinceDailyPriceService provinceDailyPriceService; + private final com.example.cattletends.service.CattleProvinceService cattleProvinceService; + private final com.example.cattletends.repository.CattleProvinceRepository cattleProvinceRepository; + + /** + * 构造器注入 + */ + public CattleDataServiceImpl(CattleDataRepository cattleDataRepository, + ProvinceDailyPriceService provinceDailyPriceService, + com.example.cattletends.service.CattleProvinceService cattleProvinceService, + com.example.cattletends.repository.CattleProvinceRepository cattleProvinceRepository) { + this.cattleDataRepository = cattleDataRepository; + this.provinceDailyPriceService = provinceDailyPriceService; + this.cattleProvinceService = cattleProvinceService; + this.cattleProvinceRepository = cattleProvinceRepository; + } + + @Override + @Transactional + public List getAllCattleData(LocalDate date) { + // 如果date为null,使用今天 + LocalDate queryDate = date != null ? date : LocalDate.now(); + Sort sort = Sort.by(Sort.Direction.ASC, "price"); + + // 查询指定日期的数据 + List dataList = cattleDataRepository.findByCreateTimeDate(queryDate, sort); + return dataList; + } + + @Override + @Transactional + public List getCattleDataByProvince(String province, LocalDate date) { + // 如果date为null,使用今天 + LocalDate queryDate = date != null ? date : LocalDate.now(); + Sort sort = Sort.by(Sort.Direction.ASC, "price"); + + // 查询指定日期和省份的数据 + List dataList = cattleDataRepository.findByProvinceAndCreateTimeDate(province.trim(), queryDate, sort); + return dataList; + } + + @Override + @Transactional + public List getCattleDataByType(String type, LocalDate date) { + // 如果date为null,使用今天 + LocalDate queryDate = date != null ? date : LocalDate.now(); + Sort sort = Sort.by(Sort.Direction.ASC, "price"); + + // 查询指定日期和品种的数据 + List dataList = cattleDataRepository.findByTypeAndCreateTimeDate(type.trim(), queryDate, sort); + return dataList; + } + + /** + * 按 location 字段去重处理 + * 如果同一个 location 有多条记录,保留最新的一条(up_time最大),更新其他记录 + * @param dataList 原始数据列表 + * @param province 省份筛选条件(可选,用于重新查询时保持筛选) + * @param type 品种筛选条件(可选,用于重新查询时保持筛选) + */ + private List deduplicateByLocation(List dataList, String province, String type) { + if (dataList == null || dataList.isEmpty()) { + return dataList; + } + + System.out.println("[查询去重] 开始按 type + location 去重处理"); + System.out.println("[查询去重] 去重前记录数: " + dataList.size()); + + // 按 type + location 分组,找出每个 type + location 组合的最新记录 + java.util.Map typeLocationMap = new java.util.HashMap<>(); + java.util.List duplicatesToUpdate = new java.util.ArrayList<>(); + + for (CattleData item : dataList) { + String itemType = item.getType(); + String location = item.getLocation(); + if ((itemType == null || itemType.trim().isEmpty()) || + (location == null || location.trim().isEmpty())) { + continue; + } + + String trimmedType = itemType.trim(); + String trimmedLocation = location.trim(); + String key = trimmedType + "|" + trimmedLocation; // 使用 | 作为分隔符 + + if (!typeLocationMap.containsKey(key)) { + // 第一次遇到该 type + location 组合,直接添加 + typeLocationMap.put(key, item); + } else { + // 已存在该 type + location 组合,比较更新时间,保留最新的 + CattleData existing = typeLocationMap.get(key); + if (item.getUpTime() != null && existing.getUpTime() != null) { + if (item.getUpTime().isAfter(existing.getUpTime())) { + // 当前记录更新,标记旧记录需要更新 + duplicatesToUpdate.add(existing); + typeLocationMap.put(key, item); + } else { + // 现有记录更新,标记当前记录需要更新 + duplicatesToUpdate.add(item); + } + } else if (item.getUpTime() != null) { + // 当前记录有更新时间,保留它 + duplicatesToUpdate.add(existing); + typeLocationMap.put(key, item); + } else if (existing.getUpTime() != null) { + // 现有记录有更新时间,保留现有记录 + duplicatesToUpdate.add(item); + } else { + // 都没有更新时间,保留ID较小的(通常是最早创建的) + if (item.getId() < existing.getId()) { + duplicatesToUpdate.add(existing); + typeLocationMap.put(key, item); + } else { + duplicatesToUpdate.add(item); + } + } + } + } + + // 删除重复记录:只保留最新的记录,删除其他所有重复记录 + int deletedCount = 0; + for (CattleData duplicate : duplicatesToUpdate) { + String duplicateType = duplicate.getType() != null ? duplicate.getType().trim() : ""; + String duplicateLocation = duplicate.getLocation() != null ? duplicate.getLocation().trim() : ""; + String key = duplicateType + "|" + duplicateLocation; + CattleData latest = typeLocationMap.get(key); + if (latest != null && !latest.getId().equals(duplicate.getId())) { + // 删除重复记录,只保留最新的 + System.out.println("[查询去重] 删除重复记录: ID=" + duplicate.getId() + ", type=[" + duplicateType + "], location=[" + duplicateLocation + "]"); + cattleDataRepository.delete(duplicate); + deletedCount++; + } + } + + // 如果删除了重复记录,重新查询(保持原有的筛选条件) + if (deletedCount > 0) { + System.out.println("[查询去重] 删除了 " + deletedCount + " 条重复记录"); + // 重新查询数据(保持原有排序和筛选条件) + Sort sort = Sort.by(Sort.Direction.ASC, "price"); + if (type != null && !type.trim().isEmpty()) { + dataList = cattleDataRepository.findByType(type.trim(), sort); + } else if (province != null && !province.trim().isEmpty()) { + dataList = cattleDataRepository.findByProvince(province.trim(), sort); + } else { + dataList = cattleDataRepository.findAll(sort); + } + } + + // 最终去重:按 type + location 去重,每个 type + location 组合只保留一条(保留最新的) + java.util.Map finalMap = new java.util.LinkedHashMap<>(); + for (CattleData item : dataList) { + String itemType = item.getType(); + String location = item.getLocation(); + if ((itemType == null || itemType.trim().isEmpty()) || + (location == null || location.trim().isEmpty())) { + continue; + } + + String trimmedType = itemType.trim(); + String trimmedLocation = location.trim(); + String key = trimmedType + "|" + trimmedLocation; + + if (!finalMap.containsKey(key)) { + finalMap.put(key, item); + } else { + CattleData existing = finalMap.get(key); + if (item.getUpTime() != null && existing.getUpTime() != null) { + if (item.getUpTime().isAfter(existing.getUpTime())) { + finalMap.put(key, item); + } + } else if (item.getUpTime() != null) { + finalMap.put(key, item); + } else if (existing.getUpTime() != null) { + // 保持现有记录 + } else { + // 都没有更新时间,保留ID较小的 + if (item.getId() < existing.getId()) { + finalMap.put(key, item); + } + } + } + } + + dataList = new java.util.ArrayList<>(finalMap.values()); + System.out.println("[查询去重] 去重后记录数: " + dataList.size()); + System.out.println("[查询去重] 去重处理完成"); + + return dataList; + } + + @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 dataList) { + List savedList = new ArrayList<>(); + int newCount = 0; + int updateCount = 0; // 保留字段以兼容返回类型,但始终为0 + + // 确定导入日期:统一使用导入时的当前日期 + LocalDate importDate = LocalDate.now(); + LocalDateTime startOfDay = importDate.atStartOfDay(); // 当天 00:00:00 + LocalDateTime endOfDay = importDate.atTime(23, 59, 59, 999999999); // 当天 23:59:59.999999999 + + System.out.println("========== 开始导入数据,导入日期: " + importDate + " =========="); + + // 删除当天所有已存在的数据 + List todayData = cattleDataRepository.findByCreateTimeBetween(startOfDay, endOfDay); + if (todayData != null && !todayData.isEmpty()) { + System.out.println("========== 删除当天已存在的数据: " + todayData.size() + " 条 =========="); + cattleDataRepository.deleteAll(todayData); + } + + // 只新增,不做去重和更新 + LocalDateTime importDateTime = LocalDateTime.now(); // 统一使用导入时的当前时间 + + for (CattleDataDTO dto : dataList) { + // 验证必填字段 + String type = dto.getType() != null ? dto.getType().trim() : null; + String location = dto.getLocation() != null ? dto.getLocation().trim() : null; + + if (type == null || type.isEmpty() || location == null || location.isEmpty()) { + continue; // 跳过无效数据 + } + + // 创建新记录 + CattleData cattleData = new CattleData(); + cattleData.setType(type); + cattleData.setProvince(dto.getProvince() != null ? dto.getProvince().trim() : null); + cattleData.setLocation(location); + cattleData.setPrice(dto.getPrice()); + + // 统一设置create_time为导入时的当前时间 + cattleData.setCreateTime(importDateTime); + + CattleData saved = cattleDataRepository.save(cattleData); + savedList.add(saved); + newCount++; + System.out.println("[新增] 创建新记录: ID=" + saved.getId() + ", type=[" + type + "], location=[" + location + "], price=" + dto.getPrice()); + } + + System.out.println("========== 导入完成: 新增 " + newCount + " 条数据 =========="); + + // 导入完成后,计算当天省份的平均价格并更新省份每日均价 + System.out.println("========== 开始计算当天省份平均价格并更新每日均价 =========="); + calculateAndUpdateProvinceDailyPrices(importDate); + System.out.println("========== 省份平均价格计算完成 =========="); + + return new ImportResult(savedList, newCount, updateCount); + } + + /** + * 计算当天省份的平均价格并更新省份每日均价 + * 只计算当天导入的数据 + * + * @param importDate 导入日期 + */ + private void calculateAndUpdateProvinceDailyPrices(LocalDate importDate) { + try { + System.out.println("导入日期: " + importDate); + + // 获取所有15个省份(从 cattleprovince 表) + List allProvinces = cattleProvinceService.getAllProvinceData(null); + System.out.println("获取到 " + (allProvinces != null ? allProvinces.size() : 0) + " 个省份"); + + if (allProvinces == null || allProvinces.isEmpty()) { + System.out.println("没有找到省份数据,跳过省份平均价格计算"); + return; + } + + // 只查询当天导入的数据 + Sort sort = Sort.by(Sort.Direction.ASC, "price"); + List todayDataList = cattleDataRepository.findByCreateTimeDate(importDate, sort); + + if (todayDataList == null || todayDataList.isEmpty()) { + System.out.println("当天没有导入数据,跳过省份平均价格计算"); + return; + } + + System.out.println("当天导入的数据条数: " + todayDataList.size()); + + int updatedCount = 0; + int createdCount = 0; + + // 按省份分组,计算每个省份的平均价格 + java.util.Map> provinceStats = new java.util.HashMap<>(); + + for (CattleData data : todayDataList) { + if (data.getProvince() == null || data.getProvince().trim().isEmpty() || data.getPrice() == null) { + continue; + } + + String originalProvince = data.getProvince().trim(); + String canonicalProvince = resolveProvinceNameForMatch(originalProvince); + + if (provinceStats.containsKey(canonicalProvince)) { + java.util.Map existing = provinceStats.get(canonicalProvince); + java.math.BigDecimal existingSum = (java.math.BigDecimal) existing.get("sum"); + int existingCount = (Integer) existing.get("count"); + existingSum = existingSum.add(data.getPrice()); + existingCount++; + existing.put("sum", existingSum); + existing.put("count", existingCount); + } else { + java.util.Map stats = new java.util.HashMap<>(); + stats.put("sum", data.getPrice()); + stats.put("count", 1); + provinceStats.put(canonicalProvince, stats); + } + } + + // 计算每个省份的平均价格并更新 + for (java.util.Map.Entry> entry : provinceStats.entrySet()) { + String province = entry.getKey(); + java.util.Map stats = entry.getValue(); + java.math.BigDecimal totalSum = (java.math.BigDecimal) stats.get("sum"); + int totalCount = (Integer) stats.get("count"); + + // 计算平均值 + java.math.BigDecimal averagePrice = totalSum.divide( + java.math.BigDecimal.valueOf(totalCount), + 2, + java.math.RoundingMode.HALF_UP + ); + + System.out.println("省份: " + province + ", 当天数据条数: " + totalCount + ", 平均价格: " + averagePrice); + + // 更新 cattleprovince 表的 province_price 字段 + java.util.Optional cattleProvinceOpt = + cattleProvinceRepository.findFirstByProvince(province); + if (cattleProvinceOpt.isPresent()) { + com.example.cattletends.entity.CattleProvince cattleProvince = cattleProvinceOpt.get(); + cattleProvince.setProvincePrice(averagePrice); + cattleProvinceRepository.save(cattleProvince); + System.out.println("✓ 更新 cattleprovince 表的 province_price: " + province + ", 价格: " + averagePrice); + } + + // 更新或创建省份每日均价记录 + provinceDailyPriceService.saveOrUpdate(province, averagePrice, importDate); + updatedCount++; + System.out.println("✓ 更新省份每日均价: " + province + ", 日期: " + importDate + ", 价格: " + averagePrice); + } + + // 对于没有数据的省份,使用省份表中的省份均价(如果有) + for (com.example.cattletends.entity.CattleProvince province : allProvinces) { + String provinceName = province.getProvince(); + if (provinceName == null || provinceName.trim().isEmpty()) { + continue; + } + + String trimmedProvince = provinceName.trim(); + if (!provinceStats.containsKey(trimmedProvince)) { + // 没有当天数据,使用省份表中的省份均价 + if (province.getProvincePrice() != null) { + provinceDailyPriceService.saveOrUpdate(trimmedProvince, province.getProvincePrice(), importDate); + createdCount++; + System.out.println("✓ 创建省份每日均价(使用省份均价): " + trimmedProvince + ", 日期: " + importDate + ", 价格: " + province.getProvincePrice()); + } + } + } + + System.out.println("省份每日均价处理完成: 更新 " + updatedCount + " 条,创建 " + createdCount + " 条"); + + } catch (Exception e) { + System.err.println("计算省份平均价格失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 将牛只数据中的省份名称,转换为与 cattleprovince / province_daily_price 表中一致的标准省份名称 + * 例如:把“安徽省”“河北省”“新疆维吾尔自治区”等映射为“安徽”“河北”“新疆”等 + */ + private String resolveProvinceNameForMatch(String province) { + if (province == null) { + return null; + } + String trimmed = province.trim(); + + // 1. 先按原样查找 + java.util.Optional exact = + cattleProvinceRepository.findFirstByProvince(trimmed); + if (exact.isPresent()) { + return exact.get().getProvince(); + } + + // 2. 尝试去掉常见后缀(省、市、自治区、回族、壮族、维吾尔) + String shortName = trimmed + .replace("维吾尔", "") + .replace("回族", "") + .replace("壮族", "") + .replace("自治区", "") + .replace("省", "") + .replace("市", "") + .trim(); + + if (!shortName.equals(trimmed)) { + java.util.Optional shortMatch = + cattleProvinceRepository.findFirstByProvince(shortName); + if (shortMatch.isPresent()) { + return shortMatch.get().getProvince(); + } + } + + // 3. 找不到匹配时,退回原始名称(保证不会中断流程) + return trimmed; + } +} +