定时任务schedule结合xml文件邮件发送在微服务中的应用

本文详细介绍了如何在微服务环境中实现定时任务,包括需求分析、代码实现及其实例。用户可以通过设定cron表达式来指定任务执行时间,如在9:00-13:00期间发送停车超时30分钟以上的数据邮件。系统通过接收前端参数,利用后台接口执行任务,同时提供了任务的暂停、恢复、删除等功能。主要涉及的技术包括Cron表达式、Quartz Scheduler、邮件服务和数据库操作。
摘要由CSDN通过智能技术生成
定时任务schedule在微服务中的实际应用
需求模拟:

​ 现有一张车辆的启动时间和停车时间统计表,需要将 某个时间段内,停车间隔超过 某个设定值 的数据在 指定时间 通过 邮件 发送给客户。简单一点就是用户需要9:00-13:00的车辆停车时间超过30min的数据邮件,并且在13:00发送。

需求分析:

​ 1.停车超时时间最好不要在后台设定为一个固定值,建议另外添加一张表来保存数据,通过前台用户来设定,设定部分是常规单表的增删改,这里不做记录。

​ 2.通过前台写入执行时间、邮件对象的参数,后台接收并执行的接口,可以使用cron表达式,具体用法可以去网上查。

​ 3.数据分析:停车的数据可能出现四种情况

​ a.
在这里插入图片描述

​ b.
在这里插入图片描述

​ c.
在这里插入图片描述

​ d.
在这里插入图片描述

​ 说明:c、d两种情况可以合并为一种情况,下一次开始的时间超出统计的结束时间且本次停车的时间小于13:00减去30min(超时参数)的数据

代码分析:

​ 前台传入cron表达式、目标邮箱、目标id

​ 后台接收数据后,发送超时统计数据邮件,返回执行结果码

(前台用户输入部分省略,下面是后端主要代码部分)

controller部分这里使用的是rest风格,SysJob类是任务数据类,包含cron等信息,可自行定义

	/**
     * 新增定时任务
     */
    @PostMapping
    public AjaxResult add(@RequestBody SysJob sysJob) throws SchedulerException, TaskException
    {
        if (!CronUtils.isValid(sysJob.getCronExpression()))
        {
            return AjaxResult.error("cron表达式不正确");
        }
        return toAjax(jobService.insertJob(sysJob));
    }

	/**
     * 定时任务状态修改
     */
    @PutMapping("/changeStatus")
    public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException
    {
        SysJob newJob = jobService.selectJobById(job.getJobId());
        newJob.setStatus(job.getStatus());
        return toAjax(jobService.changeStatus(newJob));
    }

    /**
     * 定时任务立即执行一次
     */
    @PutMapping("/run")
    public AjaxResult run(@RequestBody SysJob job) throws SchedulerException
    {
        jobService.run(job);
        return AjaxResult.success();
    }

CronUtils工具类

import java.text.ParseException;
import java.util.Date;
import org.quartz.CronExpression;

/**
 * cron表达式工具类
 */
public class CronUtils
{
    /**
     * 返回一个布尔值代表一个给定的Cron表达式的有效性
     *
     * @param cronExpression Cron表达式
     * @return boolean 表达式是否有效
     */
    public static boolean isValid(String cronExpression)
    {
        return CronExpression.isValidExpression(cronExpression);
    }

    /**
     * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
     *
     * @param cronExpression Cron表达式
     * @return String 无效时返回表达式错误描述,如果有效返回null
     */
    public static String getInvalidMessage(String cronExpression)
    {
        try
        {
            new CronExpression(cronExpression);
            return null;
        }
        catch (ParseException pe)
        {
            return pe.getMessage();
        }
    }

    /**
     * 返回下一个执行时间根据给定的Cron表达式
     *
     * @param cronExpression Cron表达式
     * @return Date 下次Cron表达式执行时间
     */
    public static Date getNextExecution(String cronExpression)
    {
        try
        {
            CronExpression cron = new CronExpression(cronExpression);
            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
        }
        catch (ParseException e)
        {
            throw new IllegalArgumentException(e.getMessage());
        }
    }
}

系统任务impl部分

@Service
public class SysJobServiceImpl implements ISysJobService
{	
    //Scheduler定时任务类,系统自带
    @Autowired
    private Scheduler scheduler;
	//定时任务mapper这里不列写了
    @Autowired
    private SysJobMapper jobMapper;
     
