# ClcSchedulerServiceImpl.java

    package cloud.wconnect.frontoffice.service.clc.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.*;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    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.frontoffice.entity.clc.ClcSchedulerJob;
    import cloud.wconnect.frontoffice.mapper.clc.ClcSchedulerJobMapper;
    import cloud.wconnect.frontoffice.pojo.dto.clc.ClcSchedulerDTO;
    import cloud.wconnect.frontoffice.service.clc.IClcSchedulerService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
    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.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.HashMap;
    import java.util.List;
    import java.util.Objects;
    import java.util.concurrent.Executor;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2024-03-12
     */
    @Service
    @DS("logbook")
    public class ClcSchedulerServiceImpl extends ServiceImpl<ClcSchedulerJobMapper, ClcSchedulerJob> implements IClcSchedulerService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private UpdateLogUtil updateLogUtil;
        @Autowired
        private Executor executor;
        @Value("${default.timezone}")
        private String defaultTimezone;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private SchedulerInternalService schedulerInternalService;

        private static final String DATASOURCE_LOGBOOK = "logbook";

        /**
         * 保存定时任务
         *
         * @param propertyDTO  资产
         * @param schedulerDTO 定时任务
         * @param userId       操作人
         * @return 定时任务 id
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public String save(PropertyDTO propertyDTO, ClcSchedulerDTO schedulerDTO, int userId) {
            Integer propertyId = propertyDTO.getId();
            ClcSchedulerJob scheduler = beanMapper.map(schedulerDTO, ClcSchedulerJob.class);
            scheduler.setPropertyId(propertyId).setRunTimes(0).setSyncState(schedulerDTO.getStatus() == BaseConstant.ZERO)
                    .setUpdateLog(updateLogUtil.get(userId));
            save(scheduler);
            String schedulerId = scheduler.getId();

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

            return schedulerId;
        }

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

        /**
         * 修改同步状态
         *
         * @param scheduleId 定时任务 id
         * @param syncState  同步状态
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        public void updateSyncState(String scheduleId, boolean syncState) {
            updateById(new ClcSchedulerJob().setId(scheduleId).setSyncState(syncState));
        }

        /**
         * 检查存在
         *
         * @param id 定时任务 id
         * @return 定时任务
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        public ClcSchedulerJob checkExist(String id) {
            ClcSchedulerJob scheduler = getById(id);
            if (Objects.isNull(scheduler)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the clcSchedulerJob is not found;id:" + id);
            }
            return scheduler;
        }

        /**
         * 修改定时任务
         *
         * @param propertyDTO  资产
         * @param scheduler    定时任务
         * @param schedulerDTO 定时任务修改内容
         * @param userId       操作人
         * @return job id
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public String update(PropertyDTO propertyDTO, ClcSchedulerJob scheduler, ClcSchedulerDTO schedulerDTO, int userId) {
            ClcSchedulerJob updateScheduler = beanMapper.map(schedulerDTO, ClcSchedulerJob.class);
            updateScheduler.setId(scheduler.getId());
            updateScheduler.setRunTimes(BaseConstant.ZERO);
            updateScheduler.setUpdateLog(updateLogUtil.append(scheduler.getUpdateLog(), userId));
            // 设为未同步
            updateScheduler.setSyncState(false);

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

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

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

        /**
         * 执行内部接口更新（删除）任务
         *
         * @param schedulerId 定时任务 id
         * @author yyt
         * @date 2024/3/13
         */
        private void callInternalServiceDeleteJob(String schedulerId) {
            schedulerInternalService.deleteJob(new JobBodyDTO().setJobId(schedulerId)
                    .setModuleId(MoudleIdConstant.FRONT_OFFICE).setFunctionId(FunctionIdConstant.CLC));
        }

        /**
         * 批量刷新同步状态为已同步
         *
         * @param updateJobIds 定时任务 id
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        public void refreshSyncStateByIds(List<String> updateJobIds) {
            update(new ClcSchedulerJob().setSyncState(true), new QueryWrapper<ClcSchedulerJob>()
                    .lambda().in(ClcSchedulerJob::getId, updateJobIds));
        }

        /**
         * 查询指定资产、指定分类下的定时任务配置
         *
         * @param propertyId 资产 id
         * @param category   分类
         * @return 定时任务配置
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        public ClcSchedulerJob getByPropertyIdAndCategory(Integer propertyId, int category) {
            return getOne(new QueryWrapper<ClcSchedulerJob>().lambda().eq(ClcSchedulerJob::getPropertyId, propertyId)
                    .eq(ClcSchedulerJob::getCategory, category));
        }

        /**
         * 调度任务执行次数递增
         *
         * @param jobId 定时任务 id
         * @author yyt
         * @date 2024/3/12
         */
        @Override
        public void plusRunTimes(String jobId) {
            baseMapper.plusRunTimes(jobId);
        }

        /**
         * 获取定时任务配置的数据区间范围
         *
         * @param propertyDTO 资产
         * @param category    分类
         * @return 数据区间范围
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        public Long getDateRange(PropertyDTO propertyDTO, int category) {
            ClcSchedulerJob schedulerJob = getByPropertyIdAndCategory(propertyDTO.getId(), category);
            String parameters = schedulerJob.getParameters();
            JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper, parameters, JobParameterDTO.class);
            return JobParameterUtils.parseDateRange(jobParameterDTO.getDateRange(), jobParameterDTO.getDateRangeUnit(), propertyDTO.getTimezone(), null)
                    .get(JobParameterDTO.START_DATE);
        }

        /**
         * 删除定时任务
         *
         * @param propertyDTO 资产
         * @param scheduler   定时任务
         * @author yyt
         * @date 2024/3/13
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void delete(PropertyDTO propertyDTO, ClcSchedulerJob scheduler) {
            String schedulerId = scheduler.getId();
            updateById(new ClcSchedulerJob().setId(schedulerId).setStatus(SchedulerCons.STATUS_DELETING)
                    .setSyncState(false));

            // 同步任务调度中心
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                try {
                    // 调用内部接口删除任务
                    callInternalServiceDeleteJob(schedulerId);
                    // 指定数据源
                    DynamicDataSourceContextHolder.push(DATASOURCE_LOGBOOK);
                    // 删除记录
                    removeById(schedulerId);
                } catch (Exception e) {
                    log.error("Delete clc scheduler job[" + schedulerId + "] failed", e);
                } finally {
                    DynamicDataSourceContextHolder.clear();
                }
            });
        }

        /**
         * 查询未删除的定时任务
         *
         * @param propertyId 资产 id
         * @param category   分类
         * @return 定时任务
         * @author yyt
         * @date 2024/11/19
         **/
        @Override
        public List<ClcSchedulerJob> list(Integer propertyId, int category) {
            return list(new QueryWrapper<ClcSchedulerJob>().lambda()
                    .eq(ClcSchedulerJob::getPropertyId, propertyId)
                    .eq(ClcSchedulerJob::getCategory, category)
                    .ne(ClcSchedulerJob::getStatus, SchedulerCons.STATUS_DELETING));
        }

        /**
         * 检查分类下的定时任务是否超过限制数量
         *
         * @param propertyId 资产 id
         * @param category   分类
         * @param limitCount 限制数量
         * @author yyt
         * @date 2024/11/19
         **/
        @Override
        public void checkCount(Integer propertyId, Integer category, int limitCount) {
            long totalCount = count(new QueryWrapper<ClcSchedulerJob>().lambda()
                    .eq(ClcSchedulerJob::getPropertyId, propertyId)
                    .eq(ClcSchedulerJob::getCategory, category)
                    .ne(ClcSchedulerJob::getStatus, SchedulerCons.STATUS_DELETING));
            if (totalCount == limitCount) {
                throw new BadRequestException(CommonErrorI18nConstant.SCHEDULER_COUNT_TRANSFINITE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Creating timing configuration rules exceeds number limit.");
            }
        }

        /**
         * 检查已存在的定时任务
         *
         * @param propertyId 资产 id
         * @param category   分类
         * @param excludeId  排除自身
         * @return 定时任务
         * @author yyt
         * @date 2024/11/19
         **/
        @Override
        public List<ClcSchedulerJob> listExistJob(Integer propertyId, Integer category, String excludeId) {
            return list(new QueryWrapper<ClcSchedulerJob>().lambda()
                    .eq(ClcSchedulerJob::getPropertyId, propertyId)
                    .eq(ClcSchedulerJob::getCategory, category)
                    .ne(ClcSchedulerJob::getStatus, SchedulerCons.STATUS_DELETING)
                    .ne(!excludeId.equals("0"), ClcSchedulerJob::getId, excludeId));
        }

    }