diff --git a/src/main/java/com/rj/controller/CpaBrandScoreStatisticsController.java b/src/main/java/com/rj/controller/CpaBrandScoreStatisticsController.java new file mode 100644 index 0000000..cc9ddc8 --- /dev/null +++ b/src/main/java/com/rj/controller/CpaBrandScoreStatisticsController.java @@ -0,0 +1,425 @@ +package com.rj.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.rj.entity.CpaBrandScoreStatistics; +import com.rj.scheduler.CpaBrandCoreScheduler; +import com.rj.service.ICpaBrandScoreStatisticsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 经销商品牌得分统计表 前端控制器 + * + * @author 李中华 + * @date 2025/1/15 + */ +@Slf4j +@RestController +@RequestMapping("/api/cpa-brand-score-statistics") +@Tag(name = "经销商品牌得分统计", description = "经销商品牌得分统计相关接口") +public class CpaBrandScoreStatisticsController { + + @Autowired + private ICpaBrandScoreStatisticsService cpaBrandScoreStatisticsService; + + @Autowired + private CpaBrandCoreScheduler cpaBrandCoreScheduler; + + /** + * 分页查询统计数据 + */ + @GetMapping("/list") + @Operation(summary = "分页查询统计数据", description = "根据条件分页查询经销商品牌得分统计数据") + public ResponseEntity> getStatisticsList( + @Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer current, + @Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size, + @Parameter(description = "经销商编码(模糊查询)") @RequestParam(required = false) String dealerCode, + @Parameter(description = "经销商名称(模糊查询)") @RequestParam(required = false) String dealerName, + @Parameter(description = "大区") @RequestParam(required = false) String bigArea, + @Parameter(description = "统计类型") @RequestParam(required = false) String statisticsType, + @Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + + log.info("分页查询统计数据,经销商编码={},经销商名称={},大区={},统计类型={},开始日期={},结束日期={}", + dealerCode, dealerName, bigArea, statisticsType, startDate, endDate); + + Map result = new HashMap<>(); + try { + Page page = new Page<>(current, size); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加查询条件 + if (dealerCode != null && !dealerCode.trim().isEmpty()) { + queryWrapper.like(CpaBrandScoreStatistics::getDealerCode, dealerCode); + } + if (dealerName != null && !dealerName.trim().isEmpty()) { + queryWrapper.like(CpaBrandScoreStatistics::getDealerName, dealerName); + } + if (bigArea != null && !bigArea.trim().isEmpty()) { + queryWrapper.eq(CpaBrandScoreStatistics::getBigArea, bigArea); + } + if (statisticsType != null && !statisticsType.trim().isEmpty()) { + queryWrapper.eq(CpaBrandScoreStatistics::getStatisticsType, statisticsType); + } + if (startDate != null) { + queryWrapper.ge(CpaBrandScoreStatistics::getStatisticsDate, startDate); + } + if (endDate != null) { + queryWrapper.le(CpaBrandScoreStatistics::getStatisticsDate, endDate); + } + + // 只查询未删除的记录 + queryWrapper.eq(CpaBrandScoreStatistics::getIsDeleted, 0); + + // 按平均得分百分比和总得分倒序排列 + queryWrapper.orderByDesc(CpaBrandScoreStatistics::getAverageScorePercentage) + .orderByDesc(CpaBrandScoreStatistics::getTotalScore); + + Page statisticsPage = cpaBrandScoreStatisticsService.page(page, queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", statisticsPage.getRecords()); + result.put("total", statisticsPage.getTotal()); + result.put("current", statisticsPage.getCurrent()); + result.put("size", statisticsPage.getSize()); + result.put("pages", statisticsPage.getPages()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("分页查询统计数据失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据条件查询统计数据(不分页) + */ + @GetMapping("/query") + @Operation(summary = "根据条件查询统计数据", description = "根据经销商编码、大区、统计类型等条件查询统计数据(不分页)") + public ResponseEntity> getList( + @Parameter(description = "经销商编码") @RequestParam(required = false) String dealerCode, + @Parameter(description = "大区") @RequestParam(required = false) String bigArea, + @Parameter(description = "统计类型") @RequestParam(required = false) String statisticsType, + @Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + + Map result = new HashMap<>(); + try { + List statisticsList = cpaBrandScoreStatisticsService.getByCondition( + dealerCode, bigArea, statisticsType, startDate, endDate); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", statisticsList); + result.put("total", statisticsList.size()); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("根据条件查询统计数据失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据ID查询统计数据 + */ + @GetMapping("/{id}") + @Operation(summary = "根据ID查询统计数据", description = "根据主键ID查询单条统计数据") + public ResponseEntity> getById( + @Parameter(description = "主键ID", required = true) @PathVariable String id) { + + Map result = new HashMap<>(); + try { + CpaBrandScoreStatistics statistics = cpaBrandScoreStatisticsService.getById(id); + if (statistics != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", statistics); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "数据不存在"); + return ResponseEntity.notFound().build(); + } + } catch (Exception e) { + log.error("根据ID查询统计数据失败", e); + result.put("success", false); + result.put("message", "查询失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 新增统计数据 + */ + @PostMapping("/add") + @Operation(summary = "新增统计数据", description = "新增一条经销商品牌得分统计数据") + public ResponseEntity> add( + @Parameter(description = "统计数据", required = true) @Valid @RequestBody CpaBrandScoreStatistics statistics) { + + Map result = new HashMap<>(); + try { + boolean success = cpaBrandScoreStatisticsService.save(statistics); + if (success) { + result.put("success", true); + result.put("message", "新增成功"); + result.put("data", statistics); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "新增失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + log.error("新增统计数据失败", e); + result.put("success", false); + result.put("message", "新增失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 修改统计数据 + */ + @PutMapping("/update") + @Operation(summary = "修改统计数据", description = "修改经销商品牌得分统计数据") + public ResponseEntity> update( + @Parameter(description = "统计数据", required = true) @Valid @RequestBody CpaBrandScoreStatistics statistics) { + + Map result = new HashMap<>(); + try { + boolean success = cpaBrandScoreStatisticsService.updateById(statistics); + if (success) { + result.put("success", true); + result.put("message", "修改成功"); + result.put("data", statistics); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "修改失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + log.error("修改统计数据失败", e); + result.put("success", false); + result.put("message", "修改失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 删除统计数据 + */ + @DeleteMapping("/{id}") + @Operation(summary = "删除统计数据", description = "根据ID删除经销商品牌得分统计数据") + public ResponseEntity> delete( + @Parameter(description = "主键ID", required = true) @PathVariable String id) { + + Map result = new HashMap<>(); + try { + boolean success = cpaBrandScoreStatisticsService.removeById(id); + if (success) { + result.put("success", true); + result.put("message", "删除成功"); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "删除失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + log.error("删除统计数据失败", e); + result.put("success", false); + result.put("message", "删除失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 获取经销商排名 + */ + @GetMapping("/ranking/dealer") + @Operation(summary = "获取经销商排名", description = "获取经销商品牌得分排名统计") + public ResponseEntity> getDealerRanking( + @Parameter(description = "统计类型") @RequestParam(required = false) String statisticsType, + @Parameter(description = "限制数量") @RequestParam(defaultValue = "10") Integer limit) { + + Map result = new HashMap<>(); + try { + List> rankingList = cpaBrandScoreStatisticsService.getDealerRanking(statisticsType, limit); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", rankingList); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("获取经销商排名失败", e); + result.put("success", false); + result.put("message", "查询失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 获取大区排名 + */ + @GetMapping("/ranking/big-area") + @Operation(summary = "获取大区排名", description = "获取大区品牌得分排名统计") + public ResponseEntity> getBigAreaRanking( + @Parameter(description = "统计类型") @RequestParam(required = false) String statisticsType) { + + Map result = new HashMap<>(); + try { + List> rankingList = cpaBrandScoreStatisticsService.getBigAreaRanking(statisticsType); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", rankingList); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("获取大区排名失败", e); + result.put("success", false); + result.put("message", "查询失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 获取统计概览 + */ + @GetMapping("/overview") + @Operation(summary = "获取统计概览", description = "获取经销商品牌得分统计概览数据") + public ResponseEntity> getOverview( + @Parameter(description = "统计类型") @RequestParam(required = false) String statisticsType, + @Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + + Map result = new HashMap<>(); + try { + Map overview = cpaBrandScoreStatisticsService.getStatisticsOverview(statisticsType, startDate, endDate); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", overview); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("获取统计概览失败", e); + result.put("success", false); + result.put("message", "查询失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 生成统计数据 + * # 触发日统计 + * POST /api/cpa-brand-score-statistics/trigger-scheduler?statisticsType=daily&date=2025-01-14 + * + * # 触发周统计 + * POST /api/cpa-brand-score-statistics/trigger-scheduler?statisticsType=weekly&startDate=2025-01-06&endDate=2025-01-12 + * + * # 触发月统计 + * POST /api/cpa-brand-score-statistics/trigger-scheduler?statisticsType=monthly&startDate=2024-12-01&endDate=2024-12-31 + * + * # 触发总计统计 + * POST /api/cpa-brand-score-statistics/trigger-scheduler?statisticsType=total + */ + @PostMapping("/generate") + @Operation(summary = "生成统计数据", description = "根据指定条件生成经销商品牌得分统计数据") + public ResponseEntity> generateStatistics( + @Parameter(description = "统计类型", required = true) @RequestParam String statisticsType, + @Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate, + @Parameter(description = "创建人") @RequestParam(defaultValue = "system") String createdBy) { + + Map result = new HashMap<>(); + try { + int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics(statisticsType, startDate, endDate, createdBy); + + result.put("success", true); + result.put("message", "生成统计数据成功"); + result.put("data", Map.of("generatedCount", count)); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("生成统计数据失败", e); + result.put("success", false); + result.put("message", "生成失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 手动触发调度器统计任务 + * + * @param statisticsType 统计类型:daily, weekly, monthly, total + * @param date 统计日期(可选,用于daily类型) + * @param startDate 开始日期(可选,用于weekly/monthly类型) + * @param endDate 结束日期(可选,用于weekly/monthly类型) + * @return 执行结果 + */ + @PostMapping("/trigger-scheduler") + @Operation(summary = "手动触发调度器统计任务", description = "手动触发调度器执行统计任务,支持daily、weekly、monthly、total类型") + public ResponseEntity> triggerScheduler( + @RequestParam String statisticsType, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { + + Map result = new HashMap<>(); + + try { + log.info("手动触发调度器统计任务,类型:{},日期:{},范围:{} - {}", statisticsType, date, startDate, endDate); + + int count = 0; + + if ("daily".equals(statisticsType)) { + if (date == null) { + date = LocalDate.now().minusDays(1); + } + count = cpaBrandCoreScheduler.manualGenerateStatistics(date, statisticsType); + } else if ("weekly".equals(statisticsType) || "monthly".equals(statisticsType)) { + if (startDate == null || endDate == null) { + throw new IllegalArgumentException("周统计或月统计需要提供开始日期和结束日期"); + } + count = cpaBrandCoreScheduler.manualGenerateStatisticsByRange(startDate, endDate, statisticsType); + } else if ("total".equals(statisticsType)) { + count = cpaBrandCoreScheduler.manualGenerateStatisticsByRange(null, null, statisticsType); + } else { + throw new IllegalArgumentException("不支持的统计类型:" + statisticsType); + } + + result.put("success", true); + result.put("message", "调度器统计任务执行成功"); + Map reMap = new HashMap<>(); + reMap.put("statisticsType", statisticsType); + reMap.put("generatedCount", count); + reMap.put("date", date); + reMap.put("startDate", startDate); + reMap.put("endDate", endDate); + result.put("data", reMap); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("手动触发调度器统计任务失败", e); + result.put("success", false); + result.put("message", "执行失败:" + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } +} diff --git a/src/main/java/com/rj/entity/CpaBrandScoreStatistics.java b/src/main/java/com/rj/entity/CpaBrandScoreStatistics.java new file mode 100644 index 0000000..66d3c03 --- /dev/null +++ b/src/main/java/com/rj/entity/CpaBrandScoreStatistics.java @@ -0,0 +1,151 @@ +package com.rj.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 经销商品牌得分统计表 + * + * @author 李中华 + * @date 2025/1/15 + */ +@Data +@TableName("cpa_brandcore_statistics") +public class CpaBrandScoreStatistics { + + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 经销商编码 + */ + @TableField("dealer_code") + private String dealerCode; + + /** + * 经销商名称 + */ + @TableField("dealer_name") + private String dealerName; + + /** + * 大区 + */ + @TableField("big_area") + private String bigArea; + + /** + * 统计日期(按日统计时使用) + */ + @TableField("statistics_date") + private LocalDate statisticsDate; + + /** + * 统计类型:total-总计,daily-按日,weekly-按周,monthly-按月 + */ + @TableField("statistics_type") + private String statisticsType; + + /** + * 总记录数 + */ + @TableField("total_records") + private Integer totalRecords; + + /** + * 总得分 + */ + @TableField("total_score") + private Integer totalScore; + + /** + * 平均得分百分比 + */ + @TableField("average_score_percentage") + private BigDecimal averageScorePercentage; + + /** + * 积极情感数量 + */ + @TableField("positive_count") + private Integer positiveCount; + + /** + * 中性情感数量 + */ + @TableField("neutral_count") + private Integer neutralCount; + + /** + * 消极情感数量 + */ + @TableField("negative_count") + private Integer negativeCount; + + /** + * 积极率百分比 + */ + @TableField("positive_rate") + private BigDecimal positiveRate; + + /** + * 中性率百分比 + */ + @TableField("neutral_rate") + private BigDecimal neutralRate; + + /** + * 消极率百分比 + */ + @TableField("negative_rate") + private BigDecimal negativeRate; + + /** + * 最早交互时间 + */ + @TableField("earliest_interaction") + private LocalDateTime earliestInteraction; + + /** + * 最晚交互时间 + */ + @TableField("latest_interaction") + private LocalDateTime latestInteraction; + + /** + * 创建时间 + */ + @TableField("created_at") + private LocalDateTime createdAt; + + /** + * 创建人 + */ + @TableField("created_by") + private String createdBy; + + /** + * 更新时间 + */ + @TableField("updated_at") + private LocalDateTime updatedAt; + + /** + * 更新人 + */ + @TableField("updated_by") + private String updatedBy; + + /** + * 是否删除(0-未删除,1-已删除) + */ + @TableField("is_deleted") + private Integer isDeleted; +} diff --git a/src/main/java/com/rj/mapper/CpaBrandScoreStatisticsMapper.java b/src/main/java/com/rj/mapper/CpaBrandScoreStatisticsMapper.java new file mode 100644 index 0000000..5801e22 --- /dev/null +++ b/src/main/java/com/rj/mapper/CpaBrandScoreStatisticsMapper.java @@ -0,0 +1,119 @@ +package com.rj.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.rj.entity.CpaBrandScoreStatistics; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * 经销商品牌得分统计表 Mapper 接口 + * + * @author 李中华 + * @date 2025/1/15 + */ +@Mapper +public interface CpaBrandScoreStatisticsMapper extends BaseMapper { + + /** + * 根据经销商编码和统计类型查询统计数据 + * + * @param dealerCode 经销商编码 + * @param statisticsType 统计类型 + * @return 统计数据列表 + */ + List selectByDealerCodeAndType(@Param("dealerCode") String dealerCode, + @Param("statisticsType") String statisticsType); + + /** + * 根据大区和统计类型查询统计数据 + * + * @param bigArea 大区 + * @param statisticsType 统计类型 + * @return 统计数据列表 + */ + List selectByBigAreaAndType(@Param("bigArea") String bigArea, + @Param("statisticsType") String statisticsType); + + /** + * 根据日期范围查询统计数据 + * + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param statisticsType 统计类型 + * @return 统计数据列表 + */ + List selectByDateRange(@Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate, + @Param("statisticsType") String statisticsType); + + /** + * 获取经销商排名统计 + * + * @param statisticsType 统计类型 + * @param limit 限制数量 + * @return 排名统计列表 + */ + List> selectDealerRanking(@Param("statisticsType") String statisticsType, + @Param("limit") Integer limit); + + /** + * 获取大区排名统计 + * + * @param statisticsType 统计类型 + * @return 大区排名统计列表 + */ + List> selectBigAreaRanking(@Param("statisticsType") String statisticsType); + + /** + * 批量插入统计数据 + * + * @param statisticsList 统计数据列表 + * @return 插入记录数 + */ + int batchInsert(@Param("statisticsList") List statisticsList); + + /** + * 根据条件删除统计数据 + * + * @param dealerCode 经销商编码 + * @param statisticsType 统计类型 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 删除记录数 + */ + int deleteByCondition(@Param("dealerCode") String dealerCode, + @Param("statisticsType") String statisticsType, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); + + /** + * 执行日统计SQL(方案1:按经销商和日期双重分组统计) + * + * @param startDateTime 开始时间 + * @param endDateTime 结束时间 + * @return 统计结果列表 + */ + List> executeDailyStatistics(@Param("startDateTime") String startDateTime, + @Param("endDateTime") String endDateTime); + + /** + * 执行日期范围统计SQL + * + * @param startDateTime 开始时间 + * @param endDateTime 结束时间 + * @return 统计结果列表 + */ + List> executeDateRangeStatistics(@Param("startDateTime") String startDateTime, + @Param("endDateTime") String endDateTime); + + /** + * 执行总计统计SQL + * + * @return 统计结果列表 + */ + List> executeTotalStatistics(); +} diff --git a/src/main/java/com/rj/service/ICpaBrandScoreStatisticsService.java b/src/main/java/com/rj/service/ICpaBrandScoreStatisticsService.java new file mode 100644 index 0000000..904e38c --- /dev/null +++ b/src/main/java/com/rj/service/ICpaBrandScoreStatisticsService.java @@ -0,0 +1,115 @@ +package com.rj.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.rj.entity.CpaBrandScoreStatistics; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * 经销商品牌得分统计表 服务类 + * + * @author 李中华 + * @date 2025/1/15 + */ +public interface ICpaBrandScoreStatisticsService extends IService { + + /** + * 根据经销商编码和统计类型查询统计数据 + * + * @param dealerCode 经销商编码 + * @param statisticsType 统计类型 + * @return 统计数据列表 + */ + List getByDealerCodeAndType(String dealerCode, String statisticsType); + + /** + * 根据大区和统计类型查询统计数据 + * + * @param bigArea 大区 + * @param statisticsType 统计类型 + * @return 统计数据列表 + */ + List getByBigAreaAndType(String bigArea, String statisticsType); + + /** + * 根据日期范围查询统计数据 + * + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param statisticsType 统计类型 + * @return 统计数据列表 + */ + List getByDateRange(LocalDate startDate, LocalDate endDate, String statisticsType); + + /** + * 获取经销商排名统计 + * + * @param statisticsType 统计类型 + * @param limit 限制数量 + * @return 排名统计列表 + */ + List> getDealerRanking(String statisticsType, Integer limit); + + /** + * 获取大区排名统计 + * + * @param statisticsType 统计类型 + * @return 大区排名统计列表 + */ + List> getBigAreaRanking(String statisticsType); + + /** + * 批量保存统计数据 + * + * @param statisticsList 统计数据列表 + * @return 是否成功 + */ + boolean batchSave(List statisticsList); + + /** + * 根据条件删除统计数据 + * + * @param dealerCode 经销商编码 + * @param statisticsType 统计类型 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 是否成功 + */ + boolean deleteByCondition(String dealerCode, String statisticsType, LocalDate startDate, LocalDate endDate); + + /** + * 生成并保存统计数据 + * + * @param statisticsType 统计类型 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param createdBy 创建人 + * @return 生成的记录数 + */ + int generateAndSaveStatistics(String statisticsType, LocalDate startDate, LocalDate endDate, String createdBy); + + /** + * 获取统计概览数据 + * + * @param statisticsType 统计类型 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 概览数据 + */ + Map getStatisticsOverview(String statisticsType, LocalDate startDate, LocalDate endDate); + + /** + * 根据条件查询统计数据 + * + * @param dealerCode 经销商编码 + * @param bigArea 大区 + * @param statisticsType 统计类型 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 统计数据列表 + */ + List getByCondition(String dealerCode, String bigArea, + String statisticsType, LocalDate startDate, LocalDate endDate); +} diff --git a/src/main/java/com/rj/service/impl/CpaBrandScoreStatisticsServiceImpl.java b/src/main/java/com/rj/service/impl/CpaBrandScoreStatisticsServiceImpl.java new file mode 100644 index 0000000..f2225d5 --- /dev/null +++ b/src/main/java/com/rj/service/impl/CpaBrandScoreStatisticsServiceImpl.java @@ -0,0 +1,339 @@ +package com.rj.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.rj.entity.CpaBrandScoreStatistics; +import com.rj.mapper.CpaBrandScoreStatisticsMapper; +import com.rj.service.ICpaBrandScoreStatisticsService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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; + +/** + * 经销商品牌得分统计表 服务实现类 + * + * @author 李中华 + * @date 2025/1/15 + */ +@Slf4j +@Service +public class CpaBrandScoreStatisticsServiceImpl extends ServiceImpl + implements ICpaBrandScoreStatisticsService { + + @Autowired + private CpaBrandScoreStatisticsMapper cpaBrandScoreStatisticsMapper; + + @Override + public List getByDealerCodeAndType(String dealerCode, String statisticsType) { + return cpaBrandScoreStatisticsMapper.selectByDealerCodeAndType(dealerCode, statisticsType); + } + + @Override + public List getByBigAreaAndType(String bigArea, String statisticsType) { + return cpaBrandScoreStatisticsMapper.selectByBigAreaAndType(bigArea, statisticsType); + } + + @Override + public List getByDateRange(LocalDate startDate, LocalDate endDate, String statisticsType) { + return cpaBrandScoreStatisticsMapper.selectByDateRange(startDate, endDate, statisticsType); + } + + @Override + public List> getDealerRanking(String statisticsType, Integer limit) { + return cpaBrandScoreStatisticsMapper.selectDealerRanking(statisticsType, limit); + } + + @Override + public List> getBigAreaRanking(String statisticsType) { + return cpaBrandScoreStatisticsMapper.selectBigAreaRanking(statisticsType); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean batchSave(List statisticsList) { + try { + if (statisticsList == null || statisticsList.isEmpty()) { + log.warn("批量保存统计数据为空"); + return false; + } + + // 设置创建时间 + LocalDateTime now = LocalDateTime.now(); + statisticsList.forEach(statistics -> { + if (statistics.getCreatedAt() == null) { + statistics.setCreatedAt(now); + } + if (statistics.getUpdatedAt() == null) { + statistics.setUpdatedAt(now); + } + if (statistics.getIsDeleted() == null) { + statistics.setIsDeleted(0); + } + }); + + int result = cpaBrandScoreStatisticsMapper.batchInsert(statisticsList); + log.info("批量保存统计数据成功,保存记录数:{}", result); + return result > 0; + } catch (Exception e) { + log.error("批量保存统计数据失败", e); + throw e; + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteByCondition(String dealerCode, String statisticsType, LocalDate startDate, LocalDate endDate) { + try { + int result = cpaBrandScoreStatisticsMapper.deleteByCondition(dealerCode, statisticsType, startDate, endDate); + log.info("根据条件删除统计数据成功,删除记录数:{}", result); + return result >= 0; + } catch (Exception e) { + log.error("根据条件删除统计数据失败", e); + throw e; + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int generateAndSaveStatistics(String statisticsType, LocalDate startDate, LocalDate endDate, String createdBy) { + try { + log.info("开始生成统计数据,统计类型:{},日期范围:{} - {}", statisticsType, startDate, endDate); + + // 先删除已存在的统计数据 + deleteByCondition(null, statisticsType, startDate, endDate); + + List> statisticsResults; + + // 根据统计类型执行不同的统计SQL + if ("daily".equals(statisticsType)) { + // 日统计:按经销商和日期双重分组 + String startDateTime = startDate.atStartOfDay().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + String endDateTime = startDate.atTime(23, 59, 59).format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + statisticsResults = cpaBrandScoreStatisticsMapper.executeDailyStatistics(startDateTime, endDateTime); + } else if ("weekly".equals(statisticsType) || "monthly".equals(statisticsType)) { + // 周统计或月统计:按日期范围统计 + String startDateTime = startDate.atStartOfDay().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + String endDateTime = endDate.atTime(23, 59, 59).format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + statisticsResults = cpaBrandScoreStatisticsMapper.executeDateRangeStatistics(startDateTime, endDateTime); + } else if ("total".equals(statisticsType)) { + // 总计统计:统计所有历史数据 + statisticsResults = cpaBrandScoreStatisticsMapper.executeTotalStatistics(); + } else { + throw new IllegalArgumentException("不支持的统计类型:" + statisticsType); + } + + // 转换为实体对象并保存 + List statisticsList = convertToStatisticsList(statisticsResults, endDate, statisticsType, createdBy); + boolean success = batchSave(statisticsList); + + if (success) { + log.info("统计数据生成完成,统计类型:{},生成记录数:{}", statisticsType, statisticsList.size()); + return statisticsList.size(); + } else { + log.error("统计数据保存失败,统计类型:{}", statisticsType); + return 0; + } + } catch (Exception e) { + log.error("生成统计数据失败", e); + throw e; + } + } + + @Override + public Map getStatisticsOverview(String statisticsType, LocalDate startDate, LocalDate endDate) { + Map overview = new HashMap<>(); + + try { + // 获取统计数据列表 + List statisticsList = getByDateRange(startDate, endDate, statisticsType); + + if (statisticsList.isEmpty()) { + overview.put("totalDealers", 0); + overview.put("totalRecords", 0); + overview.put("averageScore", 0.0); + overview.put("topDealer", null); + return overview; + } + + // 计算概览数据 + int totalDealers = (int) statisticsList.stream() + .map(CpaBrandScoreStatistics::getDealerCode) + .distinct() + .count(); + + int totalRecords = statisticsList.stream() + .mapToInt(CpaBrandScoreStatistics::getTotalRecords) + .sum(); + + double averageScore = statisticsList.stream() + .mapToDouble(s -> s.getAverageScorePercentage().doubleValue()) + .average() + .orElse(0.0); + + // 获取得分最高的经销商 + CpaBrandScoreStatistics topDealer = statisticsList.stream() + .max((s1, s2) -> s1.getAverageScorePercentage().compareTo(s2.getAverageScorePercentage())) + .orElse(null); + + overview.put("totalDealers", totalDealers); + overview.put("totalRecords", totalRecords); + overview.put("averageScore", averageScore); + overview.put("topDealer", topDealer); + overview.put("statisticsCount", statisticsList.size()); + + log.info("获取统计概览数据成功,经销商数:{},总记录数:{},平均得分:{}", + totalDealers, totalRecords, averageScore); + + } catch (Exception e) { + log.error("获取统计概览数据失败", e); + throw e; + } + + return overview; + } + + /** + * 根据条件查询统计数据(支持分页) + * + * @param dealerCode 经销商编码 + * @param bigArea 大区 + * @param statisticsType 统计类型 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 统计数据列表 + */ + public List getByCondition(String dealerCode, String bigArea, + String statisticsType, LocalDate startDate, LocalDate endDate) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + queryWrapper.eq(CpaBrandScoreStatistics::getIsDeleted, 0); + + if (dealerCode != null && !dealerCode.trim().isEmpty()) { + queryWrapper.eq(CpaBrandScoreStatistics::getDealerCode, dealerCode); + } + + if (bigArea != null && !bigArea.trim().isEmpty()) { + queryWrapper.eq(CpaBrandScoreStatistics::getBigArea, bigArea); + } + + if (statisticsType != null && !statisticsType.trim().isEmpty()) { + queryWrapper.eq(CpaBrandScoreStatistics::getStatisticsType, statisticsType); + } + + if (startDate != null) { + queryWrapper.ge(CpaBrandScoreStatistics::getStatisticsDate, startDate); + } + + if (endDate != null) { + queryWrapper.le(CpaBrandScoreStatistics::getStatisticsDate, endDate); + } + + queryWrapper.orderByDesc(CpaBrandScoreStatistics::getAverageScorePercentage) + .orderByDesc(CpaBrandScoreStatistics::getTotalScore); + + return list(queryWrapper); + } + + /** + * 将查询结果转换为统计实体列表 + * + * @param results 查询结果 + * @param statisticsDate 统计日期 + * @param statisticsType 统计类型 + * @param createdBy 创建人 + * @return 统计实体列表 + */ + private List convertToStatisticsList(List> results, + LocalDate statisticsDate, + String statisticsType, + String createdBy) { + List statisticsList = new ArrayList<>(); + LocalDateTime now = LocalDateTime.now(); + + for (Map row : results) { + CpaBrandScoreStatistics statistics = new CpaBrandScoreStatistics(); + + statistics.setId(java.util.UUID.randomUUID().toString()); + statistics.setDealerCode((String) row.get("dealer_code")); + statistics.setDealerName((String) row.get("dealer_name")); + statistics.setBigArea((String) row.get("big_area")); + + // 对于日统计,使用查询结果中的日期;其他统计类型使用传入的日期 + if ("daily".equals(statisticsType) && row.get("statistics_date") != null) { + if (row.get("statistics_date") instanceof java.sql.Date) { + statistics.setStatisticsDate(((java.sql.Date) row.get("statistics_date")).toLocalDate()); + } else { + statistics.setStatisticsDate(statisticsDate); + } + } else { + statistics.setStatisticsDate(statisticsDate); + } + + statistics.setStatisticsType(statisticsType); + + // 数值字段转换 + statistics.setTotalRecords(convertToInteger(row.get("daily_records"))); + statistics.setTotalScore(convertToInteger(row.get("daily_total_score"))); + statistics.setAverageScorePercentage(convertToBigDecimal(row.get("daily_average_score_percentage"))); + statistics.setPositiveCount(convertToInteger(row.get("daily_positive_count"))); + statistics.setNeutralCount(convertToInteger(row.get("daily_neutral_count"))); + statistics.setNegativeCount(convertToInteger(row.get("daily_negative_count"))); + statistics.setPositiveRate(convertToBigDecimal(row.get("daily_positive_rate"))); + statistics.setNeutralRate(convertToBigDecimal(row.get("daily_neutral_rate"))); + statistics.setNegativeRate(convertToBigDecimal(row.get("daily_negative_rate"))); + + // 时间字段转换 + statistics.setEarliestInteraction(convertToLocalDateTime(row.get("daily_earliest_time"))); + statistics.setLatestInteraction(convertToLocalDateTime(row.get("daily_latest_time"))); + + // 系统字段 + statistics.setCreatedAt(now); + statistics.setUpdatedAt(now); + statistics.setCreatedBy(createdBy != null ? createdBy : "system"); + statistics.setUpdatedBy(createdBy != null ? createdBy : "system"); + statistics.setIsDeleted(0); + + statisticsList.add(statistics); + } + + return statisticsList; + } + + /** + * 类型转换辅助方法 + */ + private Integer convertToInteger(Object value) { + if (value == null) return 0; + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return Integer.parseInt(value.toString()); + } + + private java.math.BigDecimal convertToBigDecimal(Object value) { + if (value == null) return java.math.BigDecimal.ZERO; + if (value instanceof Number) { + return java.math.BigDecimal.valueOf(((Number) value).doubleValue()); + } + return new java.math.BigDecimal(value.toString()); + } + + private LocalDateTime convertToLocalDateTime(Object value) { + if (value == null) return null; + if (value instanceof java.sql.Timestamp) { + return ((java.sql.Timestamp) value).toLocalDateTime(); + } + if (value instanceof java.sql.Date) { + return ((java.sql.Date) value).toLocalDate().atStartOfDay(); + } + return LocalDateTime.parse(value.toString()); + } +} diff --git a/src/main/resources/mapper/CpaBrandScoreStatisticsMapper.xml b/src/main/resources/mapper/CpaBrandScoreStatisticsMapper.xml new file mode 100644 index 0000000..458e2af --- /dev/null +++ b/src/main/resources/mapper/CpaBrandScoreStatisticsMapper.xml @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, dealer_code, dealer_name, big_area, statistics_date, statistics_type, + total_records, total_score, average_score_percentage, + positive_count, neutral_count, negative_count, + positive_rate, neutral_rate, negative_rate, + earliest_interaction, latest_interaction, + created_at, created_by, updated_at, updated_by, is_deleted + + + + + + + + + + + + + + + + + + + + INSERT INTO cpa_brandcore_statistics ( + id, dealer_code, dealer_name, big_area, statistics_date, statistics_type, + total_records, total_score, average_score_percentage, + positive_count, neutral_count, negative_count, + positive_rate, neutral_rate, negative_rate, + earliest_interaction, latest_interaction, + created_at, created_by, updated_at, updated_by, is_deleted + ) VALUES + + ( + #{item.id}, #{item.dealerCode}, #{item.dealerName}, #{item.bigArea}, + #{item.statisticsDate}, #{item.statisticsType}, + #{item.totalRecords}, #{item.totalScore}, #{item.averageScorePercentage}, + #{item.positiveCount}, #{item.neutralCount}, #{item.negativeCount}, + #{item.positiveRate}, #{item.neutralRate}, #{item.negativeRate}, + #{item.earliestInteraction}, #{item.latestInteraction}, + #{item.createdAt}, #{item.createdBy}, #{item.updatedAt}, #{item.updatedBy}, #{item.isDeleted} + ) + + ON DUPLICATE KEY UPDATE + total_records = VALUES(total_records), + total_score = VALUES(total_score), + average_score_percentage = VALUES(average_score_percentage), + positive_count = VALUES(positive_count), + neutral_count = VALUES(neutral_count), + negative_count = VALUES(negative_count), + positive_rate = VALUES(positive_rate), + neutral_rate = VALUES(neutral_rate), + negative_rate = VALUES(negative_rate), + earliest_interaction = VALUES(earliest_interaction), + latest_interaction = VALUES(latest_interaction), + updated_at = VALUES(updated_at), + updated_by = VALUES(updated_by) + + + + + DELETE FROM cpa_brandcore_statistics + WHERE is_deleted = 0 + + AND dealer_code = #{dealerCode} + + + AND statistics_type = #{statisticsType} + + + AND statistics_date >= #{startDate} + + + AND statistics_date <= #{endDate} + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/rj/scheduler/CustomerProfileStatisticSchedulerIntegrationTest.java b/src/test/java/com/rj/scheduler/CustomerProfileStatisticSchedulerIntegrationTest.java new file mode 100644 index 0000000..62fa0db --- /dev/null +++ b/src/test/java/com/rj/scheduler/CustomerProfileStatisticSchedulerIntegrationTest.java @@ -0,0 +1,90 @@ +package com.rj.scheduler; + +import com.rj.entity.CustomerProfileAnalysis; +import com.rj.service.biz.ICustomerProfileAnalysisService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * CustomerProfileStatisticScheduler 集成测试 + * 测试真实数据库操作 + * + * @author 李中华 + * @date 2025/1/3 + */ +@SpringBootTest +@ActiveProfiles("test") +class CustomerProfileStatisticSchedulerIntegrationTest { + + @Autowired + private CustomerProfileMockInsertDataScheduler scheduler; + + @Autowired + private ICustomerProfileAnalysisService customerProfileAnalysisService; + + @Test + void testRealDatabaseOperation() { + // 准备测试数据 + LocalDateTime testDate = LocalDateTime.of(2025, 9, 9, 10, 0, 0); + + // 执行数据生成 + int count = scheduler.generateCustomerProfileDataForDate(testDate); + + // 验证结果 + assertTrue(count > 0, "应该生成至少一条记录"); + assertTrue(count <= 50, "生成记录数不应超过50条"); + + // 验证数据是否保存到数据库 + List savedRecords = customerProfileAnalysisService.list(); + assertFalse(savedRecords.isEmpty(), "数据库中应该有保存的记录"); + + // 验证最新生成的记录 + CustomerProfileAnalysis latestRecord = savedRecords.get(savedRecords.size() - 1); + assertNotNull(latestRecord.getId(), "记录ID不应为空"); + assertNotNull(latestRecord.getClientName(), "客户姓名不应为空"); + assertNotNull(latestRecord.getDealerName(), "经销商门店名称不应为空"); + assertNotNull(latestRecord.getProjectName(), "项目名称不应为空"); + assertNotNull(latestRecord.getSalesPersonName(), "销售顾问姓名不应为空"); + assertNotNull(latestRecord.getNotes(), "备注不应为空"); + + // 验证情感分析字段 + assertNotNull(latestRecord.getEntityBrand(), "品牌情感不应为空"); + assertNotNull(latestRecord.getEntityPrice(), "价格情感不应为空"); + assertNotNull(latestRecord.getEntityPower(), "动力情感不应为空"); + + System.out.println("成功生成并保存了 " + count + " 条记录"); + System.out.println("最新记录详情:"); + System.out.println("客户姓名: " + latestRecord.getClientName()); + System.out.println("门店: " + latestRecord.getDealerName()); + System.out.println("项目: " + latestRecord.getProjectName()); + System.out.println("销售顾问: " + latestRecord.getSalesPersonName()); + System.out.println("备注: " + latestRecord.getNotes()); + } + + @Test + void testManualGenerateData() { + // 准备测试数据 + LocalDateTime testDate = LocalDateTime.of(2025, 1, 3, 15, 30, 0); + + // 执行手动数据生成 + int count = scheduler.manualGenerateData(testDate); + + // 验证结果 + assertTrue(count > 0, "手动生成应该返回成功记录数"); + + // 验证数据保存 + List savedRecords = customerProfileAnalysisService.list(); + assertFalse(savedRecords.isEmpty(), "数据库中应该有保存的记录"); + + System.out.println("手动生成成功,保存了 " + savedRecords + " 条记录"); + } + + +} diff --git a/src/test/java/com/rj/scheduler/CustomerProfileStatisticSchedulerTest.java b/src/test/java/com/rj/scheduler/CustomerProfileStatisticSchedulerTest.java new file mode 100644 index 0000000..86230d4 --- /dev/null +++ b/src/test/java/com/rj/scheduler/CustomerProfileStatisticSchedulerTest.java @@ -0,0 +1,162 @@ +package com.rj.scheduler; + +import com.rj.entity.CustomerProfileAnalysis; +import com.rj.service.biz.ICustomerProfileAnalysisService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * CustomerProfileStatisticScheduler 测试类 + * + * @author 李中华 + * @date 2025/1/3 + */ +@ExtendWith(MockitoExtension.class) +class CustomerProfileStatisticSchedulerTest { + + @Mock + private ICustomerProfileAnalysisService customerProfileAnalysisService; + + @InjectMocks + private CustomerProfileMockInsertDataScheduler scheduler; + + @Test + void testGenerateCustomerProfileDataForDate() { + // 准备测试数据 + LocalDateTime testDate = LocalDateTime.of(2025, 8, 3, 10, 0, 0); + + // 模拟Service返回成功 + when(customerProfileAnalysisService.saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class))) + .thenReturn(true); + + // 执行测试 + int result = scheduler.generateCustomerProfileDataForDate(testDate); + + // 验证结果 + assertTrue(result > 0, "应该生成至少一条记录"); + assertTrue(result <= 50, "生成记录数不应超过50条"); + + // 验证Service被调用 + verify(customerProfileAnalysisService, atLeastOnce()) + .saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class)); + } + + @Test + void testCustomerProfileAnalysisDataIntegrity() { + // 准备测试数据 + LocalDateTime testDate = LocalDateTime.of(2025, 1, 3, 10, 0, 0); + + // 模拟Service返回成功 + when(customerProfileAnalysisService.saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class))) + .thenAnswer(invocation -> { + CustomerProfileAnalysis analysis = invocation.getArgument(0); + + // 验证新字段不为空 + assertNotNull(analysis.getDealerName(), "经销商门店名称不应为空"); + assertNotNull(analysis.getProjectId(), "项目ID不应为空"); + assertNotNull(analysis.getProjectName(), "项目名称不应为空"); + assertNotNull(analysis.getSalesPersonId(), "销售顾问ID不应为空"); + assertNotNull(analysis.getSalesPersonName(), "销售顾问姓名不应为空"); + + // 验证字段格式 + assertTrue(analysis.getDealerName().length() > 0, "经销商门店名称长度应大于0"); + assertTrue(analysis.getProjectName().length() > 0, "项目名称长度应大于0"); + assertTrue(analysis.getSalesPersonName().length() > 0, "销售顾问姓名长度应大于0"); + + return true; + }); + + // 执行测试 + int result = scheduler.generateCustomerProfileDataForDate(testDate); + + // 验证结果 + assertTrue(result > 0, "应该生成至少一条记录"); + } + + @Test + void testManualGenerateData() { + // 准备测试数据 + LocalDateTime testDate = LocalDateTime.of(2025, 1, 3, 10, 0, 0); + + // 模拟Service返回成功 + when(customerProfileAnalysisService.saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class))) + .thenReturn(true); + + // 执行测试 + int result = scheduler.manualGenerateData(testDate); + + // 验证结果 + assertTrue(result > 0, "手动生成应该返回成功记录数"); + + // 验证Service被调用 + verify(customerProfileAnalysisService, atLeastOnce()) + .saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class)); + } + + @Test + void testGenerateCustomerProfileDataForDateWithException() { + // 准备测试数据 + LocalDateTime testDate = LocalDateTime.of(2025, 1, 3, 10, 0, 0); + + // 模拟Service抛出异常 + when(customerProfileAnalysisService.saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class))) + .thenThrow(new RuntimeException("数据库连接失败")); + + // 执行测试并验证异常 + assertThrows(RuntimeException.class, () -> { + scheduler.generateCustomerProfileDataForDate(testDate); + }); + } + + @Test + void testDataGenerationWithMockService() { + // 准备测试数据 + LocalDateTime testDate = LocalDateTime.of(2025, 1, 3, 10, 0, 0); + + // 模拟Service返回成功,并验证数据内容 + when(customerProfileAnalysisService.saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class))) + .thenAnswer(invocation -> { + CustomerProfileAnalysis analysis = invocation.getArgument(0); + + // 验证所有新字段都有值 + assertNotNull(analysis.getDealerName(), "经销商门店名称不应为空"); + assertNotNull(analysis.getProjectId(), "项目ID不应为空"); + assertNotNull(analysis.getProjectName(), "项目名称不应为空"); + assertNotNull(analysis.getSalesPersonId(), "销售顾问ID不应为空"); + assertNotNull(analysis.getSalesPersonName(), "销售顾问姓名不应为空"); + + // 验证情感分析字段 + assertNotNull(analysis.getEntityBrand(), "品牌情感不应为空"); + assertNotNull(analysis.getEntityPrice(), "价格情感不应为空"); + assertNotNull(analysis.getEntityPower(), "动力情感不应为空"); + assertNotNull(analysis.getEntitySafety(), "安全情感不应为空"); + + // 验证备注字段包含新信息 + assertNotNull(analysis.getNotes(), "备注不应为空"); + assertTrue(analysis.getNotes().contains(analysis.getDealerName()), "备注应包含门店名称"); + assertTrue(analysis.getNotes().contains(analysis.getProjectName()), "备注应包含项目名称"); + assertTrue(analysis.getNotes().contains(analysis.getSalesPersonName()), "备注应包含销售顾问姓名"); + + return true; + }); + + // 执行测试 + int result = scheduler.generateCustomerProfileDataForDate(testDate); + + // 验证结果 + assertTrue(result > 0, "应该生成至少一条记录"); + + // 验证Service被调用 + verify(customerProfileAnalysisService, atLeastOnce()) + .saveCustomerProfileAnalysis(any(CustomerProfileAnalysis.class)); + } +}