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;
|
|
/**
|
* <p>
|
* 定时巡检任务表 服务实现类
|
* </p>
|
*
|
* @author ld
|
* @since 2025-06-30
|
*/
|
@Service
|
@Slf4j
|
@RequiredArgsConstructor
|
public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService {
|
|
private final TimingTaskMapper timingTaskMapper;
|
|
private final InspectionTaskMapper inspectionTaskMapper;
|
|
private final TimingTaskScheduler timingTaskScheduler;
|
|
private final SysUserMapper sysUserMapper;
|
|
|
@Override
|
public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
|
// 1. 先分页查询定时任务数据
|
IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, null);
|
|
// 2. 如果没有数据,直接返回空分页
|
if (taskPage.getRecords().isEmpty()) {
|
return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
|
}
|
|
// 3. 收集所有需要查询的用户ID
|
Set<Long> 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<Long, String> userNickNameMap = new HashMap<>();
|
if (!userIds.isEmpty()) {
|
List<SysUser> users = sysUserMapper.selectBatchIds(userIds);
|
users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
|
}
|
|
// 5. 转换为DTO
|
List<TimingTaskDto> 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<String> 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<TimingTaskDto> 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<DayOfWeek> 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<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
|
Set<DayOfWeek> 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<TimingTask> 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));
|
}
|
}
|