# MorSchedulerServiceImpl.java

    package cloud.wconnect.workperformance.service.mor.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.*;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.scheduler.JobBodyDTO;
    import cloud.wconnect.commonlib.pojo.dto.scheduler.JobParameterDTO;
    import cloud.wconnect.commonlib.service.SchedulerInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.workperformance.entity.mor.MorScheduler;
    import cloud.wconnect.workperformance.mapper.mor.MorSchedulerMapper;
    import cloud.wconnect.workperformance.pojo.dto.mor.scheduler.MorSchedulerDTO;
    import cloud.wconnect.workperformance.service.mor.IMorSchedulerService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import jakarta.annotation.Resource;

    import java.text.MessageFormat;
    import java.util.*;
    import java.util.concurrent.Executor;

    /**
     * <p>
     * mor 功能的调度数据 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-03-14
     */
    @Service
    @DS("work_performance")
    public class MorSchedulerServiceImpl extends ServiceImpl<MorSchedulerMapper, MorScheduler> implements IMorSchedulerService {
        @Resource
        private SchedulerInternalService schedulerInternalService;
        @Resource
        private UpdateLogUtil updateLogUtil;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private Executor executor;
        @Resource
        private ObjectMapper objectMapper;
        @Value("${default.timezone}")
        private String defaultTimezone;

        @Value("${default.first-day.sva-setting}")
        private Integer startOfWeek;

        private final static Integer MODULE_ID = MoudleIdConstant.WORK_PERFORMANCE;
        private final static Integer FUNCTION_ID = FunctionIdConstant.MONTHLY_OT_REPORT;

        /**
         * 查询调度任务的配置
         *
         * @param propertyId 资产ID
         * @param category   分类
         * @return 调度任务
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public List<MorScheduler> list(int propertyId, int category) {
            return list(new LambdaQueryWrapper<MorScheduler>()
                    .eq(MorScheduler::getPropertyId, propertyId)
                    .ne(MorScheduler::getStatus, SchedulerCons.STATUS_DELETING)
                    .eq(MorScheduler::getCategory, category));
        }

        /**
         * 校验是否存在
         *
         * @param id 调度任务 id
         * @return MorScheduler
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public MorScheduler checkExist(String id) {
            MorScheduler scheduler = getById(id);
            if (Objects.isNull(scheduler)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The MorScheduler is not found, id = " + id);
            }
            return scheduler;
        }

        /**
         * 校验归属
         *
         * @param propertyId 资产 id
         * @param scheduler  调度任务
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public void checkBelong(int propertyId, MorScheduler scheduler) {
            if (!scheduler.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("The MorScheduler not belong current property," +
                                        "schedulerId:{0},schedulerBelongPropertyId:{1},currentPropertyId:{2}",
                                scheduler.getId(), scheduler.getPropertyId(), propertyId));
            }
        }

        /**
         * 获取日期区间
         *
         * @param propertyDTO 资产DTO
         * @param type        邮件配置类型
         * @return java.util.Map<java.lang.String, java.lang.Long>
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public Map<String, Long> getDateRange(PropertyDTO propertyDTO, int type) {
            MorScheduler schedulerJob = getOne(new LambdaQueryWrapper<MorScheduler>()
                    .eq(MorScheduler::getPropertyId, propertyDTO.getId())
                    .eq(MorScheduler::getCategory, type));

            if (Objects.isNull(schedulerJob)) {
                return Collections.emptyMap();
            }

            String parameters = schedulerJob.getParameters();
            if (StringUtils.isBlank(parameters) || parameters.equals("{}")) {
                return Collections.emptyMap();
            }

            JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper, parameters, JobParameterDTO.class);

            // 解析日期表达式
            String timezone = propertyDTO.getTimezone();
            Map<String, Long> dateRange = JobParameterUtils.parseDateRange(jobParameterDTO.getDateRange(),
                    jobParameterDTO.getDateRangeUnit(), timezone, startOfWeek);
            dateRange.forEach((key, value) ->
                    dateRange.put(key, DateTimeUtils.parseToLong(DateTimeUtils.toZonedDateTime(value, timezone),
                            BaseConstant.DATE_FORMAT_YYYYMMDD))
            );
            return dateRange;
        }

        /**
         * 创建调度任务
         *
         * @param schedulerDTO 调度任务DTO
         * @param propertyDTO  资产DTO
         * @param operatorId   操作人 id
         * @return 调度任务id
         * @author zc
         * @date 2024/3/14
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public String save(MorSchedulerDTO schedulerDTO, PropertyDTO propertyDTO, int operatorId) {
            MorScheduler scheduler = beanMapper.map(schedulerDTO, MorScheduler.class, item -> {
                item.setRunTimes(BaseConstant.ZERO);
                item.setSyncState(schedulerDTO.getStatus() == SchedulerCons.STATUS_STOP);
                item.setUpdateLog(updateLogUtil.get(operatorId));
            });
            // 保存调度任务
            save(scheduler);
            String jobId = scheduler.getId();

            // 同步定时任务
            if (!scheduler.getSyncState()) {
                TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                    // 同步任务调度中心
                    try {
                        // 调用内部接口保存任务
                        callInternalServiceLaunchJob(StringUtils.EMPTY, scheduler, propertyDTO, defaultTimezone);
                        // 指定数据源
                        pushDynamicDataSource();
                        // 更新为已同步
                        updateSyncState(jobId, true);
                    } catch (Exception e) {
                        log.error("Launch mor scheduler job[" + jobId + "] failed", e);
                    } finally {
                        DynamicDataSourceContextHolder.clear();
                    }
                });
            }
            return jobId;
        }

        /**
         * 更新同步状态
         *
         * @param id        主键ID
         * @param syncState 同步状态
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public void updateSyncState(String id, boolean syncState) {
            updateById(new MorScheduler().setId(id).setSyncState(syncState));
        }

        /**
         * 修改调度任务
         *
         * @param propertyDTO     资产DTO
         * @param originScheduler 修改前的调度任务
         * @param schedulerDTO    修改后的调度任务DTO
         * @param operatorId      操作人 id
         * @return job id
         * @author zc
         * @date 2024/3/14
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public String update(PropertyDTO propertyDTO, MorScheduler originScheduler,
                             MorSchedulerDTO schedulerDTO, int operatorId) {
            MorScheduler scheduler = beanMapper.map(schedulerDTO, MorScheduler.class, item -> {
                item.setRunTimes(BaseConstant.ZERO);
                item.setSyncState(Boolean.FALSE);
                item.setUpdateLog(updateLogUtil.append(originScheduler.getUpdateLog(), operatorId));
            });

            String originJobId = originScheduler.getId();
            // 更新调度任务的 job id
            updateAndGenerateNewJobId(scheduler);
            String newJobId = scheduler.getId();

            // 同步任务调度中心
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                if (scheduler.getStatus() == SchedulerCons.STATUS_START) {
                    try {
                        // 调用内部接口保存任务
                        callInternalServiceLaunchJob(originJobId, scheduler, propertyDTO, defaultTimezone);
                        // 指定数据源
                        pushDynamicDataSource();
                        // 更新为已同步
                        updateSyncState(newJobId, true);
                    } catch (Exception e) {
                        log.error("Launch mor scheduler job[" + newJobId + "] failed", e);
                    } finally {
                        DynamicDataSourceContextHolder.clear();
                    }
                } else {
                    try {
                        // 调用内部接口删除任务
                        callInternalServiceDeleteJob(originJobId);
                        // 指定数据源
                        pushDynamicDataSource();
                        // 更新为已同步
                        updateSyncState(newJobId, true);
                    } catch (Exception e) {
                        log.error("Delete mor scheduler job[" + originJobId + "] failed", e);
                    } finally {
                        DynamicDataSourceContextHolder.clear();
                    }
                }
            });
            return newJobId;
        }

        /**
         * 更新调度任务和生成新的 job id
         *
         * @param schedulerJob 调度任务
         * @author zc
         * @date 2024/5/28
         */
        private void updateAndGenerateNewJobId(MorScheduler schedulerJob) {
            String newJobId = CommonUtils.randomUuidWithoutDashes();
            update(schedulerJob, new LambdaUpdateWrapper<MorScheduler>()
                    .set(MorScheduler::getId, newJobId)
                    .eq(MorScheduler::getId, schedulerJob.getId()));
            schedulerJob.setId(newJobId);
        }

        /**
         * 校验分类下最大可配置调度任务的数量
         *
         * @param propertyId 资产Id
         * @param category   分类
         * @param maxNum     最大数量
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public void checkMaxNum(int propertyId, int category, int maxNum) {
            long count = count(new LambdaQueryWrapper<MorScheduler>()
                    .eq(MorScheduler::getPropertyId, propertyId)
                    .eq(MorScheduler::getCategory, category)
                    .ne(MorScheduler::getStatus, SchedulerCons.STATUS_DELETING));
            if (count >= maxNum) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_EXISTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The MorSchedulerJob maximum configuration:{0}, propertyId{1}, category:{2}",
                                maxNum, propertyId, category));
            }
        }

        /**
         * 刷新调度任务同步状态为已同步
         *
         * @param ids 调度任务ID集合
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public void refreshSyncStateByIds(List<String> ids) {
            update(new UpdateWrapper<MorScheduler>().lambda()
                    .in(MorScheduler::getId, ids)
                    .set(MorScheduler::getSyncState, Boolean.TRUE));
        }

        /**
         * 调度任务执行次数递增
         *
         * @param jobId 主键ID
         * @author zc
         * @date 2024/3/14
         */
        @Override
        public void plusRunTimes(String jobId) {
            baseMapper.plusRunTimes(jobId);
        }

        /**
         * 调用内部接口删除任务
         *
         * @param jobId 任务ID
         * @author zc
         * @date 2024/3/14
         */
        private void callInternalServiceDeleteJob(String jobId) {
            schedulerInternalService.deleteJob(new JobBodyDTO().setJobId(jobId)
                    .setModuleId(MODULE_ID).setFunctionId(FUNCTION_ID));
        }

        /**
         * 调用内部接口保存任务
         *
         * @param originJobId     原调度任务 id
         * @param schedulerJob    调度任务对象
         * @param propertyDTO     资产DTO
         * @param defaultTimezone 默认时区
         * @author zc
         * @date 2024/3/14
         */
        private void callInternalServiceLaunchJob(String originJobId, MorScheduler schedulerJob, PropertyDTO propertyDTO,
                                                  String defaultTimezone) throws JsonProcessingException {
            JobBodyDTO task = new JobBodyDTO()
                    .setJobId(schedulerJob.getId()).setOriginJobId(originJobId)
                    .setModuleId(MODULE_ID).setFunctionId(FUNCTION_ID)
                    .setStartDate(schedulerJob.getStartDate())
                    .setCronRule(CronRuleUtil.generateCronRuleForScheduler(objectMapper.readValue(schedulerJob.getScheduler(), HashMap.class),
                            schedulerJob.getStartDate(), propertyDTO.getTimezone(), defaultTimezone));
            schedulerInternalService.launchJob(task);
        }

        /**
         * 设置数据源
         *
         * @author zc
         * @date 2024/3/16
         */
        private void pushDynamicDataSource() {
            DynamicDataSourceContextHolder.push("work_performance");
        }
    }