package com.ruoyi.safe.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.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.safe.mapper.SafeLineInspectionTaskMapper;
import com.ruoyi.safe.pojo.SafeLineInspectionTask;
import com.ruoyi.safe.service.SafeLineInspectionTaskService;
import lombok.RequiredArgsConstructor;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
*
* 安全生产--线路定时巡检任务 服务实现类
*
*/
@Service
@RequiredArgsConstructor
public class SafeLineInspectionTaskServiceImpl extends ServiceImpl implements SafeLineInspectionTaskService {
private static final int ENABLED = 1;
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final Map 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 final SafeLineInspectionTaskMapper safeLineInspectionTaskMapper;
private final SafeLineInspectionTaskScheduler safeLineInspectionTaskScheduler;
private final SysUserMapper sysUserMapper;
@Override
public IPage pageSafeLineInspectionTask(Page page, SafeLineInspectionTask task) {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(task.getInspectionName()), SafeLineInspectionTask::getInspectionName, task.getInspectionName())
.like(StringUtils.isNotBlank(task.getLineName()), SafeLineInspectionTask::getLineName, task.getLineName())
.like(StringUtils.isNotBlank(task.getInspectionProject()), SafeLineInspectionTask::getInspectionProject, task.getInspectionProject())
.eq(task.getIsEnabled() != null, SafeLineInspectionTask::getIsEnabled, task.getIsEnabled())
.orderByDesc(SafeLineInspectionTask::getId);
IPage result = safeLineInspectionTaskMapper.selectPage(page, queryWrapper);
fillInspectorName(result.getRecords());
return result;
}
@Override
@Transactional
public boolean saveOrUpdateTask(SafeLineInspectionTask task) throws SchedulerException {
SafeLineInspectionTask oldTask = null;
if (task.getId() != null) {
oldTask = safeLineInspectionTaskMapper.selectById(task.getId());
if (oldTask == null) {
throw new IllegalArgumentException("定时巡检任务不存在");
}
}
normalizeTask(task, oldTask);
task.setNextExecutionTime(calculateFirstExecutionTime(task.getFrequencyType(), task.getFrequencyDetail()));
boolean result;
if (task.getId() == null) {
result = safeLineInspectionTaskMapper.insert(task) > 0;
if (result && isEnabled(task.getIsEnabled())) {
safeLineInspectionTaskScheduler.scheduleTask(task);
}
return result;
}
result = safeLineInspectionTaskMapper.updateById(task) > 0;
if (result) {
boolean oldEnabled = oldTask != null && isEnabled(oldTask.getIsEnabled());
boolean newEnabled = isEnabled(task.getIsEnabled());
if (!newEnabled) {
safeLineInspectionTaskScheduler.unscheduleTask(task.getId());
} else if (oldEnabled) {
safeLineInspectionTaskScheduler.rescheduleTask(task);
} else {
safeLineInspectionTaskScheduler.scheduleTask(task);
}
}
return result;
}
@Override
@Transactional
public boolean removeTaskByIds(List ids) {
boolean removed = removeBatchByIds(ids);
if (removed) {
for (Long id : ids) {
safeLineInspectionTaskScheduler.unscheduleTask(id);
}
}
return removed;
}
@Override
public LocalDateTime calculateFirstExecutionTime(String frequencyType, String frequencyDetail) {
LocalDateTime now = LocalDateTime.now();
return calculateNextExecutionTime(frequencyType, frequencyDetail, now);
}
@Override
public LocalDateTime calculateNextExecutionTime(String frequencyType, String frequencyDetail, LocalDateTime currentTime) {
validateFrequency(frequencyType, frequencyDetail);
return switch (frequencyType) {
case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime);
case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime);
case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime);
default -> throw new IllegalArgumentException("不支持的频率类型: " + frequencyType);
};
}
private void normalizeTask(SafeLineInspectionTask task, SafeLineInspectionTask oldTask) {
if (StringUtils.isBlank(task.getInspectionName())) {
throw new IllegalArgumentException("巡检任务名称不能为空");
}
if (StringUtils.isBlank(task.getLineName())) {
throw new IllegalArgumentException("线路名称不能为空");
}
if (StringUtils.isBlank(task.getInspectionProject())) {
throw new IllegalArgumentException("巡检项目不能为空");
}
if (StringUtils.isBlank(task.getInspectorId())) {
throw new IllegalArgumentException("巡检人不能为空");
}
task.setIsEnabled(resolveEnabledValue(task.getIsEnabled(), oldTask));
task.setFrequencyType(task.getFrequencyType() == null ? null : task.getFrequencyType().trim().toUpperCase());
task.setFrequencyDetail(task.getFrequencyDetail() == null ? null : task.getFrequencyDetail().trim());
validateFrequency(task.getFrequencyType(), task.getFrequencyDetail());
}
private void validateFrequency(String frequencyType, String frequencyDetail) {
if (StringUtils.isBlank(frequencyType) || StringUtils.isBlank(frequencyDetail)) {
throw new IllegalArgumentException("任务频率不能为空");
}
switch (frequencyType) {
case "DAILY" -> parseTime(frequencyDetail);
case "WEEKLY" -> {
String[] parts = splitFrequencyDetail(frequencyDetail);
if (!WEEK_DAY_MAP.containsKey(parts[0])) {
throw new IllegalArgumentException("无效的星期: " + parts[0]);
}
parseTime(parts[1]);
}
case "MONTHLY" -> {
String[] parts = splitFrequencyDetail(frequencyDetail);
validateDayOfMonth(parts[0]);
parseTime(parts[1]);
}
default -> throw new IllegalArgumentException("不支持的频率类型: " + frequencyType);
}
}
private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime currentTime) {
LocalTime executionTime = parseTime(timeStr);
LocalDateTime candidate = LocalDateTime.of(currentTime.toLocalDate(), executionTime);
return currentTime.isBefore(candidate) ? candidate : candidate.plusDays(1);
}
private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime currentTime) {
String[] parts = splitFrequencyDetail(detail);
DayOfWeek targetDay = WEEK_DAY_MAP.get(parts[0]);
LocalTime targetTime = parseTime(parts[1]);
int daysUntil = targetDay.getValue() - currentTime.getDayOfWeek().getValue();
if (daysUntil < 0 || (daysUntil == 0 && !currentTime.toLocalTime().isBefore(targetTime))) {
daysUntil += 7;
}
return LocalDateTime.of(currentTime.toLocalDate().plusDays(daysUntil), targetTime);
}
private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime currentTime) {
String[] parts = splitFrequencyDetail(detail);
int dayOfMonth = validateDayOfMonth(parts[0]);
LocalTime targetTime = parseTime(parts[1]);
YearMonth currentMonth = YearMonth.from(currentTime);
for (int i = 0; i < 24; i++) {
YearMonth targetMonth = currentMonth.plusMonths(i);
if (dayOfMonth > targetMonth.lengthOfMonth()) {
continue;
}
LocalDateTime candidate = LocalDateTime.of(
targetMonth.getYear(),
targetMonth.getMonthValue(),
dayOfMonth,
targetTime.getHour(),
targetTime.getMinute()
);
if (currentTime.isBefore(candidate)) {
return candidate;
}
}
throw new IllegalArgumentException("无法计算下次执行时间");
}
private String[] splitFrequencyDetail(String detail) {
String[] parts = detail.split(",");
if (parts.length != 2) {
throw new IllegalArgumentException("频率详情格式错误");
}
return Arrays.stream(parts).map(String::trim).toArray(String[]::new);
}
private LocalTime parseTime(String timeStr) {
try {
return LocalTime.parse(timeStr, TIME_FORMATTER);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("时间格式必须为HH:mm", e);
}
}
private int validateDayOfMonth(String dayStr) {
try {
int day = Integer.parseInt(dayStr);
if (day < 1 || day > 31) {
throw new IllegalArgumentException("日期必须在1-31之间");
}
return day;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("日期必须在1-31之间", e);
}
}
private Integer resolveEnabledValue(Integer requestEnabled, SafeLineInspectionTask oldTask) {
if (requestEnabled != null) {
return requestEnabled;
}
if (oldTask != null && oldTask.getIsEnabled() != null) {
return oldTask.getIsEnabled();
}
return ENABLED;
}
private boolean isEnabled(Integer enabledValue) {
return Objects.equals(enabledValue, ENABLED);
}
private void fillInspectorName(List records) {
if (records == null || records.isEmpty()) {
return;
}
Set userIds = records.stream()
.map(SafeLineInspectionTask::getInspectorId)
.filter(StringUtils::isNotBlank)
.flatMap(ids -> Arrays.stream(ids.split(",")))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.map(this::parseUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (userIds.isEmpty()) {
return;
}
Map userNameMap = new HashMap<>();
List users = sysUserMapper.selectUserByIds(new ArrayList<>(userIds));
users.forEach(user -> userNameMap.put(user.getUserId(), user.getNickName()));
for (SafeLineInspectionTask record : records) {
if (StringUtils.isBlank(record.getInspectorId())) {
continue;
}
List names = Arrays.stream(record.getInspectorId().split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.map(this::parseUserId)
.filter(Objects::nonNull)
.map(id -> userNameMap.getOrDefault(id, "未知用户"))
.collect(Collectors.toList());
record.setInspector(names);
record.setInspectorName(String.join(",", names));
}
}
private Long parseUserId(String idStr) {
try {
return Long.parseLong(idStr);
} catch (NumberFormatException e) {
return null;
}
}
}