在實際項目開發中,除了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並配置查詢方法
