定时任务
在实际项目开发中,除了Web应用、SOA服务外,还有一类不可缺少的,那就是定时任务调度。定时任务的场景可以说非常广泛:
-
某些网站会定时发送优惠邮件;
-
银行系统还款日信用卡催收款;
-
某些应用的生日祝福短信等。
那究竟何为定时任务调度,一句话概括就是:基于给定的时间点、给定的时间间隔、自动执行的任务
2.3.1 入门案例
-
修改模块引导类,开启SpringTask功能支持
@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) @MapperScan("com.tanhua.admin.mapper") @EnableScheduling //开启定时任务支持 public class AdminServerApplication { public static void main(String[] args) { SpringApplication.run(AdminServerApplication.class,args); } }
配置定时任务类
@Component public class AnalysisTask { /** * 配置时间规则 */ @Scheduled( cron = "0/20 * * * * ? ") public void analysis() throws ParseException { //业务逻辑 String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); System.out.println("当前时间:"+time); } }
对于定时任务,我们使用的时候主要是注重两个方面,一个是定时任务的业务,另一个就是Cron表达式。
是否必须 | 允许值 | 特殊字符 | |
---|---|---|---|
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
时 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | , - * ? / L W C |
月 | 是 | 1-12 或 JAN-DEC | , - * / |
周 | 是 | 1-7 或 SUN-SAT | , - * ? / L C # |
月份和星期的名称是不区分大小写的。FRI 和 fri 是一样的。
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.tanhua.model.admin.Log; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; @Repository public interface LogMapper extends BaseMapper<Log> { /** * 根据操作时间和类型统计日志统计用户数量 * * @param type * @param logTime * @return */ @Select("SELECT COUNT(DISTINCT user_id) FROM tb_log WHERE TYPE=#{type} AND log_time=#{logTime}") Integer queryByTypeAndLogTime(@Param("type") String type, @Param("logTime") String logTime); /** * 根据时间统计用户数量 * * @param logTime * @return */ @Select("SELECT COUNT(DISTINCT user_id) FROM tb_log WHERE log_time=#{logTime}") Integer queryByLogTime(String logTime); /** * 查询次日留存 , 从昨天活跃的用户中查询今日活跃用户 * * @param today * @param yestoday * @return */ @Select("SELECT COUNT(DISTINCT user_id) FROM tb_log WHERE log_time=#{today} AND user_id IN (SELECT user_id FROM tb_log WHERE TYPE='0102' AND log_time=#{yestoday})") Integer queryNumRetention1d(@Param("today") String today, @Param("yestoday") String yestoday); }
为了方便操作,可以通过以下单元测试方法。保存若干操作数据
import com.tanhua.manager.domain.Log; import com.tanhua.manager.mapper.LogMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Random; @RunWith(SpringRunner.class) @SpringBootTest public class LogTest { @Autowired private LogMapper logMapper; private String logTime = ""; //模拟登录数据 public void testInsertLoginLog() { for (int i = 0; i < 5; i++) { Log log = new Log(); log.setUserId((long)(i+1)); log.setLogTime(logTime); log.setType("0101"); logMapper.insert(log); } } //模拟注册数据 public void testInsertRegistLog() { for (int i = 0; i < 10; i++) { Log log = new Log(); log.setUserId((long)(i+1)); log.setLogTime(logTime); log.setType("0102"); logMapper.insert(log); } } //模拟其他操作 public void testInsertOtherLog() { String[] types = new String[]{"0201","0202","0203","0204","0205","0206","0207","0301","0302","0303","0304"}; for (int i = 0; i < 10; i++) { Log log = new Log(); log.setUserId((long)(i+1)); log.setLogTime(logTime); int index = new Random().nextInt(10); log.setType(types[index]); logMapper.insert(log); } } @Test public void generData() { testInsertLoginLog(); testInsertRegistLog(); testInsertOtherLog(); } }
@Component public class AnalysisTask { @Autowired private AnalysisService analysisService; /** * 配置时间规则 * 在学习测试时,可以将时间间隔设置相对短一些 */ @Scheduled( cron = "0/20 * * * * ? ") public void analysis() throws ParseException { //业务逻辑 String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); System.out.println("开始统计:"+time); //调logService完成日志统计 analysisService.analysis(); System.out.println("结束统计"); } }
配置AnalysisService
import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.tanhua.admin.mapper.AnalysisMapper; import com.tanhua.admin.mapper.LogMapper; import com.tanhua.model.admin.Analysis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.expression.ParseException; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.Date; /** * @author Administrator */ @Service public class AnalysisService { @Autowired private AnalysisMapper analysisMapper; @Autowired private LogMapper logMapper; /** * 定时统计日志数据到统计表中 * 1、查询tb_log表中的数 (每日注册用户数,每日登陆用户,活跃的用户数据,次日留存的用户) * 2、构造AnalysisByDay对象 * 3、完成统计数据的更新或者保存 */ public void analysis() throws ParseException { //1、定义查询的日期 String todayStr = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); String yesdayStr = DateUtil.yesterday().toString("yyyy-MM-dd"); //2、统计数据-注册数量 Integer regCount = logMapper.queryByTypeAndLogTime("0102", todayStr); //3、统计数据-登录数量 Integer loginCount = logMapper.queryByTypeAndLogTime("0101", todayStr); //4、统计数据-活跃数量 Integer activeCount = logMapper.queryByLogTime(todayStr); //5、统计数据-次日留存 Integer numRetention1d = logMapper.queryNumRetention1d(todayStr, yesdayStr); //6、根据日期查询数据 QueryWrapper<Analysis> qw = new QueryWrapper<Analysis>(); qw.eq("record_date",new SimpleDateFormat("yyyy-MM-dd").parse(todayStr)); //7、构造Analysis对象 Analysis analysis = analysisMapper.selectOne(qw); //8、如果存在,更新,如果不存在保存 if(analysis != null) { analysis.setNumRegistered(regCount); analysis.setNumLogin(loginCount); analysis.setNumActive(activeCount); analysis.setNumRetention1d(numRetention1d); analysisMapper.updateById(analysis); }else { analysis = new Analysis(); analysis.setNumRegistered(regCount); analysis.setNumLogin(loginCount); analysis.setNumActive(activeCount); analysis.setNumRetention1d(numRetention1d); analysis.setRecordDate(new SimpleDateFormat("yyyy-MM-dd").parse(todayStr)); analysis.setCreated(new Date()); analysisMapper.insert(analysis); } } }
创建LogMapper并配置查询方法