Quartz 定時任務
最近自己負責的公司專案中業務上需要用到定時任務,所以對定時任務做一個總結。
問題分析
有這樣一個業務邏輯:使用者登入上傳一個加密檔案,後端程式碼解析檔案後從伺服器打包幾個檔案放到一個pkg/$SESSIONID
資料夾中,使用者在瀏覽器端點選“下載”按鈕,將伺服器上pkg/$SESSIONID
中的檔案下載到本地,之後刪除$SESSIONID
資料夾。另外如果使用者在點選“下載”按鈕之前就點選了“退出登入”按鈕,則也應該清空$SESSIONID
資料夾。
就是這麼簡單的一個業務邏輯,但卻發現存在一個 Bug,那就是當用戶登入提交加密檔案後,伺服器上建立了$SESSIONID
資料夾,使用者卻既沒有點選“下載”按鈕,也沒有點選“退出登入”按鈕,那麼當此次會話自動過期時,該會話對應的臨時目錄將永遠得不到刪除。如果這種現象經常出現,將會導致伺服器上積累大量臨時檔案佔用磁碟資源。
我想到的解決辦法是使用定時任務,定時掃描那些存在時間過長的檔案並將其刪除。另外更簡便的方法是編寫 Shell 指令碼,定期刪除檔案。此處我使用Quartz
定時任務完成這一目的。
引入 quartz 依賴
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency>
編寫 quartz 配置檔案
總的來說在該配置檔案中就做三件事:
- 建立任務,指定執行任務的目標物件和目標方法
- 建立觸發器,繫結任務,並指定該觸發器何時被觸發,從而執行任務
- 建立排程器
spring-quartz.xml
:
<?xml version="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scanbase-package="com.adups.controller"/> <!-- 建立任務 --> <beanclass="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"id="cleanFileJobDetail"> <!-- 目標物件 --> <propertyname="targetObject"ref="cleanController"/> <!-- 目標方法 --> <propertyname="targetMethod"value="cleanFile"/> </bean> <!-- 建立SimpleTrigger觸發器 --> <!--<bean class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean" id="simpleTrigger">--> <!-- 引用任務 --> <!--<property name="jobDetail" ref="jobDetail"/>--> <!-- 指定迴圈時間,以秒為單位 --> <!--<property name="repeatInterval" value="10000"/>--> <!--</bean>--> <!-- 建立 CronTrigger 觸發器 --> <beanclass="org.springframework.scheduling.quartz.CronTriggerFactoryBean"id="cleanFileCronTrigger"> <!-- 引用任務 --> <propertyname="jobDetail"ref="cleanFileJobDetail"/> <!--指定 Cron 表示式-每天凌晨 3 點執行一次 --> <propertyname="cronExpression"value="0 0 3 * * ?"/> </bean> <!-- 建立排程器 --> <beanclass="org.springframework.scheduling.quartz.SchedulerFactoryBean"id="stdScheduler"lazy-init="false"> <propertyname="triggers"> <list> <!--<ref bean="simpleTrigger"/>--> <refbean="cleanFileCronTrigger"/> </list> </property> </bean> </beans>
關於 Cron 表示式下面的例子足夠全面:
表示式 | 含義 |
---|---|
*/5 * * * * ? | 每隔5秒執行一次 |
0 */1 * * * ? | 每隔1分鐘執行一次 |
0 0 23 * * ? | 每天23點執行一次 |
0 0 23 ? * * | 每天23點執行一次 |
0 0 23 * * ? * | 每天23點執行一次 |
0 0 23 * * ? 2016 | 2016年每天23點執行一次 |
0 0 1 * * ? | 每天1點執行一次 |
0 0 1 1 * ? | 每月1號1點執行一次 |
0 * 14 * * ? | 每天14:00點到14:59期間,每隔1分鐘執行一次 |
0 0-5 14 * * ? | 每天14:00點到14:05期間,每隔1分鐘執行一次 |
0 0 23 L * ? | 每月最後一天23點執行一次 |
0 0 1 ? * L | 每週星期六1點執行一次 |
0 26,29,33 * * * ? | 在26分、29分、33分執行一次 |
0 0 0,13,18,21 * * ? | 每天的0點、13點、18點、21點都執行一次 |
0 0 0 ? * 6#3 | 每月第3個星期六 |
如果你想了解這些例子的語法規則,參考下面兩張表格,但我覺得根本沒必要,直接使用上面表格中的例子反而來的簡單直接
欄位 | 可選值 | 特殊字元 |
---|---|---|
秒 | 0 - 59 | , - * / |
分 | 0 - 59 | , - * / |
時 | 0 - 23 | , - * / |
日 | 1 - 31 | , - * / ? L W |
月 | 1 - 12 | , - * / |
周 | 1(Sun) - 7(Sat) | , - * / ? L # |
年(可選) | 1970 - 2099 | , - * / |
字元 | 說明 |
---|---|
, | 指定多個數值 |
- | 指定一個範圍 |
* | 代表整個時間段 |
/ | 多長時間執行一次 |
? | 不確定的值 |
L | 每月最後一天(日)/每月最後一個星期六(周) |
W | 最近的工作日 |
# | 每月第N個工作日 |
啟動類引入配置檔案
@SpringBootApplication // = @Configuration + @EnableAutoConfiguration + @ComponentScan @ImportResource("classpath:spring-quartz.xml") @MapperScan("com.adups.**.dao") public class App{ public static void main( String[] args ) { ApplicationContext context = SpringApplication.run(App.class, args); // 啟動時載入(其實不用手動呼叫,專案啟動後,定時任務就會被立即執行一次,所以下面兩行程式碼可以註釋) CleanController cleanController = context.getBean("cleanController", CleanController.class); cleanController.cleanFile(); } }
編寫要執行的任務程式碼
/** *@Author: Xinling Jing *@Date: 2018/10/8 0008 16:19 */ @Controller public class CleanController{ private Logger logger = LoggerFactory.getLogger(CleanController.class); @Autowired private UDisk uDisk; public void cleanFile() { File pkg = new File(uDisk.getPkgPath()); Long currentTime = System.currentTimeMillis(); File[] files = pkg.listFiles(pathname -> { Long modifiedTime = pathname.lastModified(); // 過濾超過 24 小時的檔案放到 files 陣列中 if (((currentTime - modifiedTime) * 1.0 / (1000 * 60 * 60)) > 24) { return true; } return false; }); if (files != null) { String[] path = new String[files.length]; for (int i = 0; i < files.length; i++) { path[i] = files[i].getPath(); } // 刪除資料夾 FileUtil.delFolder(path); } logger.info("定時任務執行,刪除 {} 目錄下超過 24 小時的資料夾", uDisk.getPkgPath()); } }
總結
定時任務非常簡單,但卻非常實用,公司很多專案中都用到了 Quartz 定時任務,不得不會。另外編寫指令碼執行定時任務也是不錯的解決方法,根據實際情況自行選擇。