| | |
| | | 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.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.bean.BeanUtils; |
| | | import com.ruoyi.inspectiontask.dto.TimingTaskDto; |
| | | import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper; |
| | | import com.ruoyi.inspectiontask.mapper.InspectionItemMapper; |
| | | import com.ruoyi.inspectiontask.mapper.TimingTaskItemMapper; |
| | | import com.ruoyi.inspectiontask.mapper.TimingTaskMapper; |
| | | import com.ruoyi.inspectiontask.pojo.InspectionItem; |
| | | import com.ruoyi.inspectiontask.pojo.TimingTask; |
| | | import com.ruoyi.inspectiontask.pojo.TimingTaskItem; |
| | | import com.ruoyi.inspectiontask.service.TimingTaskService; |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | | import com.ruoyi.project.system.mapper.SysUserMapper; |
| | | import lombok.RequiredArgsConstructor; |
| | | 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.*; |
| | | 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.*; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService { |
| | | |
| | | @Autowired |
| | | private TimingTaskMapper timingTaskMapper; |
| | | private final TimingTaskMapper timingTaskMapper; |
| | | private final TimingTaskScheduler timingTaskScheduler; |
| | | private final SysUserMapper sysUserMapper; |
| | | private final InspectionItemMapper inspectionItemMapper; |
| | | private final TimingTaskItemMapper timingTaskItemMapper; |
| | | |
| | | @Autowired |
| | | private InspectionTaskMapper inspectionTaskMapper; |
| | | |
| | | @Autowired |
| | | private TimingTaskScheduler timingTaskScheduler; |
| | | |
| | | @Autowired |
| | | private SysUserMapper sysUserMapper; |
| | | |
| | | private static final int ENABLED = 1; |
| | | private static final int DISABLED = 0; |
| | | |
| | | @Override |
| | | public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) { |
| | | // 1. 先分页查询定时任务数据 |
| | | IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, null); |
| | | // 构建查询条件 |
| | | LambdaQueryWrapper<TimingTask> queryWrapper = new LambdaQueryWrapper<>(); |
| | | if (StringUtils.isNotBlank(timingTask.getTaskName())) { |
| | | queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName()); |
| | | } |
| | | queryWrapper.eq(TimingTask::getDeleted, 0); |
| | | if (timingTask.getIsEnabled() != null) { |
| | | queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled()); |
| | | } |
| | | // 按 ID 倒序排序 |
| | | queryWrapper.orderByDesc(TimingTask::getId); |
| | | IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper); |
| | | |
| | | // 2. 如果没有数据,直接返回空分页 |
| | | if (taskPage.getRecords().isEmpty()) { |
| | |
| | | dto.setInspector(inspectorNickNames); |
| | | } |
| | | |
| | | List<TimingTaskItem> taskItems = timingTaskItemMapper.selectList( |
| | | new LambdaQueryWrapper<TimingTaskItem>() |
| | | .eq(TimingTaskItem::getTimingTaskId, task.getId()) |
| | | .orderByAsc(TimingTaskItem::getSortNum)); |
| | | dto.setInspectionItems(taskItems); |
| | | dto.setInspectionItemIds(taskItems.stream() |
| | | .map(TimingTaskItem::getInspectionItemId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList())); |
| | | |
| | | return dto; |
| | | }).collect(Collectors.toList()); |
| | | |
| | |
| | | @Override |
| | | @Transactional |
| | | public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException { |
| | | TimingTask oldTimingTask = null; |
| | | if (Objects.nonNull(timingTaskDto.getId())) { |
| | | oldTimingTask = timingTaskMapper.selectById(timingTaskDto.getId()); |
| | | if (oldTimingTask == null) { |
| | | throw new IllegalArgumentException("定时任务不存在"); |
| | | } |
| | | } |
| | | List<InspectionItem> selectedItems = resolveSelectedItems(timingTaskDto, oldTimingTask); |
| | | List<TimingTaskItem> customItems = normalizeCustomItems(timingTaskDto.getCustomItems()); |
| | | if (selectedItems.isEmpty() && customItems.isEmpty()) { |
| | | throw new ServiceException("请选择或添加至少一个巡检内容"); |
| | | } |
| | | TimingTask timingTask = new TimingTask(); |
| | | BeanUtils.copyProperties(timingTaskDto, timingTask); |
| | | timingTask.setIsEnabled(resolveEnabledValue(timingTask.getIsEnabled(), oldTimingTask)); |
| | | timingTask.setActive(ENABLED == timingTask.getIsEnabled()); |
| | | // 1. 解析字符串为 LocalDate(只包含年月日) |
| | | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | LocalDate localDate = LocalDate.parse(timingTaskDto.getDateStr(), formatter); |
| | | |
| | | LocalDate localDate = LocalDate.now(); |
| | | if (StringUtils.isNotEmpty(timingTaskDto.getDateStr())) { |
| | | localDate = LocalDate.parse(timingTaskDto.getDateStr(), formatter); |
| | | } |
| | | // 2. 获取当前系统的 LocalTime(包含时分秒) |
| | | LocalTime currentTime = LocalTime.now(); |
| | | |
| | |
| | | // 设置创建人信息和默认值 |
| | | if (Objects.isNull(timingTaskDto.getId())) { |
| | | timingTask.setRegistrationDate(LocalDate.now()); |
| | | timingTask.setActive(true); |
| | | |
| | | // 计算首次执行时间 |
| | | LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask); |
| | | timingTask.setNextExecutionTime(firstExecutionTime); |
| | | int result = timingTaskMapper.insert(timingTask); |
| | | if (result > 0) { |
| | | saveTaskItems(timingTask, selectedItems, customItems); |
| | | } |
| | | if (result > 0 && isEnabled(timingTask.getIsEnabled(), timingTask.isActive())) { |
| | | // 新增成功后添加到调度器 |
| | | timingTaskScheduler.scheduleTimingTask(timingTask); |
| | | } |
| | | return result; |
| | | } else { |
| | | |
| | | |
| | | int result = timingTaskMapper.updateById(timingTask); |
| | | if (result > 0) { |
| | | // 更新成功后重新调度任务 |
| | | timingTaskScheduler.rescheduleTimingTask(timingTask); |
| | | timingTaskItemMapper.delete(new LambdaQueryWrapper<TimingTaskItem>() |
| | | .eq(TimingTaskItem::getTimingTaskId, timingTask.getId())); |
| | | saveTaskItems(timingTask, selectedItems, customItems); |
| | | boolean oldEnabled = isEnabled( |
| | | oldTimingTask == null ? null : oldTimingTask.getIsEnabled(), |
| | | oldTimingTask != null && oldTimingTask.isActive()); |
| | | boolean newEnabled = isEnabled(timingTask.getIsEnabled(), timingTask.isActive()); |
| | | if (!newEnabled) { |
| | | timingTaskScheduler.unscheduleTimingTask(timingTask.getId()); |
| | | } else if (oldEnabled) { |
| | | // 更新成功后重新调度任务 |
| | | timingTaskScheduler.rescheduleTimingTask(timingTask); |
| | | } else { |
| | | // 从禁用改为启用时重新创建调度任务 |
| | | timingTaskScheduler.scheduleTimingTask(timingTask); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | private List<InspectionItem> resolveSelectedItems(TimingTask request, TimingTask oldTask) { |
| | | List<Long> itemIds = request.getInspectionItemIds(); |
| | | if (itemIds == null && oldTask != null) { |
| | | itemIds = timingTaskItemMapper.selectList(new LambdaQueryWrapper<TimingTaskItem>() |
| | | .eq(TimingTaskItem::getTimingTaskId, oldTask.getId())) |
| | | .stream().map(TimingTaskItem::getInspectionItemId).filter(Objects::nonNull).toList(); |
| | | } |
| | | if (request.getTaskId() == null || StringUtils.isBlank(request.getFrequencyType())) { |
| | | throw new ServiceException("设备和巡检频率不能为空"); |
| | | } |
| | | if (itemIds == null || itemIds.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | if (new HashSet<>(itemIds).size() != itemIds.size()) { |
| | | throw new ServiceException("巡检内容不能重复选择"); |
| | | } |
| | | List<InspectionItem> items = inspectionItemMapper.selectBatchIds(itemIds); |
| | | if (items.size() != itemIds.size()) { |
| | | throw new ServiceException("所选巡检内容不存在"); |
| | | } |
| | | Long deviceId = request.getTaskId().longValue(); |
| | | String frequency = request.getFrequencyType(); |
| | | Set<Long> oldItemIds = oldTask == null ? Collections.emptySet() |
| | | : new HashSet<>(timingTaskItemMapper.selectList(new LambdaQueryWrapper<TimingTaskItem>() |
| | | .eq(TimingTaskItem::getTimingTaskId, oldTask.getId())) |
| | | .stream().map(TimingTaskItem::getInspectionItemId).filter(Objects::nonNull).toList()); |
| | | boolean invalid = items.stream().anyMatch(item -> |
| | | (item.getDeviceLedgerId() != null && !deviceId.equals(item.getDeviceLedgerId())) |
| | | || !(frequency.equals(item.getFrequencyType()) || "ALL".equals(item.getFrequencyType())) |
| | | || (item.getDeleted() != null && item.getDeleted() == 1 |
| | | || item.getIsActive() == null || item.getIsActive() != 1) && !oldItemIds.contains(item.getId())); |
| | | if (invalid) { |
| | | throw new ServiceException("所选巡检内容与设备或频率不匹配"); |
| | | } |
| | | Map<Long, InspectionItem> itemMap = items.stream() |
| | | .collect(Collectors.toMap(InspectionItem::getId, Function.identity())); |
| | | return itemIds.stream().map(itemMap::get).toList(); |
| | | } |
| | | |
| | | private List<TimingTaskItem> normalizeCustomItems(List<TimingTaskItem> customItems) { |
| | | if (customItems == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | return customItems.stream() |
| | | .filter(Objects::nonNull) |
| | | .filter(item -> StringUtils.isNotBlank(item.getItemProject()) |
| | | && StringUtils.isNotBlank(item.getItemContent())) |
| | | .peek(item -> { |
| | | item.setItemProject(item.getItemProject().trim()); |
| | | item.setItemContent(item.getItemContent().trim()); |
| | | }) |
| | | .toList(); |
| | | } |
| | | |
| | | private void saveTaskItems( |
| | | TimingTask task, |
| | | List<InspectionItem> selectedItems, |
| | | List<TimingTaskItem> customItems) { |
| | | for (int index = 0; index < selectedItems.size(); index++) { |
| | | InspectionItem source = selectedItems.get(index); |
| | | TimingTaskItem taskItem = new TimingTaskItem(); |
| | | taskItem.setTimingTaskId(task.getId()); |
| | | taskItem.setInspectionItemId(source.getId()); |
| | | taskItem.setItemProject(source.getItemProject()); |
| | | taskItem.setItemContent(source.getItemContent()); |
| | | taskItem.setSortNum(index); |
| | | taskItem.setTenantId(task.getTenantId()); |
| | | taskItem.setDeptId(task.getDeptId()); |
| | | timingTaskItemMapper.insert(taskItem); |
| | | } |
| | | for (int index = 0; index < customItems.size(); index++) { |
| | | TimingTaskItem source = customItems.get(index); |
| | | TimingTaskItem taskItem = new TimingTaskItem(); |
| | | taskItem.setTimingTaskId(task.getId()); |
| | | taskItem.setItemProject(source.getItemProject()); |
| | | taskItem.setItemContent(source.getItemContent()); |
| | | taskItem.setSortNum(selectedItems.size() + index); |
| | | taskItem.setTenantId(task.getTenantId()); |
| | | taskItem.setDeptId(task.getDeptId()); |
| | | timingTaskItemMapper.insert(taskItem); |
| | | } |
| | | } |
| | | |
| | | public LocalDateTime calculateFirstExecutionTime(TimingTask task) { |
| | | // 根据频率类型和详情计算首次执行时间 |
| | | if (task == null || StringUtils.isBlank(task.getFrequencyDetail())) { |
| | | throw new IllegalArgumentException("请填写任务执行日期和时间"); |
| | | } |
| | | String frequencyType = task.getFrequencyType(); |
| | | if ("DAILY".equals(frequencyType)) { |
| | | // 如果是每天执行,计算今天或明天的具体时间 |
| | |
| | | |
| | | // 映射星期简写与DayOfWeek |
| | | 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); |
| | |
| | | } |
| | | |
| | | private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) { |
| | | return null; |
| | | String[] parts = frequencyDetail.split(","); |
| | | if (parts.length != 3) { |
| | | throw new IllegalArgumentException("季度频率格式错误,应为'季度内月份,日期,时间'"); |
| | | } |
| | | int monthInQuarter = Integer.parseInt(parts[0]); |
| | | int dayOfMonth = Integer.parseInt(parts[1]); |
| | | LocalTime time = LocalTime.parse(parts[2], DateTimeFormatter.ofPattern("HH:mm")); |
| | | if (monthInQuarter < 1 || monthInQuarter > 3 || dayOfMonth < 1 || dayOfMonth > 31) { |
| | | throw new IllegalArgumentException("季度频率日期无效"); |
| | | } |
| | | |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | int currentMonthInQuarter = (now.getMonthValue() - 1) % 3 + 1; |
| | | int monthsUntilTarget = monthInQuarter - currentMonthInQuarter; |
| | | if (monthsUntilTarget < 0) { |
| | | monthsUntilTarget += 3; |
| | | } |
| | | YearMonth targetMonth = YearMonth.from(now).plusMonths(monthsUntilTarget); |
| | | int targetDay = Math.min(dayOfMonth, targetMonth.lengthOfMonth()); |
| | | LocalDateTime target = targetMonth.atDay(targetDay).atTime(time); |
| | | if (!target.isAfter(now)) { |
| | | targetMonth = targetMonth.plusMonths(3); |
| | | target = targetMonth.atDay(Math.min(dayOfMonth, targetMonth.lengthOfMonth())).atTime(time); |
| | | } |
| | | return target; |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | 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; |
| | | 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; |
| | | } |
| | | |
| | | private Integer resolveEnabledValue(Integer requestEnabled, TimingTask oldTimingTask) { |
| | | if (requestEnabled != null) { |
| | | return requestEnabled; |
| | | } |
| | | if (oldTimingTask != null) { |
| | | if (oldTimingTask.getIsEnabled() != null) { |
| | | return oldTimingTask.getIsEnabled(); |
| | | } |
| | | return oldTimingTask.isActive() ? ENABLED : DISABLED; |
| | | } |
| | | return ENABLED; |
| | | } |
| | | |
| | | private boolean isEnabled(Integer enabledValue, boolean activeFallback) { |
| | | if (enabledValue != null) { |
| | | return ENABLED == enabledValue; |
| | | } |
| | | return activeFallback; |
| | | } |
| | | |
| | | @Override |
| | | public int delByIds(Long[] ids) { |
| | | if (ids == null || ids.length == 0) { |
| | | return 0; |
| | | } |
| | | int i = timingTaskMapper.deleteBatchIds(Arrays.asList(ids)); |
| | | if(i > 0){ |
| | | if (i > 0) { |
| | | timingTaskItemMapper.delete(new LambdaQueryWrapper<TimingTaskItem>() |
| | | .in(TimingTaskItem::getTimingTaskId, Arrays.asList(ids))); |
| | | for (Long id : ids) { |
| | | timingTaskScheduler.unscheduleTimingTask(id); |
| | | } |