定时器 ,周期性的统计分析品牌维度

This commit is contained in:
spllzh
2025-09-21 16:53:07 +08:00
parent cd72a3680c
commit 004ba02996
26 changed files with 118 additions and 207 deletions

View File

@@ -112,5 +112,6 @@ public class PasswordUtil {

View File

@@ -96,5 +96,6 @@ public class ServiceManager {

View File

@@ -74,5 +74,6 @@ public class AliyunConfig {

View File

@@ -195,5 +195,6 @@ public class AuthController {

View File

@@ -334,5 +334,6 @@ public class MenuController {

View File

@@ -304,5 +304,6 @@ public class RoleController {

View File

@@ -310,5 +310,6 @@ public class UserRoleController {

View File

@@ -78,3 +78,4 @@ public class DifyWorkflowResponseDto {

View File

@@ -22,3 +22,4 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper<CustomerProfil

View File

@@ -61,5 +61,6 @@ public class LoginResponse {

View File

@@ -80,5 +80,6 @@ public class AudioStatisticsScheduler {

View File

@@ -33,7 +33,7 @@ public class CpaBrandCoreScheduler {
log.info("开始执行经销商品牌得分日统计任务...");
LocalDate yesterday = LocalDate.now().minusDays(1);
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("daily", yesterday, yesterday, "system");
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("daily", yesterday, yesterday, "systemScheduler");
log.info("经销商品牌得分日统计任务执行完成,统计日期:{},生成记录数:{}",
yesterday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), count);
@@ -44,21 +44,17 @@ public class CpaBrandCoreScheduler {
/**
* 每周一凌晨4点执行周统计任务
* 统计上周的数据
* 统计上周的数据(服务层自动计算上周时间范围)
*/
@Scheduled(cron = "0 0 4 * * MON")
public void generateWeeklyStatistics() {
try {
log.info("开始执行经销商品牌得分周统计任务...");
LocalDate lastWeekStart = LocalDate.now().minusWeeks(1).with(java.time.DayOfWeek.MONDAY);
LocalDate lastWeekEnd = lastWeekStart.plusDays(6);
// 周统计的时间范围由服务层自动计算这里传递null即可
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("weekly", null, null, "systemCpaBrandCoreScheduler");
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("weekly", lastWeekStart, lastWeekEnd, "system");
log.info("经销商品牌得分周统计任务执行完成,统计周期:{} - {},生成记录数:{}",
lastWeekStart.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
lastWeekEnd.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), count);
log.info("经销商品牌得分周统计任务执行完成,生成记录数:{}", count);
} catch (Exception e) {
log.error("经销商品牌得分周统计任务执行失败", e);
}
@@ -66,21 +62,17 @@ public class CpaBrandCoreScheduler {
/**
* 每月1号凌晨5点执行月统计任务
* 统计上个月的数据
* 统计上个月的数据(服务层自动计算上月时间范围)
*/
@Scheduled(cron = "0 0 5 1 * ?")
public void generateMonthlyStatistics() {
try {
log.info("开始执行经销商品牌得分月统计任务...");
LocalDate lastMonthStart = LocalDate.now().minusMonths(1).withDayOfMonth(1);
LocalDate lastMonthEnd = lastMonthStart.withDayOfMonth(lastMonthStart.lengthOfMonth());
// 月统计的时间范围由服务层自动计算这里传递null即可
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("monthly", null, null, "systemCpaBrandCoreScheduler");
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("monthly", lastMonthStart, lastMonthEnd, "system");
log.info("经销商品牌得分月统计任务执行完成,统计周期:{} - {},生成记录数:{}",
lastMonthStart.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
lastMonthEnd.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), count);
log.info("经销商品牌得分月统计任务执行完成,生成记录数:{}", count);
} catch (Exception e) {
log.error("经销商品牌得分月统计任务执行失败", e);
}
@@ -95,7 +87,7 @@ public class CpaBrandCoreScheduler {
try {
log.info("开始执行经销商品牌得分总计统计任务...");
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("total", null, null, "system");
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics("total", null, null, "systemCpaBrandCoreScheduler");
log.info("经销商品牌得分总计统计任务执行完成,生成记录数:{}", count);
} catch (Exception e) {
@@ -113,7 +105,7 @@ public class CpaBrandCoreScheduler {
public int manualGenerateStatistics(LocalDate date, String statisticsType) {
try {
log.info("手动执行经销商品牌得分统计任务,统计日期:{},统计类型:{}", date, statisticsType);
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics(statisticsType, date, date, "manual");
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics(statisticsType, date, date, "manualCpaBrandCoreScheduler");
log.info("手动经销商品牌得分统计任务执行完成,统计日期:{},统计类型:{},生成记录数:{}",
date, statisticsType, count);
return count;
@@ -126,17 +118,27 @@ public class CpaBrandCoreScheduler {
/**
* 手动触发日期范围统计任务
*
* @param startDate 开始日期
* @param endDate 结束日期
* @param startDate 开始日期对于weekly/monthly类型此参数将被忽略服务层自动计算
* @param endDate 结束日期对于weekly/monthly类型此参数将被忽略服务层自动计算
* @param statisticsType 统计类型
* @return 生成的记录数
*/
public int manualGenerateStatisticsByRange(LocalDate startDate, LocalDate endDate, String statisticsType) {
try {
log.info("手动执行经销商品牌得分统计任务,统计范围:{} - {},统计类型:{}", startDate, endDate, statisticsType);
if ("weekly".equals(statisticsType) || "monthly".equals(statisticsType)) {
log.info("手动执行经销商品牌得分统计任务,统计类型:{}(时间范围由服务层自动计算)", statisticsType);
} else {
log.info("手动执行经销商品牌得分统计任务,统计范围:{} - {},统计类型:{}", startDate, endDate, statisticsType);
}
int count = cpaBrandScoreStatisticsService.generateAndSaveStatistics(statisticsType, startDate, endDate, "manual");
log.info("手动经销商品牌得分统计任务执行完成,统计范围:{} - {},统计类型:{},生成记录数:{}",
startDate, endDate, statisticsType, count);
if ("weekly".equals(statisticsType) || "monthly".equals(statisticsType)) {
log.info("手动经销商品牌得分统计任务执行完成,统计类型:{},生成记录数:{}", statisticsType, count);
} else {
log.info("手动经销商品牌得分统计任务执行完成,统计范围:{} - {},统计类型:{},生成记录数:{}",
startDate, endDate, statisticsType, count);
}
return count;
} catch (Exception e) {
log.error("手动经销商品牌得分统计任务执行失败,统计范围:{} - {},统计类型:{}", startDate, endDate, statisticsType, e);

View File

@@ -134,18 +134,23 @@ public class CustomerProfileMockInsertDataScheduler {
public void generateDailyCustomerProfileData() {
try {
log.info("开始执行客户画像数据生成任务...");
// 生成昨天的数据
LocalDateTime yesterday = LocalDateTime.now().minusDays(1);
int count = generateCustomerProfileDataForDate(yesterday);
log.info("客户画像数据生成任务执行完成,生成日期:{},生成记录数:{}",
yesterday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), count);
// 生成今天的数据
LocalDateTime today = LocalDateTime.now().minusDays(0);
int todayCount = generateCustomerProfileDataForDate(today);
// 生成昨天的数据
LocalDateTime yesterday = LocalDateTime.now().minusDays(1);
int count = generateCustomerProfileDataForDate(yesterday);
// 生成前天天的数据
LocalDateTime yesterday2 = LocalDateTime.now().minusDays(2);
int count2 = generateCustomerProfileDataForDate(yesterday2);
log.info("客户画像数据生成任务执行完成,生成日期:{},生成记录数:{}",
yesterday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), count);
log.info("客户画像数据生成任务执行完成,生成日期:{},生成记录数:{}",
today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), todayCount);
} catch (Exception e) {

View File

@@ -43,3 +43,4 @@ public interface ICustomerProfileAnalysisService extends IService<CustomerProfil

View File

@@ -78,3 +78,4 @@ public class CustomerProfileAnalysisServiceImpl extends ServiceImpl<CustomerProf

View File

@@ -106,36 +106,81 @@ public class CpaBrandScoreStatisticsServiceImpl extends ServiceImpl<CpaBrandScor
public int generateAndSaveStatistics(String statisticsType, LocalDate startDate, LocalDate endDate, String createdBy) {
try {
log.info("开始生成统计数据,统计类型:{},日期范围:{} - {}", statisticsType, startDate, endDate);
// 先删除已存在的统计数据
deleteByCondition(null, statisticsType, startDate, endDate);
List<Map<String, Object>> statisticsResults;
// 根据统计类型执行不同的统计SQL
if ("daily".equals(statisticsType)) {
// 日统计:按经销商和日期双重分组
// 日统计:使用传入的日期,按经销商和日期双重分组
if (startDate == null) {
throw new IllegalArgumentException("日统计需要提供统计日期");
}
// 先删除已存在的统计数据
deleteByCondition(null, statisticsType, startDate, startDate);
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"));
} else if ("weekly".equals(statisticsType)) {
// 周统计:自动计算上周的开始和结束时间
LocalDate lastWeekStart = LocalDate.now().minusWeeks(1).with(java.time.DayOfWeek.MONDAY);
LocalDate lastWeekEnd = lastWeekStart.plusDays(6);
log.info("周统计自动计算时间范围:{} - {}", lastWeekStart, lastWeekEnd);
// 先删除已存在的统计数据
deleteByCondition(null, statisticsType, lastWeekStart, lastWeekEnd);
String startDateTime = lastWeekStart.atStartOfDay().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String endDateTime = lastWeekEnd.atTime(23, 59, 59).format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
statisticsResults = cpaBrandScoreStatisticsMapper.executeDateRangeStatistics(startDateTime, endDateTime);
} else if ("monthly".equals(statisticsType)) {
// 月统计:自动计算上个月的开始和结束时间
LocalDate lastMonthStart = LocalDate.now().minusMonths(1).withDayOfMonth(1);
LocalDate lastMonthEnd = lastMonthStart.withDayOfMonth(lastMonthStart.lengthOfMonth());
log.info("月统计自动计算时间范围:{} - {}", lastMonthStart, lastMonthEnd);
// 先删除已存在的统计数据
deleteByCondition(null, statisticsType, lastMonthStart, lastMonthEnd);
String startDateTime = lastMonthStart.atStartOfDay().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String endDateTime = lastMonthEnd.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)) {
// 总计统计:统计所有历史数据
// 先删除已存在的统计数据
deleteByCondition(null, statisticsType, null, null);
statisticsResults = cpaBrandScoreStatisticsMapper.executeTotalStatistics();
} else {
throw new IllegalArgumentException("不支持的统计类型:" + statisticsType);
}
// 转换为实体对象并保存
List<CpaBrandScoreStatistics> statisticsList = convertToStatisticsList(statisticsResults, endDate, statisticsType, createdBy);
LocalDate statisticsDate;
if ("weekly".equals(statisticsType)) {
// 周统计使用上周的结束日期作为统计日期
statisticsDate = LocalDate.now().minusWeeks(1).with(java.time.DayOfWeek.MONDAY).plusDays(6);
} else if ("monthly".equals(statisticsType)) {
// 月统计使用上月的最后一天作为统计日期
statisticsDate = LocalDate.now().minusMonths(1).withDayOfMonth(1).withDayOfMonth(
LocalDate.now().minusMonths(1).withDayOfMonth(1).lengthOfMonth());
} else {
// 日统计和总计统计使用传入的日期
statisticsDate = endDate;
}
List<CpaBrandScoreStatistics> statisticsList = convertToStatisticsList(statisticsResults, statisticsDate, statisticsType, createdBy);
boolean success = batchSave(statisticsList);
if (success) {
log.info("统计数据生成完成,统计类型:{},生成记录数:{}", statisticsType, statisticsList.size());
log.info("统计数据生成完成,统计类型:{}统计日期:{}生成记录数:{}", statisticsType, statisticsDate, statisticsList.size());
return statisticsList.size();
} else {
log.error("统计数据保存失败,统计类型:{}", statisticsType);

View File

@@ -43,5 +43,6 @@ public interface IMenuService extends IService<Menu> {

View File

@@ -43,5 +43,6 @@ public interface IUserRoleService extends IService<UserRole> {

View File

@@ -47,5 +47,6 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM

View File

@@ -47,5 +47,6 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR

View File

@@ -47,5 +47,6 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i

View File

@@ -57,5 +57,6 @@ spring:

View File

@@ -32,7 +32,7 @@ class CustomerProfileStatisticSchedulerIntegrationTest {
@Test
void testRealDatabaseOperation() {
// 准备测试数据
LocalDateTime testDate = LocalDateTime.of(2025, 9, 9, 10, 0, 0);
LocalDateTime testDate = LocalDateTime.of(2025, 7, 8, 10, 0, 0);
// 执行数据生成
int count = scheduler.generateCustomerProfileDataForDate(testDate);
@@ -59,7 +59,7 @@ class CustomerProfileStatisticSchedulerIntegrationTest {
assertNotNull(latestRecord.getEntityPrice(), "价格情感不应为空");
assertNotNull(latestRecord.getEntityPower(), "动力情感不应为空");
System.out.println("成功生成并保存了 " + count + " 条记录");
System.out.println("成功生成并保存了 " + count + " 条记录"+" , 累计总数记录数: "+savedRecords.size());
System.out.println("最新记录详情:");
System.out.println("客户姓名: " + latestRecord.getClientName());
System.out.println("门店: " + latestRecord.getDealerName());

View File

@@ -1,162 +0,0 @@
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));
}
}