# MorSchedulerController.java

    package cloud.wconnect.workperformance.controller.mor.overtime;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.cons.SchedulerCons;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.scheduler.JobParameterDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.CronRuleUtil;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.workperformance.cons.mor.MorNotifyTypeEnum;
    import cloud.wconnect.workperformance.entity.mor.MorScheduler;
    import cloud.wconnect.workperformance.pojo.dto.mor.scheduler.MorSchedulerDTO;
    import cloud.wconnect.workperformance.service.mor.IMorSchedulerService;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.web.bind.annotation.*;

    import java.text.MessageFormat;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;

    /**
     * <p>
     * mor 功能的调度数据 前端控制器
     * </p>
     *
     * @author zc
     * @since 2024-03-14
     */
    @RestController
    @RequestMapping("/mor/scheduler")
    public class MorSchedulerController {
        @Resource
        private IMorSchedulerService schedulerService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询调度任务
         *
         * @param property 资产标识
         * @param category 调度任务类型
         * @return 调度任务
         * @author zc
         * @date 2024/3/14
         */
        @ApiId(1049)
        @GetMapping("/{category:\\d+}")
        public List<MorSchedulerDTO> list(String property, @PathVariable int category) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验支持的定时任务类型
            checkSchedulerCategory(category);
            // 查询调度任务
            List<MorScheduler> schedulerList = schedulerService.list(propertyId, category);
            if (CollectionUtils.isEmpty(schedulerList)) {
                return Collections.emptyList();
            }
            return beanMapper.mapList(schedulerList, MorSchedulerDTO.class);
        }

        /**
         * 保存调度任务
         *
         * @param property     资产标识
         * @param id           调度任务id
         * @param schedulerDTO 调度任务DTO
         * @return java.util.Map<java.lang.String, java.lang.Integer>
         * @author zc
         * @date 2024/3/14
         */
        @ApiId(1050)
        @PostMapping("/{id}")
        public Map<String, String> save(@RequestParam String property, @PathVariable String id,
                                        @RequestBody @Valid MorSchedulerDTO schedulerDTO) {
            int category = schedulerDTO.getCategory();
            // 校验支持的定时任务类型
            checkSchedulerCategory(category);
            // 校验定时任务配置参数
            if (category == MorNotifyTypeEnum.MISSING_REPORT_WORK_HOUR_LAST_MONTH.getType()) {
                checkParametersForWorkHourLastMonth(schedulerDTO);
            } else if (category == MorNotifyTypeEnum.MISSING_REPORT_WORK_HOUR_MTD_YESTERDAY.getType()) {
                checkParametersForWorkHourMtdYesterday(schedulerDTO);
            }

            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            schedulerDTO.setPropertyId(propertyId);
            if (!id.equals("0")) {
                // 校验调度任务是否存在
                MorScheduler scheduler = schedulerService.checkExist(id);
                // 校验归属
                schedulerService.checkBelong(propertyId, scheduler);
                if (scheduler.getStatus() == SchedulerCons.STATUS_DELETING) {
                    throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_UPDATE_DELETING,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The deleting MorScheduler cannot update, SchedulerJobId=" + scheduler.getId());
                }
                if (!scheduler.getCategory().equals(schedulerDTO.getCategory())) {
                    throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_CANNOT_CHANGE_CATEGORY,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The MorScheduler category cannot update, SchedulerJobId=" + scheduler.getId());
                }
                // 修改调度任务
                schedulerDTO.setId(id);
                return ImmutableMap.of("id", schedulerService.update(propertyDTO, scheduler, schedulerDTO, sessionContext.getLoginUserId()));
            }
            // 限制同一个资产下同一个 category 只允许有一条状态为未删除中的 scheduler 记录
            schedulerService.checkMaxNum(propertyId, schedulerDTO.getCategory(), 1);
            // 新增调度任务
            return ImmutableMap.of("id", schedulerService.save(schedulerDTO, propertyDTO, sessionContext.getLoginUserId()));
        }

        /**
         * 校验支持的定时任务类型
         *
         * @param schedulerType 定时任务类型
         * @author zc
         * @date 2026/3/6
         */
        private void checkSchedulerCategory(int schedulerType) {
            List<Integer> effectiveSchedulerTypeList = List.of(
                    MorNotifyTypeEnum.MISSING_REPORT_WORK_HOUR_LAST_MONTH.getType(),
                    MorNotifyTypeEnum.MISSING_REPORT_WORK_HOUR_MTD_YESTERDAY.getType());
            if (!effectiveSchedulerTypeList.contains(schedulerType)) {
                throw new BadRequestException(CommonErrorI18nConstant.NOTIFY_TYPE_NOT_SUPPORTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The currently selected scheduler type is not supported, " +
                                "schedulerCategory must be in {0}", effectiveSchedulerTypeList));
            }
        }

        /**
         * 校验入参 for Overtime Work Hour Last Month
         *
         * @param schedulerDTO 调度任务DTO
         * @author zc
         * @date 2024/3/17
         */
        private void checkParametersForWorkHourLastMonth(MorSchedulerDTO schedulerDTO) {
            // 检查 scheduler json 格式是否合法
            CronRuleUtil.validateCronRuleForScheduler(objectMapper, Lists.newArrayList(SchedulerCons.EVERY_MONTH),
                    schedulerDTO.getScheduler());
            // 校验调度任务参数
            checkParameters(schedulerDTO.getParameters());
            JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                    schedulerDTO.getParameters(), JobParameterDTO.class);
            // 校验日期单位
            checkParametersDateRangeUnit(jobParameterDTO.getDateRangeUnit());
            // 校验日期范围
            checkParametersDateRange(jobParameterDTO.getDateRange());
            // 校验阈值
            checkParametersThreshold(jobParameterDTO.getThreshold());
            // 组装日期单位、日期范围
            jobParameterDTO.setDateRangeUnit(JobParameterDTO.MONTH);
            jobParameterDTO.setDateRange(MorSchedulerDTO.DATE_RANGE_WORK_HOUR_LAST_MONTH);
            schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
        }

        /**
         * 校验入参 for Overtime Work Hour MTD Yesterday
         *
         * @param schedulerDTO 调度任务DTO
         * @author zc
         * @date 2025/3/15
         */
        private void checkParametersForWorkHourMtdYesterday(MorSchedulerDTO schedulerDTO) {
            // 检查 scheduler json 格式是否合法
            CronRuleUtil.validateCronRuleForScheduler(objectMapper, Lists.newArrayList(SchedulerCons.EVERY_DAY),
                    schedulerDTO.getScheduler());
            // 校验调度任务参数
            checkParameters(schedulerDTO.getParameters());
            JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper,
                    schedulerDTO.getParameters(), JobParameterDTO.class);
            // 校验日期单位
            checkParametersDateRangeUnit(jobParameterDTO.getDateRangeUnit());
            // 校验日期范围
            checkParametersDateRange(jobParameterDTO.getDateRange());
            // 校验阈值
            checkParametersThreshold(jobParameterDTO.getThreshold());
            // 组装日期单位、日期范围
            jobParameterDTO.setDateRangeUnit(JobParameterDTO.DAY);
            jobParameterDTO.setDateRange(MorSchedulerDTO.DATE_RANGE_WORK_HOUR_MTD_YESTERDAY);
            schedulerDTO.setParameters(JsonUtils.convertObjToString(objectMapper, jobParameterDTO));
        }

        /**
         * 校验日期单位
         *
         * @param dateRangeUnit 日期单位
         * @author zc
         * @date 2024/3/13
         */
        private void checkParametersDateRangeUnit(Integer dateRangeUnit) {
            if (Objects.isNull(dateRangeUnit)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_DATE_RANGE_UNIT),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The dateRangeUnit cannot be null");
            }
            List<Integer> dateRangeUnitList = Lists.newArrayList(JobParameterDTO.DAY, JobParameterDTO.WEEK, JobParameterDTO.MONTH);
            if (!dateRangeUnitList.contains(dateRangeUnit)) {
                throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_DATE_RANGE_UNIT,
                                List.of(CommonFieldI18nConstant.DAY, CommonFieldI18nConstant.WEEK, CommonFieldI18nConstant.MONTH)),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The dateRangeUnit must be in [1:day 2:week 3:month]");
            }
        }

        /**
         * 校验日期范围
         *
         * @param dateRange 日期范围
         * @author zc
         * @date 2024/3/13
         */
        private void checkParametersDateRange(String dateRange) {
            if (StringUtils.isBlank(dateRange)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_DATE_RANGE),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The dateRange cannot be blank");
            }
        }

        /**
         * 校验阈值
         *
         * @param threshold 阈值
         * @author zc
         * @date 2024/3/13
         */
        private void checkParametersThreshold(Integer threshold) {
            if (Objects.isNull(threshold)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_THRESHOLD),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The threshold cannot be blank");
            }
        }

        /**
         * 校验调度任务参数
         *
         * @param parameters 调度任务参数
         * @author zc
         * @date 2024/3/13
         */
        private void checkParameters(String parameters) {
            if (StringUtils.isBlank(parameters)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.SCHEDULER_EXEC_PARAMETERS),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The parameters cannot be blank");
            }
        }
    }