    /**
     * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
     */
    @PostConstruct
    public void init() throws SchedulerException, TaskException, ParseException {
        scheduler.clear();
        List<SysJob> jobList = jobMapper.selectJobAll();
        for (SysJob job : jobList)
        {
            if(ScheduleUtils.isValidateCanDoExpression(job.getCronExpression())){
                ScheduleUtils.createScheduleJob(scheduler, job);
            }

        }
    }
    /**
     * 暂停任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)//事务回滚
    public int pauseJob(SysJob job) throws SchedulerException
    {
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
        int rows = jobMapper.updateJob(job);
        if (rows > 0)
        {
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
        return rows;
    }
    
     /**
     * 恢复任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int resumeJob(SysJob job) throws SchedulerException
    {
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
        int rows = jobMapper.updateJob(job);
        if (rows > 0)
        {
            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
        return rows;
    }
    
     /**
     * 删除任务后,所对应的trigger也将被删除
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteJob(SysJob job) throws SchedulerException
    {
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        int rows = jobMapper.deleteJobById(jobId);
        if (rows > 0)
        {
            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
        return rows;
    }
    
    /**
     * 任务调度状态修改
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int changeStatus(SysJob job) throws SchedulerException
    {
        int rows = 0;
        String status = job.getStatus();
        if (ScheduleConstants.Status.NORMAL.getValue().equals(status))
        {
            rows = resumeJob(job);
        }
        else if (ScheduleConstants.Status.PAUSE.getValue().equals(status))
        {
            rows = pauseJob(job);
        }
        return rows;
    }
    
    /**
     * 立即运行任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void run(SysJob job) throws SchedulerException
    {
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        SysJob properties = selectJobById(job.getJobId());
        // 参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
        scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap);
    }

    /**
     * 新增任务
     *
     * @param job 调度信息 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertJob(SysJob job) throws SchedulerException, TaskException
    {
        if(job.getUserId()==null){
            job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
        }
        int rows = jobMapper.insertJob(job);
        if (rows > 0)
        {
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
        return rows;
    }

    /**
     * 更新任务的时间表达式
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateJob(SysJob job) throws SchedulerException, TaskException
    {
        SysJob properties = selectJobById(job.getJobId());
        int rows = jobMapper.updateJob(job);
        if (rows > 0)
        {
            updateSchedulerJob(job, properties.getJobGroup());
        }
        return rows;
    }

    /**
     * 更新任务
     *
     * @param job 任务对象
     * @param jobGroup 任务组名
     */
    public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException
    {
        Long jobId = job.getJobId();
        // 判断是否存在
        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
        if (scheduler.checkExists(jobKey))
        {
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(jobKey);
        }
        ScheduleUtils.createScheduleJob(scheduler, job);
    }

    /**
     * 校验cron表达式是否有效
     *
     * @param cronExpression 表达式
     * @return 结果
     */
    @Override
    public boolean checkCronExpressionIsValid(String cronExpression)
    {
        return CronUtils.isValid(cronExpression);
    }

    @Override
    public List<Long> queryJobIdsByUserId(Integer userId) {
        return  jobMapper.queryJobIdsByUserId(userId);
    }
    
}
/**
 * 任务调度通用常量
 */
public class ScheduleConstants
{
    public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";

    /** 执行目标key */
    public static final String TASK_PROPERTIES = "TASK_PROPERTIES";

    /** 默认 */
    public static final String MISFIRE_DEFAULT = "0";

    /** 立即触发执行 */
    public static final String MISFIRE_IGNORE_MISFIRES = "1";

    /** 触发一次执行 */
    public static final String MISFIRE_FIRE_AND_PROCEED = "2";

    /** 不触发立即执行 */
    public static final String MISFIRE_DO_NOTHING = "3";

    public enum Status
    {
        /**
         * 正常
         */
        NORMAL("0"),
        /**
         * 暂停
         */
        PAUSE("1");

        private String value;

        private Status(String value)
        {
            this.value = value;
        }

        public String getValue()
        {
            return value;
        }
    }
}

定时任务工具类:

/**
 * 定时任务工具类
 *
 * @author foms
 *
 */
public class ScheduleUtils
{
    /**
     * 得到quartz任务类
     *
     * @param sysJob 执行计划
     * @return 具体执行任务类
     */
    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
    {
        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }

