package com.ruoyi.inspectiontask.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.bean.BeanUtils;
|
import com.ruoyi.device.mapper.DeviceAreaMapper;
|
import com.ruoyi.device.mapper.DeviceLedgerMapper;
|
import com.ruoyi.device.pojo.DeviceArea;
|
import com.ruoyi.device.pojo.DeviceLedger;
|
import com.ruoyi.inspectiontask.dto.TimingTaskDto;
|
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
|
import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
|
import com.ruoyi.inspectiontask.pojo.TimingTask;
|
import com.ruoyi.inspectiontask.service.TimingTaskService;
|
import com.ruoyi.project.system.domain.SysUser;
|
import com.ruoyi.project.system.mapper.SysUserMapper;
|
import lombok.extern.slf4j.Slf4j;
|
import org.quartz.SchedulerException;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.time.DayOfWeek;
|
import java.time.DateTimeException;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.time.Year;
|
import java.time.YearMonth;
|
import java.time.format.DateTimeFormatter;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/**
|
* @author :yys
|
* @date : 2025/9/19 10:55
|
*/
|
@Service
|
@Slf4j
|
public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService {
|
|
@Autowired
|
private TimingTaskMapper timingTaskMapper;
|
|
@Autowired
|
private InspectionTaskMapper inspectionTaskMapper;
|
|
@Autowired
|
private TimingTaskScheduler timingTaskScheduler;
|
|
@Autowired
|
private SysUserMapper sysUserMapper;
|
|
@Autowired
|
private DeviceLedgerMapper deviceLedgerMapper;
|
|
@Autowired
|
private DeviceAreaMapper deviceAreaMapper;
|
|
@Override
|
public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
|
LambdaQueryWrapper<TimingTask> queryWrapper = new LambdaQueryWrapper<>();
|
if (StringUtils.isNotBlank(timingTask.getTaskName())) {
|
queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName());
|
}
|
if (timingTask.getAreaId() != null) {
|
queryWrapper.eq(TimingTask::getAreaId, timingTask.getAreaId());
|
}
|
IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
|
if (taskPage.getRecords().isEmpty()) {
|
return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
|
}
|
|
Set<Long> userIds = new HashSet<>();
|
taskPage.getRecords().forEach(task -> {
|
if (task.getRegistrantId() != null) {
|
userIds.add(task.getRegistrantId());
|
}
|
if (task.getCreateTime() != null) {
|
task.setDateStr(task.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
}
|
if (StringUtils.isNotBlank(task.getInspectorIds())) {
|
Arrays.stream(task.getInspectorIds().split(","))
|
.filter(StringUtils::isNotBlank)
|
.map(Long::valueOf)
|
.forEach(userIds::add);
|
}
|
});
|
|
Map<Long, String> userNickNameMap = new HashMap<>();
|
if (!userIds.isEmpty()) {
|
List<SysUser> users = sysUserMapper.selectUserByIds(new ArrayList<>(userIds));
|
users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
|
}
|
|
Map<Long, String> areaNameMap = deviceAreaMapper.selectBatchIds(taskPage.getRecords().stream()
|
.map(TimingTask::getAreaId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList()))
|
.stream()
|
.collect(Collectors.toMap(DeviceArea::getId, DeviceArea::getAreaName, (left, right) -> left, HashMap::new));
|
|
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(), "未知用户"));
|
}
|
dto.setAreaName(areaNameMap.get(task.getAreaId()));
|
if (StringUtils.isNotBlank(task.getInspectorIds())) {
|
List<String> inspectorNickNames = new ArrayList<>();
|
for (String idStr : task.getInspectorIds().split(",")) {
|
if (StringUtils.isNotBlank(idStr)) {
|
Long id = Long.valueOf(idStr);
|
inspectorNickNames.add(userNickNameMap.getOrDefault(id, "未知用户"));
|
}
|
}
|
dto.setInspector(inspectorNickNames);
|
}
|
return dto;
|
}).collect(Collectors.toList());
|
|
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);
|
prepareTimingTask(timingTask);
|
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
LocalDate localDate = LocalDate.now();
|
if (StringUtils.isNotEmpty(timingTaskDto.getDateStr())) {
|
localDate = LocalDate.parse(timingTaskDto.getDateStr(), formatter);
|
}
|
LocalTime currentTime = LocalTime.now();
|
timingTask.setCreateTime(LocalDateTime.of(localDate, currentTime));
|
|
if (Objects.isNull(timingTaskDto.getId())) {
|
timingTask.setRegistrationDate(LocalDate.now());
|
timingTask.setActive(true);
|
timingTask.setNextExecutionTime(calculateFirstExecutionTime(timingTask));
|
int result = timingTaskMapper.insert(timingTask);
|
if (result > 0) {
|
timingTaskScheduler.scheduleTimingTask(timingTask);
|
}
|
return result;
|
}
|
|
int result = timingTaskMapper.updateById(timingTask);
|
if (result > 0) {
|
timingTaskScheduler.rescheduleTimingTask(timingTask);
|
}
|
return result;
|
}
|
|
private void prepareTimingTask(TimingTask timingTask) {
|
Long[] selectedTaskIds = timingTask.getTaskIds();
|
if ((selectedTaskIds == null || selectedTaskIds.length == 0) && timingTask.getTaskId() != null) {
|
selectedTaskIds = new Long[]{timingTask.getTaskId().longValue()};
|
}
|
if (selectedTaskIds == null || selectedTaskIds.length == 0) {
|
timingTask.setTaskIdsStr(null);
|
return;
|
}
|
|
List<Long> deviceIds = Arrays.stream(selectedTaskIds)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
if (deviceIds.isEmpty()) {
|
timingTask.setTaskIdsStr(null);
|
return;
|
}
|
|
List<DeviceLedger> devices = deviceLedgerMapper.selectBatchIds(deviceIds);
|
Map<Long, DeviceLedger> deviceMap = devices.stream()
|
.collect(Collectors.toMap(DeviceLedger::getId, device -> device, (left, right) -> left, LinkedHashMap::new));
|
|
List<Long> validDeviceIds = deviceIds.stream()
|
.filter(deviceMap::containsKey)
|
.collect(Collectors.toList());
|
if (validDeviceIds.isEmpty()) {
|
throw new IllegalArgumentException("所选设备不存在");
|
}
|
|
timingTask.setTaskIds(validDeviceIds.toArray(new Long[0]));
|
timingTask.setTaskIdsStr(validDeviceIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
timingTask.setTaskId(validDeviceIds.get(0).intValue());
|
if (timingTask.getAreaId() == null) {
|
DeviceLedger firstDevice = deviceMap.get(validDeviceIds.get(0));
|
if (firstDevice != null) {
|
timingTask.setAreaId(firstDevice.getAreaId());
|
}
|
}
|
timingTask.setTaskName(validDeviceIds.stream()
|
.map(deviceMap::get)
|
.filter(Objects::nonNull)
|
.map(DeviceLedger::getDeviceName)
|
.filter(StringUtils::isNotBlank)
|
.collect(Collectors.joining(",")));
|
}
|
|
public LocalDateTime calculateFirstExecutionTime(TimingTask task) {
|
String frequencyType = task.getFrequencyType();
|
if ("DAILY".equals(frequencyType)) {
|
return calculateDailyFirstExecution(task.getFrequencyDetail());
|
} else if ("WEEKLY".equals(frequencyType)) {
|
return calculateWeeklyFirstExecution(task.getFrequencyDetail());
|
} else if ("MONTHLY".equals(frequencyType)) {
|
return calculateMonthlyFirstExecution(task.getFrequencyDetail());
|
} else if ("YEARLY".equals(frequencyType)) {
|
return calculateYearlyFirstExecution(task.getFrequencyDetail());
|
} else if ("QUARTERLY".equals(frequencyType)) {
|
return calculateCustomFirstExecution(task.getFrequencyDetail());
|
} else {
|
throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
|
}
|
}
|
|
private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) {
|
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 static final Map<String, DayOfWeek> WEEK_DAY_MAP = new HashMap<>();
|
|
static {
|
WEEK_DAY_MAP.put("MON", DayOfWeek.MONDAY);
|
WEEK_DAY_MAP.put("TUE", DayOfWeek.TUESDAY);
|
WEEK_DAY_MAP.put("WED", DayOfWeek.WEDNESDAY);
|
WEEK_DAY_MAP.put("THU", DayOfWeek.THURSDAY);
|
WEEK_DAY_MAP.put("FRI", DayOfWeek.FRIDAY);
|
WEEK_DAY_MAP.put("SAT", DayOfWeek.SATURDAY);
|
WEEK_DAY_MAP.put("SUN", DayOfWeek.SUNDAY);
|
}
|
|
private LocalDateTime calculateWeeklyFirstExecution(String frequencyDetail) {
|
String[] parts = frequencyDetail.split(",");
|
if (parts.length != 2) {
|
throw new IllegalArgumentException("参数格式错误,应为 MON,13:43");
|
}
|
|
String weekDayStr = parts[0].trim();
|
String timeStr = parts[1].trim();
|
DayOfWeek targetDay = WEEK_DAY_MAP.get(weekDayStr);
|
if (targetDay == null) {
|
throw new IllegalArgumentException("无效的星期: " + weekDayStr);
|
}
|
|
LocalTime targetTime = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm"));
|
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime targetDateTime = now.with(targetDay).with(targetTime);
|
if (targetDateTime.isBefore(now)) {
|
targetDateTime = targetDateTime.plusWeeks(1);
|
}
|
return targetDateTime;
|
}
|
|
private LocalDateTime calculateMonthlyFirstExecution(String frequencyDetail) {
|
String[] parts = frequencyDetail.split(",");
|
if (parts.length != 2) {
|
throw new IllegalArgumentException("参数格式错误,应为 03,17:00");
|
}
|
|
String dayStr = parts[0].trim();
|
String timeStr = parts[1].trim();
|
|
int dayOfMonth;
|
try {
|
dayOfMonth = Integer.parseInt(dayStr);
|
} catch (NumberFormatException e) {
|
throw new IllegalArgumentException("无效的日期格式: " + dayStr, e);
|
}
|
|
if (dayOfMonth < 1 || dayOfMonth > 31) {
|
throw new IllegalArgumentException("日期必须在 1-31 之间: " + dayOfMonth);
|
}
|
|
LocalTime targetTime;
|
try {
|
targetTime = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm"));
|
} catch (DateTimeException e) {
|
throw new IllegalArgumentException("无效的时间格式: " + timeStr, e);
|
}
|
|
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime targetDateTime = now.withDayOfMonth(dayOfMonth).with(targetTime);
|
boolean isDateAdjusted = targetDateTime.getDayOfMonth() != dayOfMonth;
|
if (targetDateTime.isBefore(now) || isDateAdjusted) {
|
LocalDateTime nextMonth = now.plusMonths(1);
|
LocalDateTime nextMonthTarget = nextMonth.withDayOfMonth(dayOfMonth).with(targetTime);
|
if (nextMonthTarget.getDayOfMonth() != dayOfMonth) {
|
int lastDayOfMonth = nextMonth.getMonth().length(Year.of(nextMonth.getYear()).isLeap());
|
nextMonthTarget = nextMonth.withDayOfMonth(lastDayOfMonth).with(targetTime);
|
}
|
targetDateTime = nextMonthTarget;
|
}
|
return targetDateTime;
|
}
|
|
private LocalDateTime calculateYearlyFirstExecution(String frequencyDetail) {
|
String[] parts = frequencyDetail.split(",");
|
if (parts.length != 3) {
|
throw new IllegalArgumentException("参数格式错误,应为 03,15,17:00");
|
}
|
|
int month = Integer.parseInt(parts[0].trim());
|
int dayOfMonth = Integer.parseInt(parts[1].trim());
|
LocalTime targetTime = LocalTime.parse(parts[2].trim(), DateTimeFormatter.ofPattern("HH:mm"));
|
|
LocalDateTime now = LocalDateTime.now();
|
YearMonth currentYearMonth = YearMonth.of(now.getYear(), month);
|
int adjustedDay = Math.min(dayOfMonth, currentYearMonth.lengthOfMonth());
|
LocalDateTime targetDateTime = LocalDateTime.of(now.getYear(), month, adjustedDay, targetTime.getHour(), targetTime.getMinute());
|
if (!targetDateTime.isAfter(now)) {
|
YearMonth nextYearMonth = YearMonth.of(now.getYear() + 1, month);
|
adjustedDay = Math.min(dayOfMonth, nextYearMonth.lengthOfMonth());
|
targetDateTime = LocalDateTime.of(now.getYear() + 1, month, adjustedDay, targetTime.getHour(), targetTime.getMinute());
|
}
|
return targetDateTime;
|
}
|
|
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.setLastExecutionTime(LocalDateTime.now());
|
LocalDateTime nextExecutionTime = calculateNextExecutionTime(
|
task.getFrequencyType(),
|
task.getFrequencyDetail(),
|
LocalDateTime.now()
|
);
|
task.setNextExecutionTime(nextExecutionTime);
|
timingTaskMapper.updateById(task);
|
}
|
|
private LocalDateTime calculateNextExecutionTime(String frequencyType,
|
String frequencyDetail,
|
LocalDateTime currentTime) {
|
try {
|
switch (frequencyType) {
|
case "DAILY":
|
return calculateDailyNextTime(frequencyDetail, currentTime);
|
case "WEEKLY":
|
return calculateWeeklyNextTime(frequencyDetail, currentTime);
|
case "MONTHLY":
|
return calculateMonthlyNextTime(frequencyDetail, currentTime);
|
case "QUARTERLY":
|
return calculateQuarterlyNextTime(frequencyDetail, currentTime);
|
case "YEARLY":
|
return calculateYearlyNextTime(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);
|
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];
|
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]);
|
return current.plusMonths(1)
|
.withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
|
.with(time);
|
}
|
|
private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
|
String[] parts = detail.split(",");
|
int quarterMonth = Integer.parseInt(parts[0]);
|
int dayOfMonth = Integer.parseInt(parts[1]);
|
LocalTime time = LocalTime.parse(parts[2]);
|
|
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 LocalDateTime calculateYearlyNextTime(String detail, LocalDateTime current) {
|
String[] parts = detail.split(",");
|
int month = Integer.parseInt(parts[0]);
|
int dayOfMonth = Integer.parseInt(parts[1]);
|
LocalTime time = LocalTime.parse(parts[2]);
|
|
YearMonth targetYearMonth = YearMonth.of(current.getYear(), month);
|
int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
|
LocalDateTime target = LocalDateTime.of(current.getYear(), month, adjustedDay, time.getHour(), time.getMinute());
|
if (!target.isAfter(current)) {
|
targetYearMonth = YearMonth.of(current.getYear() + 1, month);
|
adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
|
target = LocalDateTime.of(current.getYear() + 1, month, adjustedDay, time.getHour(), time.getMinute());
|
}
|
return target;
|
}
|
|
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;
|
}
|
|
@Override
|
public int delByIds(Long[] ids) {
|
int i = timingTaskMapper.deleteBatchIds(Arrays.asList(ids));
|
if (i > 0) {
|
for (Long id : ids) {
|
timingTaskScheduler.unscheduleTimingTask(id);
|
}
|
}
|
return i;
|
}
|
}
|