还差回退机制
This commit is contained in:
@@ -392,7 +392,7 @@ public class CattleDataController {
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Result<CattleData> createCattleData(
|
||||
@ApiParam(value = "牛只数据信息", required = true)
|
||||
@Valid @RequestBody CattleDataDTO dto) {
|
||||
@Valid @ModelAttribute CattleDataDTO dto) {
|
||||
CattleData data = cattleDataService.createCattleData(dto);
|
||||
return Result.success("创建成功", data);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Void> 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格式是否正确");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
|
||||
@@ -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<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);
|
||||
|
||||
/**
|
||||
* 根据品种、省份、产地、价格查询牛只数据(用于判断重复)
|
||||
* 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException
|
||||
*
|
||||
* @param type 品种
|
||||
* @param province 省份
|
||||
* @param location 产地
|
||||
* @param price 价格
|
||||
* @return 第一条匹配的牛只数据(如果存在)
|
||||
*/
|
||||
Optional<CattleData> findFirstByTypeAndProvinceAndLocationAndPrice(
|
||||
String type, String province, String location, BigDecimal price);
|
||||
|
||||
/**
|
||||
* 根据品种和产地查询牛只数据(用于判断重复)
|
||||
* 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException
|
||||
*
|
||||
* @param type 品种
|
||||
* @param location 产地
|
||||
* @return 第一条匹配的牛只数据(如果存在)
|
||||
*/
|
||||
Optional<CattleData> 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<CattleData> 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<String> 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<CattleData> 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<CattleData> 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<CattleData> 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<CattleData> 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<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);
|
||||
|
||||
/**
|
||||
* 根据品种、省份、产地、价格查询牛只数据(用于判断重复)
|
||||
* 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException
|
||||
*
|
||||
* @param type 品种
|
||||
* @param province 省份
|
||||
* @param location 产地
|
||||
* @param price 价格
|
||||
* @return 第一条匹配的牛只数据(如果存在)
|
||||
*/
|
||||
Optional<CattleData> findFirstByTypeAndProvinceAndLocationAndPrice(
|
||||
String type, String province, String location, BigDecimal price);
|
||||
|
||||
/**
|
||||
* 根据品种和产地查询牛只数据(用于判断重复)
|
||||
* 注意:使用 findFirstBy,避免因为存在多条相同记录导致 NonUniqueResultException
|
||||
*
|
||||
* @param type 品种
|
||||
* @param location 产地
|
||||
* @return 第一条匹配的牛只数据(如果存在)
|
||||
*/
|
||||
Optional<CattleData> 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<CattleData> 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<String> 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<CattleData> 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<CattleData> 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<CattleData> 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<CattleData> findByTypeAndCreateTimeDate(String type, LocalDate date, Sort sort);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CattleData> getAllCattleData(LocalDate date);
|
||||
|
||||
/**
|
||||
* 根据省份获取牛只数据
|
||||
*
|
||||
* @param province 省份
|
||||
* @param date 日期(可选,如果为null则查询今天的数据)
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getCattleDataByProvince(String province, LocalDate date);
|
||||
|
||||
/**
|
||||
* 根据品种获取牛只数据
|
||||
*
|
||||
* @param type 品种
|
||||
* @param date 日期(可选,如果为null则查询今天的数据)
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> 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<CattleDataDTO> 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<CattleData> getAllCattleData(LocalDate date);
|
||||
|
||||
/**
|
||||
* 根据省份获取牛只数据
|
||||
*
|
||||
* @param province 省份
|
||||
* @param date 日期(可选,如果为null则查询今天的数据)
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> getCattleDataByProvince(String province, LocalDate date);
|
||||
|
||||
/**
|
||||
* 根据品种获取牛只数据
|
||||
*
|
||||
* @param type 品种
|
||||
* @param date 日期(可选,如果为null则查询今天的数据)
|
||||
* @return 牛只数据列表
|
||||
*/
|
||||
List<CattleData> 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<CattleDataDTO> dataList);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CattleData> getAllCattleData(LocalDate date) {
|
||||
// 如果date为null,使用今天
|
||||
LocalDate queryDate = date != null ? date : LocalDate.now();
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "price");
|
||||
|
||||
// 查询指定日期的数据
|
||||
List<CattleData> dataList = cattleDataRepository.findByCreateTimeDate(queryDate, sort);
|
||||
return dataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<CattleData> getCattleDataByProvince(String province, LocalDate date) {
|
||||
// 如果date为null,使用今天
|
||||
LocalDate queryDate = date != null ? date : LocalDate.now();
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "price");
|
||||
|
||||
// 查询指定日期和省份的数据
|
||||
List<CattleData> dataList = cattleDataRepository.findByProvinceAndCreateTimeDate(province.trim(), queryDate, sort);
|
||||
return dataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<CattleData> getCattleDataByType(String type, LocalDate date) {
|
||||
// 如果date为null,使用今天
|
||||
LocalDate queryDate = date != null ? date : LocalDate.now();
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "price");
|
||||
|
||||
// 查询指定日期和品种的数据
|
||||
List<CattleData> dataList = cattleDataRepository.findByTypeAndCreateTimeDate(type.trim(), queryDate, sort);
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 location 字段去重处理
|
||||
* 如果同一个 location 有多条记录,保留最新的一条(up_time最大),更新其他记录
|
||||
* @param dataList 原始数据列表
|
||||
* @param province 省份筛选条件(可选,用于重新查询时保持筛选)
|
||||
* @param type 品种筛选条件(可选,用于重新查询时保持筛选)
|
||||
*/
|
||||
private List<CattleData> deduplicateByLocation(List<CattleData> 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<String, CattleData> typeLocationMap = new java.util.HashMap<>();
|
||||
java.util.List<CattleData> 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<String, CattleData> 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<CattleDataDTO> dataList) {
|
||||
List<CattleData> 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<CattleData> 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<com.example.cattletends.entity.CattleProvince> 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<CattleData> 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<String, java.util.Map<String, Object>> 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<String, Object> 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<String, Object> stats = new java.util.HashMap<>();
|
||||
stats.put("sum", data.getPrice());
|
||||
stats.put("count", 1);
|
||||
provinceStats.put(canonicalProvince, stats);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算每个省份的平均价格并更新
|
||||
for (java.util.Map.Entry<String, java.util.Map<String, Object>> entry : provinceStats.entrySet()) {
|
||||
String province = entry.getKey();
|
||||
java.util.Map<String, Object> 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<com.example.cattletends.entity.CattleProvince> 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<com.example.cattletends.entity.CattleProvince> 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<com.example.cattletends.entity.CattleProvince> 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<CattleData> getAllCattleData(LocalDate date) {
|
||||
// 如果date为null,使用今天
|
||||
LocalDate queryDate = date != null ? date : LocalDate.now();
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "price");
|
||||
|
||||
// 查询指定日期的数据
|
||||
List<CattleData> dataList = cattleDataRepository.findByCreateTimeDate(queryDate, sort);
|
||||
return dataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<CattleData> getCattleDataByProvince(String province, LocalDate date) {
|
||||
// 如果date为null,使用今天
|
||||
LocalDate queryDate = date != null ? date : LocalDate.now();
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "price");
|
||||
|
||||
// 查询指定日期和省份的数据
|
||||
List<CattleData> dataList = cattleDataRepository.findByProvinceAndCreateTimeDate(province.trim(), queryDate, sort);
|
||||
return dataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<CattleData> getCattleDataByType(String type, LocalDate date) {
|
||||
// 如果date为null,使用今天
|
||||
LocalDate queryDate = date != null ? date : LocalDate.now();
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "price");
|
||||
|
||||
// 查询指定日期和品种的数据
|
||||
List<CattleData> dataList = cattleDataRepository.findByTypeAndCreateTimeDate(type.trim(), queryDate, sort);
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 location 字段去重处理
|
||||
* 如果同一个 location 有多条记录,保留最新的一条(up_time最大),更新其他记录
|
||||
* @param dataList 原始数据列表
|
||||
* @param province 省份筛选条件(可选,用于重新查询时保持筛选)
|
||||
* @param type 品种筛选条件(可选,用于重新查询时保持筛选)
|
||||
*/
|
||||
private List<CattleData> deduplicateByLocation(List<CattleData> 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<String, CattleData> typeLocationMap = new java.util.HashMap<>();
|
||||
java.util.List<CattleData> 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<String, CattleData> 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<CattleDataDTO> dataList) {
|
||||
List<CattleData> 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<CattleData> 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<com.example.cattletends.entity.CattleProvince> 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<CattleData> 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<String, java.util.Map<String, Object>> 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<String, Object> 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<String, Object> stats = new java.util.HashMap<>();
|
||||
stats.put("sum", data.getPrice());
|
||||
stats.put("count", 1);
|
||||
provinceStats.put(canonicalProvince, stats);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算每个省份的平均价格并更新
|
||||
for (java.util.Map.Entry<String, java.util.Map<String, Object>> entry : provinceStats.entrySet()) {
|
||||
String province = entry.getKey();
|
||||
java.util.Map<String, Object> 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<com.example.cattletends.entity.CattleProvince> 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<com.example.cattletends.entity.CattleProvince> 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<com.example.cattletends.entity.CattleProvince> shortMatch =
|
||||
cattleProvinceRepository.findFirstByProvince(shortName);
|
||||
if (shortMatch.isPresent()) {
|
||||
return shortMatch.get().getProvince();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 找不到匹配时,退回原始名称(保证不会中断流程)
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user