最近發現論壇內有些使用者當月線上時間被清空,究其原因發現是清空當月線上時間的計劃任務在某一錯誤時刻被執行。
就本帖分析下計劃任務的實現過程,方便使用者排查錯誤。

資料庫結構:

論壇內現有的計劃任務資料被儲存在 pre_common_cron 表中,表中資料與論壇後臺計劃任務列表中的資料一致。
weekday 欄位為 X 表示每週星期 X 執行計劃任務,day 欄位為 X 表示每月 X 日執行計劃任務。 X 為-1 表示不限制,即每天都執行計劃任務。

執行計劃任務:

執行計劃任務在 class_core.php 中,初始化計劃任務的函式_init_cron() 中

  1. function _init_cron() {
  2.                 $ext = empty($this->config['remote']['on']) || empty($this->config['remote']['cron']) || APPTYPEID == 200;
  3.                 if($this->init_cron && $this->init_setting && $ext) {
  4.                         if($this->var['cache']['cronnextrun'] <= TIMESTAMP) {//判斷當前是否有計劃任務出於待執行狀態
  5.                                 require_once libfile('class/cron');
  6.                                 discuz_cron::run();//執行計劃任務
  7.                         }
  8.                 }
  9.         }

計劃任務執行函式 discuz_cron::run()

  1. function run($cronid = 0) {
  2.                 global $_G;
  3.                 $timestamp = TIMESTAMP;
  4.                 $cron = DB::fetch_first("SELECT * FROM ".DB::table('common_cron')."
  5.                                 WHERE ".($cronid ? "cronid='$cronid'" : "available>'0' AND nextrun<='$timestamp'")."
  6.                                 ORDER BY nextrun LIMIT 1"); //取出一條符合執行條件的計劃任務
  7.                 $processname ='DZ_CRON_'.(empty($cron) ? 'CHECKER' : $cron['cronid']);
  8.                 if($cronid && !empty($cron)) { //為了手動執行計劃任務解鎖
  9.                         discuz_process::unlock($processname);
  10.                 }
  11.                 if(discuz_process::islocked($processname, 600)) { //檢查計劃任務程式是否上鎖
  12.                         return false;
  13.                 }
  14.                 if($cron) {  //計劃任務執行部分
  15.                         $cron['filename'] = str_replace(array('..', '/', ''), '', $cron['filename']);
  16.                         $cronfile = DISCUZ_ROOT.'./source/include/cron/'.$cron['filename'];
  17.                         $cron['minute'] = explode(" ", $cron['minute']);
  18.                         discuz_cron::setnextime($cron); //根據後臺設定,更新該計劃任務執行的時間
  19.                         @set_time_limit(1000);
  20.                         @ignore_user_abort(TRUE); //設定與客戶機斷開不會終止指令碼的執行
  21.                         if(!@include $cronfile) { //執行具體計劃任務程式
  22.                                 return false;
  23.                         }
  24.                 }
  25.                 discuz_cron::nextcron(); //設定最近一次計劃任務執行的時間
  26.                 discuz_process::unlock($processname); //解鎖程式
  27.                 return true;
  28.         }

注意:
每一個入口檔案,如 forum.php,space.php 都有計劃任務執行的入口,但開啟一次頁面只執行一條計劃任務。

總結:
計劃任務涉及的檔案並不多,如果計劃任務出現異常,通常只需要將 class_core.php,class_cron.php 重新上傳覆蓋即可。