    /**
     * 构建任务触发对象
     */
    public static TriggerKey getTriggerKey(Long jobId, String jobGroup)
    {
        //通过task类名获取对应的任务类容
        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 构建任务键对象
     */
    public static JobKey getJobKey(Long jobId, String jobGroup)
    {
        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
    {
        Class<? extends Job> jobClass = getQuartzJobClass(job);
        // 构建job信息
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();

        // 表达式调度构建器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);

        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
                .withSchedule(cronScheduleBuilder).build();

        // 放入参数,运行时的方法可以获取
        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);

        // 判断是否存在
        if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
        {
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(getJobKey(jobId, jobGroup));
        }

        scheduler.scheduleJob(jobDetail, trigger);

        // 暂停任务
        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
        {
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
    }

    /**
     * 设置定时任务策略
     */
    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
            throws TaskException
    {
        switch (job.getMisfirePolicy())
        {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
                        + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
        }
    }

    /**
     * 校验cron表达式是否能执行
     * @param cron
     * @return
     */
    public static boolean isValidateCanDoExpression(String cron) throws ParseException {
        //先校验cron表达式格式是否正确
        if(!isValidExpression(cron)) {
            return false;
        }
        CronTriggerImpl triggerImpl = new CronTriggerImpl();
        try {
            triggerImpl.setCronExpression(cron);
        } catch (ParseException e) {
            return false;
        }
        Date date = triggerImpl.computeFirstFireTime(null);
        return date != null && date.after(new Date());
    }

    /**
     * 校验cron表达式格式
     * @param cron
     * @return
     */
    public static boolean isValidExpression(String cron){
        if(StringUtils.isEmpty(cron)){
            return false;
        }
        return CronExpression.isValidExpression(cron);
    }

}

task类(自定义调度方法,方法名与前台参数相同)

给定时任务添加任务、具体的数据处理

@Component("parkingTimeOutTask")
public class ParkingTimeOutTask {
     @Autowired
    private ParkingTimeOutService parkingTimeOutService;
    
    // 9.00-13.00 停车超时数据
    public void executeParkingTimeOutData(Long deptId, String emails){
        Date date = new Date();
        String msg = "9.00-13.00停车超时数据";
        log.info("9.00-13.00 停车超时统计数据 start ... [{}]", DateUtil.now());
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                log.info("停车超时统计数据执行时间 [{}]", DateUtil.now());
                //ParkingTimeOutForm为超时数据查询参数表单,具体省略
                ParkingTimeOutForm form = new ParkingTimeOutForm();
                form.setDeptId(deptId);
                //任务设置为在13:00执行,所以开始时间为当前时间(任务执行时的时间)减去240min
                form.setBeginTime(DateUtil.formatDateTime(DateUtil.offsetMinute(date, -240)));
                //结束时间为当前时间
                form.setEndTime(DateUtil.formatDateTime(date));
                //ParkingTimeOutService的实现方法,传入查询表单form,邮箱地址emails,邮件标题msg
                parkingTimeOutService.executeParkingTimeOutData(form, emails,msg);
            }
        }, TimeUnit.MINUTES.toMillis(0));
        log.info("停车超时统计数据 end");
    }
}

接下来是数据处理,以及发送邮件的impl类:

@Slf4j
@Service
public class ParkingTimeOutServiceImpl implements IParkingTimeOutService {
	//邮件参数自己设置
    @Value("${邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀>}")
    private String host;
    @Value("${邮件服务器的SMTP端口,可选,默认25}")
    private Integer port;
    @Value("${发件人(必须正确,否则发送失败)}")
    private String from;
    @Value("${账号}")
    private String user;
    @Value("${密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)}")
    private String pass;
    @Value("${使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。}")
    private boolean starttlsEnable;

    private MailAccount account;

    @Autowired
    private RemoteTimeOutSettingService TimeOutSettingService;
    
    //邮箱参数
    @PostConstruct
    public void init(){
        account = new MailAccount();
        account.setHost(host);
        account.setPort(port);
        account.setUser(user);
        account.setPass(pass);
        account.setFrom(from);
        account.setStarttlsEnable(starttlsEnable);
    }
    
    @Override
    public void executeParkingTimeoutData(ParkingTimeOutForm form, String emails,String msg) {
    	//VParkingTimeout是xml表格报表参数类,发送的邮件附带的是此xml格式的文件,可按实际需求设定。remoteSettingService通过feign调用停车时间数据服务
    	//说明:这里的停车时间数据位于另外一个模块,需要用feign调用,调用方法这里不做介绍。
        List<VParkingTimeout> list = remoteSettingService.getParkingTimeoutList(form).getData();
        log.info(" %s点停车超时统计 list ===> [{}]", JSON.toJSONString(list));
        //判断内容是否为空
        if (CollUtil.isNotEmpty(list)) {
            timeoutSettingDataWriteToExcel(msg, emails, list);
        }else {
            log.info("停车超时数据为空");
        }
    }
    
