package com.ruoyi.business.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.business.dto.TimingTaskDto;
import com.ruoyi.business.entity.TimingTask;
import com.ruoyi.business.mapper.InspectionTaskMapper;
import com.ruoyi.business.mapper.TimingTaskMapper;
import com.ruoyi.business.service.TimingTaskService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.system.mapper.SysUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import java.time.*;
import java.util.*;
import java.util.stream.Collectors;
/**
*
* 定时巡检任务表 服务实现类
*
*
* @author ld
* @since 2025-06-30
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class TimingTaskServiceImpl extends ServiceImpl implements TimingTaskService {
private final TimingTaskMapper timingTaskMapper;
private final InspectionTaskMapper inspectionTaskMapper;
private final TimingTaskScheduler timingTaskScheduler;
private final SysUserMapper sysUserMapper;
@Override
public IPage selectTimingTaskList(Page page, TimingTask timingTask) {
// 1. 先分页查询定时任务数据
IPage taskPage = timingTaskMapper.selectPage(page, null);
// 2. 如果没有数据,直接返回空分页
if (taskPage.getRecords().isEmpty()) {
return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
}
// 3. 收集所有需要查询的用户ID
Set userIds = new HashSet<>();
// 收集登记人ID
taskPage.getRecords().forEach(task -> {
if (task.getRegistrantId() != null) {
userIds.add(task.getRegistrantId());
}
});
// 收集巡检人ID(多个ID以逗号分隔)
taskPage.getRecords().forEach(task -> {
if (StringUtils.isNotBlank(task.getInspectorIds())) {
Arrays.stream(task.getInspectorIds().split(","))
.filter(StringUtils::isNotBlank)
.map(Long::valueOf)
.forEach(userIds::add);
}
});
// 4. 批量查询用户信息
Map userNickNameMap = new HashMap<>();
if (!userIds.isEmpty()) {
List users = sysUserMapper.selectBatchIds(userIds);
users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
}
// 5. 转换为DTO
List dtoList = taskPage.getRecords().stream().map(task -> {
TimingTaskDto dto = new TimingTaskDto();
// 复制基本属性
BeanUtils.copyProperties(task, dto);
// 设置登记人昵称
if (task.getRegistrantId() != null) {
dto.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "未知用户"));
}
// 设置巡检人昵称列表
if (StringUtils.isNotBlank(task.getInspectorIds())) {
List inspectorNickNames = Arrays.stream(task.getInspectorIds().split(","))
.filter(StringUtils::isNotBlank)
.map(idStr -> {
Long id = Long.valueOf(idStr);
return userNickNameMap.getOrDefault(id, "未知用户");
})
.toList();
dto.setInspector(inspectorNickNames.toString());
}
return dto;
}).collect(Collectors.toList());
// 6. 构建返回的分页对象
Page resultPage = new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
resultPage.setRecords(dtoList);
return resultPage;
}
@Override
@Transactional
public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
TimingTask timingTask = new TimingTask();
BeanUtils.copyProperties(timingTaskDto, timingTask);
// 设置创建人信息和默认值
if (Objects.isNull(timingTaskDto.getId())) {
timingTask.setRegistrationDate(LocalDate.now());
timingTask.setActive(true);
// 计算首次执行时间
LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
timingTask.setNexExecutionTime(firstExecutionTime);
int result = timingTaskMapper.insert(timingTask);
if (result > 0) {
// 新增成功后添加到调度器
timingTaskScheduler.scheduleTimingTask(timingTask);
}
return result;
} else {
int result = timingTaskMapper.updateById(timingTask);
if (result > 0) {
// 更新成功后重新调度任务
timingTaskScheduler.rescheduleTimingTask(timingTask);
}
return result;
}
}
private LocalDateTime calculateFirstExecutionTime(TimingTask task) {
// 根据频率类型和详情计算首次执行时间
return switch (task.getFrequencyType()) {
case "DAILY" ->
// 如果是每天执行,计算今天或明天的具体时间
calculateDailyFirstExecution(task.getFrequencyDetail());
case "WEEKLY" ->
// 如果是每周执行,计算下周的具体星期几
calculateWeeklyFirstExecution(task.getFrequencyDetail());
case "MONTHLY" ->
// 如果是每月执行,计算下个月的具体日期
calculateMonthlyFirstExecution(task.getFrequencyDetail());
case "QUARTERLY" ->
// 自定义频率,如每小时、每30分钟等
calculateCustomFirstExecution(task.getFrequencyDetail());
default -> throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
};
}
private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) {
// frequencyDetail可能是具体时间,如 "14:30"
LocalTime executionTime = LocalTime.parse(frequencyDetail);
LocalDateTime now = LocalDateTime.now();
LocalDateTime todayExecution = LocalDateTime.of(now.toLocalDate(), executionTime);
// 如果今天的时间已过,则安排明天执行
return now.isBefore(todayExecution) ? todayExecution : todayExecution.plusDays(1);
}
private LocalDateTime calculateWeeklyFirstExecution(String frequencyDetail) {
return null;
}
private LocalDateTime calculateMonthlyFirstExecution(String frequencyDetail) {
return null;
}
private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
return null;
}
@Override
@Transactional
public void updateTaskExecutionTime(Long taskId) {
TimingTask task = timingTaskMapper.selectById(taskId);
if (task == null) {
throw new RuntimeException("定时任务不存在,ID: " + taskId);
}
// 更新最后执行时间为当前时间
task.setLastExecuteTime(LocalDateTime.now());
// 计算下次执行时间
LocalDateTime nextExecutionTime = calculateNextExecutionTime(
task.getFrequencyType(),
task.getFrequencyDetail(),
LocalDateTime.now()
);
task.setNexExecutionTime(nextExecutionTime);
// 更新数据库
timingTaskMapper.updateById(task);
}
/**
* 计算下次执行时间
*/
private LocalDateTime calculateNextExecutionTime(String frequencyType,
String frequencyDetail,
LocalDateTime currentTime) {
try {
return switch (frequencyType) {
case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime);
case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime);
case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime);
case "QUARTERLY" -> calculateQuarterlyNextTime(frequencyDetail, currentTime);
default -> throw new IllegalArgumentException("不支持的频率类型: " + frequencyType);
};
} catch (Exception e) {
throw new RuntimeException("计算下次执行时间失败: " + e.getMessage(), e);
}
}
/**
* 计算每日任务的下次执行时间
*/
private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
LocalTime executionTime = LocalTime.parse(timeStr); // 解析格式 "HH:mm"
LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
// 如果今天的时间已过,则安排明天
return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
}
/**
* 计算每周任务的下次执行时间
*/
private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
String[] parts = detail.split(",");
String dayOfWeekStr = parts[0]; // 如 "MON" 或 "MON|WED|FRI"
LocalTime time = LocalTime.parse(parts[1]); // 时间部分
// 解析星期几(支持多个星期)
Set targetDays = parseDayOfWeeks(dayOfWeekStr);
// 从当前时间开始找下一个符合条件的星期几
LocalDateTime nextTime = current;
while (true) {
nextTime = nextTime.plusDays(1);
if (targetDays.contains(nextTime.getDayOfWeek())) {
return LocalDateTime.of(nextTime.toLocalDate(), time);
}
// 防止无限循环(理论上不会发生)
if (nextTime.isAfter(current.plusYears(1))) {
throw new RuntimeException("无法找到下次执行时间");
}
}
}
/**
* 计算每月任务的下次执行时间
*/
private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
String[] parts = detail.split(",");
int dayOfMonth = Integer.parseInt(parts[0]);
LocalTime time = LocalTime.parse(parts[1]);
// 从下个月开始计算
LocalDateTime nextTime = current.plusMonths(1)
.withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
.with(time);
return nextTime;
}
/**
* 计算每季度任务的下次执行时间
*/
private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
String[] parts = detail.split(",");
int quarterMonth = Integer.parseInt(parts[0]); // 1=第1个月,2=第2个月,3=第3个月
int dayOfMonth = Integer.parseInt(parts[1]);
LocalTime time = LocalTime.parse(parts[2]);
// 计算当前季度
int currentQuarter = (current.getMonthValue() - 1) / 3 + 1;
int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
YearMonth targetYearMonth;
if (currentMonthInQuarter < quarterMonth) {
// 本季度内还有执行机会
targetYearMonth = YearMonth.from(current)
.plusMonths(quarterMonth - currentMonthInQuarter);
} else {
// 需要到下个季度
targetYearMonth = YearMonth.from(current)
.plusMonths(3 - currentMonthInQuarter + quarterMonth);
}
// 处理月末日期
int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
return LocalDateTime.of(
targetYearMonth.getYear(),
targetYearMonth.getMonthValue(),
adjustedDay,
time.getHour(),
time.getMinute()
);
}
/**
* 解析星期几字符串
*/
private Set parseDayOfWeeks(String dayOfWeekStr) {
Set days = new HashSet<>();
String[] dayStrs = dayOfWeekStr.split("\\|");
for (String dayStr : dayStrs) {
switch (dayStr) {
case "MON": days.add(DayOfWeek.MONDAY); break;
case "TUE": days.add(DayOfWeek.TUESDAY); break;
case "WED": days.add(DayOfWeek.WEDNESDAY); break;
case "THU": days.add(DayOfWeek.THURSDAY); break;
case "FRI": days.add(DayOfWeek.FRIDAY); break;
case "SAT": days.add(DayOfWeek.SATURDAY); break;
case "SUN": days.add(DayOfWeek.SUNDAY); break;
default: throw new IllegalArgumentException("无效的星期几: " + dayStr);
}
}
return days;
}
// /**
// * 每天17:40准时触发,执行到期任务
// */
// @Scheduled(cron = "0 55 17 * * ?", zone = "Asia/Shanghai")
// @Transactional
// public void executeDueTasks() {
// LocalDateTime now = LocalDateTime.now();
// log.info("定时任务触发,当前时间: {}", now);
//
// // 1. 查询所有需要立即执行的任务
// List dueTasks = timingTaskMapper.selectActiveTasks(now);
//
// // 2. 处理每个任务
// dueTasks.forEach(task -> {
// // 2.1 转换为巡检任务并保存
// InspectionTask inspectionTask = convertToInspectionTask(task);
// inspectionTaskMapper.insert(inspectionTask);
//
// // 2.2 计算并更新下次执行时间
// updateNextExecutionTime(task, now);
//
// log.info("任务[{}]已执行,下次执行时间: {}",
// task.getTaskName(),
// task.getNexExecutionTime());
// });
// }
//
// private InspectionTask convertToInspectionTask(TimingTask timingTask) {
// InspectionTask inspectionTask = new InspectionTask();
//
// // 复制基本属性
// inspectionTask.setTaskName(timingTask.getTaskName());
// inspectionTask.setInspectorId(timingTask.getInspectorIds());
// inspectionTask.setPort(timingTask.getInspectionLocation());
// inspectionTask.setRemarks("自动生成自定时任务ID: " + timingTask.getId());
// inspectionTask.setRegistrantId(timingTask.getRegistrantId());
// inspectionTask.setFrequency(timingTask.getFrequencyType());
// return inspectionTask;
// }
//
// private void updateNextExecutionTime(TimingTask task, LocalDateTime currentExecutionTime) {
// switch (task.getFrequencyType()) {
// case "DAILY":
// task.setNexExecutionTime(currentExecutionTime.plusDays(1));
// break;
// case "WEEKLY":
// task.setNexExecutionTime(currentExecutionTime.plusWeeks(1));
// break;
// case "MONTHLY":
// task.setNexExecutionTime(currentExecutionTime.plusMonths(1));
// break;
// default:
// task.setNexExecutionTime(null); // 单次任务
// }
// timingTaskMapper.updateById(task);
// }
@Override
public int delByIds(Long[] ids) {
return timingTaskMapper.deleteByIds(Arrays.asList(ids));
}
}