    //将数据变成xml格式,添加到邮件发送的方法
     private void timeoutSettingDataWriteToExcel(String subject, String emails, List<VParkingTimeout> list){
        String path = FileUtil.getTmpDirPath()
                + File.separator
                + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN)
                + RandomUtil.randomNumbers(4)
                + ".xlsx";
        File file = FileUtil.file(path);
        ExcelWriter writer = new ExcelWriter(true, subject);
        writer.addHeaderAlias("id","序号");
        writer.addHeaderAlias("Name","车辆编号");
        writer.addHeaderAlias("Number","车牌号");
        writer.addHeaderAlias("driverNameBefore","停车前驾驶员姓名");
        writer.addHeaderAlias("driverIcBefore","停机前驾驶员工号");
        writer.addHeaderAlias("startTime","停机开始时间");
        writer.addHeaderAlias("shutdown","停机结束时间");
        writer.addHeaderAlias("parkingTime","停机时长");
        writer.addHeaderAlias("driverNameAfter","停机后驾驶员姓名");
        writer.addHeaderAlias("driverIcAfter","停机后驾驶员工号");
        writer.write(list);
        writer.flush(file);
        String content = String.format("尊敬的客户您好:为你统计的数据为%s,详情请查看附件", subject);
        MailUtil.send(account, emails, subject, content, false, file);
        log.info("邮件发送成功");
    }
    
}

车辆数据模块的超时数据获取impl

@Override
    public List<VParkingTimeout> getParkingTimeoutList(ParkingTimeOutForm form) {
        long t = System.currentTimeMillis();
        //获取设定超时时间
        //这里也使用feign调用最开始设置的那张超时设置单表的模块的方法
        Long timeoutSetting = (remoteSysReportParameterService.getParameter(form.getDeptId())).getData();
        //FmsCarDeviceRunRecordForm为车辆停车开车时间统计数据表单
        FmsCarDeviceRunRecordForm runRecordForm = new FmsCarDeviceRunRecordForm();
        List<VParkingTimeout> timeoutList = new ArrayList<>();
        //通过信息表单设置查询表单
        runRecordForm.setDeptId(form.getDeptId());//此处根据实际需求设置
        runRecordForm.setStartTime(form.getBeginTime());
        runRecordForm.setEndTime(form.getEndTime());
        //通过表单信息获取数据集合
        //当前类的selectFmsCarDeviceRunRecordListIntact为获取数据库中停车间隔超过设定值的数据的方法
        List<FmsCarDeviceRunRecord> runRecordList = this.selectFmsCarDeviceRunRecordListIntact(runRecordForm);
        // 停车间隔为,下次的开始时间减去本次的结束时间
        if (CollUtil.isNotEmpty(runRecordList)){
            Long n = 0L;
            for (int i = 0; i < runRecordList.size()-1; i++) {
                //停车间隔时间(单位:分钟)
                long pTime = (runRecordList.get(i).getEndDateTime().getTime()-runRecordList.get(i+1).getStartDateTime().getTime())/(1000*60);
                //根据条件构建数据并添加到集合
                if(pTime>timeoutSetting){
                    VParkingTimeout timeout = new VParkingTimeout();
                    //设置参数
                    n++;
                    timeout.setId(n);
                  timeout.setDriverIcBefore(runRecordList.get(i).getDriverIc());
                    timeout.setDriverIcAfter(runRecordList.get(i+1).getDriverIc());
                    timeout.setDriverNameBefore(runRecordList.get(i).getFmsDriver().getDriverName());
                    timeout.setDriverNameAfter(runRecordList.get(i+1).getFmsDriver().getDriverName());
                    timeout.setCarName(runRecordList.get(i).getFmsCar().getCarName());
                    timeout.setLicencePlateNumber(runRecordList.get(i).getFmsCar().getLicensePlateNumber());
                    timeout.setShutdown(runRecordList.get(i).getEndDateTime().toString());
                   
timeout.setStartTime(runRecordList.get(i+1).getStartDateTime().toString());
                    timeout.setParkingTime(pTime);
                    //添加到集合
                    timeoutList.add(timeout);
                }
            }
            System.out.println("excution: " + (System.currentTimeMillis() - t) );
            return timeoutList;
        }
        System.out.println("excution2: " + (System.currentTimeMillis() - t) );
        return null;
    }
 timeout.setLicencePlateNumber(runRecordList.get(i).getFmsCar().getLicensePlateNumber());
                    timeout.setShutdown(runRecordList.get(i).getEndDateTime().toString());
                    timeout.setStartTime(runRecordList.get(i+1).getStartDateTime().toString());
                    timeout.setParkingTime(pTime);
                    //添加到集合
                    timeoutList.add(timeout);
                }
            }
            System.out.println("excution: " + (System.currentTimeMillis() - t) );
            return timeoutList;
        }
        System.out.println("excution2: " + (System.currentTimeMillis() - t) );
        return null;
    }

至此,整个业务的核心代码与逻辑处理完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值