我的系統使用CodeIgniter框架，並架設於PHP Ver 8.1的網頁伺服器內

我想在我的訊息發送系統上加入任務排程功能，當群發訊息時，將分批的訊息加至排程中，再順序執行exec CLI。
例如合共1000條訊息，每100條分成一組，當第一組完成後，啟動第二組，如此類推。

以下是我系統中有關訊息發送的相關Database架構:
	
Database Table - waha_message_send_records
// 用於記錄每條訊息的發送狀態
#	名稱	類型
1	id	int(11)
2	messageLogID	int(11) //Related to Database Table - logs (id)
3	user	text
4	tel	text
5	isGroup	tinyint(4)
6	batchGroupIndex	int(11)
7	batchMsgIndex	int(11)
8	status	text
9	log_msg	text
10  retryCount int(11)
11	created_at	datetime
12	updated_at	datetime

Database Table - waha_message_root
// 可用於記錄群體訊息發送的數據總覽
#	名稱	類型
1	pid 🔑	int(11)
2	messageLogID	int(11) //Related to Database Table - logs (id)
3   totalMsgCount int(11) // 相同發送任務內所有排程的訊息數量總和
4	totalBatchCount	int(11) 相同發送任務內所有排程批次數量總和
5	fileDataPath	text // 相同發送任務載有有關File JSONData (由waha_send_wts_group_message生成) 的檔案路徑
6	messageDataPath	text // 相同發送任務載有有關Message JSONData (由waha_send_wts_group_message生成) 的檔案路徑
7	created_at	datetime
8	updated_at	datetime
	
Database Table - waha_message_processors
// 可用於記錄任務排程及狀態
#	名稱	類型
1	pid 🔑	int(11)
2	messageLogID	int(11) //Related to Database Table - logs (id)
4   type text // "Retry" if If current process is create for retry, otherwise null
5	batchGroupIndex	int(11) // If current process is for retry, reset to 1 for the first retry process(batch)
6	totalBatchCount	int(11) // If current process is for retry, reset to the total process(batch) that for retry only
7	msgCount	int(11)
8	successCount	int(11)
9	failCount	int(11)
10	status	text
11	log_msg	text
12  remarks text // "Retry" If current process is create for retry.
13	created_at	datetime
14	updated_at	datetime

Database Table - waha_message_retryfail_records
// 用於記錄超出重試上限的訊息記錄
#	名稱	類型
1	id	int(11)
2	messageLogID	int(11) //Related to Database Table - logs (id)
3	user	text
4	tel	text
6	batchGroupIndex	int(11)
7	batchMsgIndex	int(11)
9	log_msg	text
10	created_at	datetime
11	updated_at	datetime

Database Table - logs
// 用於記錄整體訊息發送任務的狀態
#	名稱	類型
1	id	int(11)
2	datetime	datetime
3	component	text
4	type	text
5	status	text
6	uid	text
7	username	text
8	request	text
9	response	text
10	attachment	text
11	c_total_user	int(11)
12	c_success_user	int(11)
13	c_fail_user	int(11)
14	batchSize	int(11)
15	batchCount	int(11)

以下是我系統中有關訊息發送的程式碼:

application/controllers/admin/Notification_setting.php
//處理發送單一訊息及群發訊息
public function waha_send_wts_message(){
        if ($this->ion_auth->logged_in() && $this->ion_auth->is_admin()) {
            $rID = $this->input->post('userID');
            $userData = fetch_details('memberList_n', ['rID'=>$rID], 'rID, memberID, tel')[0];
            $_POST['select_user'] = $userData['tel'];
            
            $chatID = "852" . $userData['tel'] . "@c.us" ;
            $message = $this->input->post('title') . "\n" . $this->input->post('message') . "\n" . $this->input->post('footer');
            $type = $this->input->post('message_template_type');
            // Upload File Setup
            $image_path = 'error';
        	$targetDir_Docs = "uploads/whatsapp/docs/";
        	$targetDir_IMG = "uploads/whatsapp/img/";
        	if (!is_dir($targetDir_Docs)) {
                mkdir($targetDir_Docs, 0777, true);
            }
            if (!is_dir($targetDir_IMG)) {
                mkdir($targetDir_IMG, 0777, true);
            }
            if($type == "IMAGE"){
            	if(is_array($_FILES) && !empty($_FILES["update_image"]["name"])) {
            	    // Get the file path 
                	$_path = $_FILES['update_image']['tmp_name'];
                	$_filetype = pathinfo($_FILES['update_image']['name'])['extension'];
            		$_name = date("YmdHis") . "_" . $chatID.".".$_filetype;
            		
            		$_data = file_get_contents($_path);
            		if(is_uploaded_file($_path)) {
            			$return = file_put_contents($targetDir_IMG.$_name, $_data);
            			if($return !== false){
            				// Success
            				$f_path = $targetDir_IMG.$_name;
            				$_file = array(
            				    "name" => $_name,
            				    'url' => base_url($targetDir_IMG.$_name)
            				);
            				//  waha_send_wts_message($msgLogID, $type, $user, $message, $doc = null, $link_preview = true, $session = "default", $randMin = 3, $randMax = 6, $isGroup = true)
            				$this->response['error'] = false;
            	            $this->response['message'] = "已提交發送訊息請求。";
            	            $msgLogID = $this->Logs_model->add_logs(array(
            	                "component" => "Message",
            				    "type" => "Send Message",
            				    "status" => "Executed",
            				    "uid" => $this->session->userdata('user_id'),
            				    "username" => $this->session->userdata('username'),
            				    "request" => $_POST,
            				    "response" => $this->response,
            				    "attachment" => $_file['url']
            				));
            				$this->notification_model->waha_send_wts_message($msgLogID, $type, (object) $userData, $message, $_file, true, "default", 3,  6, false);
            	            print_r(json_encode($this->response));
                            return;
            			} else {
            				// Failed to upload
            				$this->response['error'] = true;
        		            $this->response['message'] = "圖片上傳失敗，請重試";
        		            $this->Logs_model->add_logs(array(
        		                "component" => "Message",
            				    "type" => "Send Message",
            				    "status" => "Failed",
            				    "uid" => $this->session->userdata('user_id'),
            				    "username" => $this->session->userdata('username'),
            				    "request" => $_POST,
            				    "response" => $this->response,
            				));
        		            print_r(json_encode($this->response));
    		                return;
            			}
            		}
            	}
            }
            if($type == "DOCS"){
                if(is_array($_FILES) && !empty($_FILES["update_doc"]["name"])) {
            	    // Get the file path 
                	$_path = $_FILES['update_doc']['tmp_name'];
                	$_filetype = pathinfo($_FILES['update_doc']['name'])['extension'];
            		$_name = date("YmdHis") . "_" . $chatID.".".$_filetype;
            		
            		$_data = file_get_contents($_path);
            		if(is_uploaded_file($_path)) {
            			$return = file_put_contents($targetDir_Docs.$_name, $_data);
            			if($return !== false){
            				// Success
            				$f_path = $targetDir_Docs.$_name;
            				$_file = array(
            				    "name" => $_name,
            				    'url' => base_url($targetDir_Docs.$_name)
            				);
            				// waha_send_wts_message($type, $chatID, $message, $doc = null, $link_preview = true, $session = "default")
            				
            				$this->response['error'] = false;
            	            $this->response['message'] = "已提交發送訊息請求。";
            	            $msgLogID = $this->Logs_model->add_logs(array(
            	                "component" => "Message",
            				    "type" => "Send Message",
            				    "status" => "Executed",
            				    "uid" => $this->session->userdata('user_id'),
            				    "username" => $this->session->userdata('username'),
            				    "request" => $_POST,
            				    "response" => $this->response,
            				    "attachment" => $_file['url']
            				));
            				$this->notification_model->waha_send_wts_message($msgLogID, $type,  (object)  $userData, $message, $_file, true, "default", 3,  6, false);
            	            print_r(json_encode($this->response));
                            return;
            			} else {
            				// Failed to upload
            				$this->response['error'] = true;
        		            $this->response['message'] = "文件上傳失敗，請重試";
        		            $this->Logs_model->add_logs(array(
        		                "component" => "Message",
            				    "type" => "Send Message",
            				    "status" => "Failed",
            				    "uid" => $this->session->userdata('user_id'),
            				    "username" => $this->session->userdata('username'),
            				    "request" => $_POST,
            				    "response" => $this->response,
            				));
        		            print_r(json_encode($this->response));
    		                return;
            			}
            		}
            	}
            }
            if($type == "TEXT"){
                
                $this->response['error'] = false;
	            $this->response['message'] = "已提交發送訊息請求。";
	            $msgLogID = $this->Logs_model->add_logs(array(
	                "component" => "Message",
    			    "type" => "Send Message",
    			    "status" => "Executed",
    			    "uid" => $this->session->userdata('user_id'),
    			    "username" => $this->session->userdata('username'),
    			    "request" => $_POST,
    			    "response" => $this->response,
    			));
    			$this->notification_model->waha_send_wts_message($msgLogID, $type,  (object)  $userData, $message, null, true, "default", 3,  6, false);
	            print_r(json_encode($this->response));
                return;
            }
        } else {
            redirect('admin/login', 'refresh');
        }
    }
	
public function waha_send_wts_group_message() {
        if ($this->ion_auth->logged_in() && $this->ion_auth->is_admin()) {
            set_time_limit(0);
    
            $batchSize = 100;
            $delay = 5;
        
            $users = json_decode($this->input->post('users'));
            $message = $this->input->post('title') . "\n" . $this->input->post('message') . "\n" . $this->input->post('footer');
            $type = $this->input->post('message_template_type');
        
            unset($_POST["users"]);
        
            // 準備上傳資料夾
            $targetDir_Docs = "uploads/whatsapp/docs/";
        	$targetDir_IMG = "uploads/whatsapp/img/";
        	if (!is_dir($targetDir_Docs)) {
                mkdir($targetDir_Docs, 0777, true);
            }
            if (!is_dir($targetDir_IMG)) {
                mkdir($targetDir_IMG, 0777, true);
            }
        
            $_file = null; // 上傳檔案資訊
        
            if ($type == "IMAGE" && !empty($_FILES["update_image"]["name"])) {
                $_path = $_FILES['update_image']['tmp_name'];
                $_filetype = pathinfo($_FILES['update_image']['name'], PATHINFO_EXTENSION);
                $_name = date("YmdHis") . "_group_" . preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $_FILES['update_image']['name']);
                $_data = file_get_contents($_path);
                if (is_uploaded_file($_path)) {
                    $return = file_put_contents($targetDir_IMG . $_name, $_data);
                    if ($return !== false) {
                        $_file = [
                            "name" => $_name,
                            "url" => base_url($targetDir_IMG . $_name)
                        ];
                    } else {
                        // 上傳失敗
                        $this->response['error'] = true;
                        $this->response['message'] = "圖片上傳失敗，請重試";
                        print_r(json_encode($this->response));
                        return;
                    }
                }
            }
        
            if ($type == "DOCS" && !empty($_FILES["update_doc"]["name"])) {
                $_path = $_FILES['update_doc']['tmp_name'];
                $_filetype = pathinfo($_FILES['update_doc']['name'], PATHINFO_EXTENSION);
                $_name = date("YmdHis") . "_group_" . preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $_FILES['update_doc']['name']);
                $_data = file_get_contents($_path);
                if (is_uploaded_file($_path)) {
                    $return = file_put_contents($targetDir_Docs . $_name, $_data);
                    if ($return !== false) {
                        $_file = [
                            "name" => $_name,
                            "url" => base_url($targetDir_Docs . $_name)
                        ];
                    } else {
                        $this->response['error'] = true;
                        $this->response['message'] = "文件上傳失敗，請重試";
                        print_r(json_encode($this->response));
                        return;
                    }
                }
            }
        
            // 紀錄發送任務到 logs (取決於你 Logs_model 實作)
            $this->load->model('Logs_model');
            $msgLogID = $this->Logs_model->add_logs([
                "component" => "Message",
                "type" => "Send Group Message",
                "status" => "Executing",
                "uid" => $this->session->userdata('user_id'),
                "username" => $this->session->userdata('username'),
                "request" => $_POST,
                "response" => ['message' => 'Executing'],
                "attachment" => $_file['url'] ?? null,
                "c_total_user" => count($users),
                "batchSize" => $batchSize,
                "batchCount" => ceil(count($users) / $batchSize)
            ]);
			
			
        
            
            $msgLogID_arg = escapeshellarg($msgLogID);
            $message_base64 = base64_encode($message);
            $type_arg = escapeshellarg($type);
            //$users_arg = escapeshellarg(json_encode($users));
            //$file_arg = escapeshellarg(json_encode($_file));
            $users_base64 = base64_encode(json_encode($users));
            $file_base64 = base64_encode(json_encode($_file));
            
            $targetDir_UserJson = "uploads/whatsapp_call_api/usersData/";
        	$targetDir_FileJson = "uploads/whatsapp_call_api/fileData/";
        	$targetDir_MessageJson = "uploads/whatsapp_call_api/messageData/";
        	
        	if (!is_dir($targetDir_UserJson)) {
                mkdir($targetDir_UserJson, 0777, true);
            }
            if (!is_dir($targetDir_FileJson)) {
                mkdir($targetDir_FileJson, 0777, true);
            }
            if (!is_dir($targetDir_MessageJson)) {
                mkdir($targetDir_MessageJson, 0777, true);
            }
            
            $userFileName = $targetDir_UserJson.time()."_".$msgLogID."_".$type.".txt";
            $userFile = fopen($userFileName, "w");
            fwrite($userFile, $users_base64);
            fclose($userFile);
            
            $fileDataFileName = $targetDir_FileJson.time()."_".$msgLogID."_".$type.".txt";
            $fileDataFile = fopen($fileDataFileName, "w");
            fwrite($fileDataFile, $file_base64);
            fclose($fileDataFile);
            
            $messageFileName = $targetDir_MessageJson.time()."_".$msgLogID."_".$type.".txt";
            $messageFile = fopen($messageFileName, "w");
            fwrite($messageFile, $message_base64);
            fclose($messageFile);
            
            
            
            
            $php_cli = PHP_BINDIR . '/php';
            $cli_script = FCPATH . 'index.php';
            $output = [];
            $return_var = 0;
            
            $userFilePath = FCPATH . $userFileName;
            $fileDataFilePath = FCPATH . $fileDataFileName;
            $messageFilePath = FCPATH . $messageFileName;
            
            $cmd = "nohup {$php_cli} {$cli_script} admin/notification_cli/send_batch {$msgLogID_arg} {$messageFilePath} {$type_arg} '{$userFilePath}' '{$fileDataFilePath}' > /dev/null 2>&1 &";
            exec($cmd, $output, $return_var);

        
            $this->response['error'] = false;
            $this->response['message'] = "訊息發送請求已提交至伺服器處理。";// . "\nCMD: " . $cmd . "\nOUTPUT: " . implode(",", $output) . "\nRETURN: " . $return_var;
            print_r(json_encode($this->response));
            return;

        } else {
           redirect('admin/login', 'refresh');
        }
    }
	
application/controllers/admin/Notification_cli.php
//接收、處理及執行由waha_send_wts_group_message發出的CLI指令
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Notification_cli extends CI_Controller {

    public function __construct() {
        parent::__construct();
        
        /*
        if (!$this->input->is_cli_request()) {
            exit("This script can only be run from CLI.");
        }
        */

        /*
        $apiKey = $this->input->get_request_header('X-API-KEY'); // 從 Header 取
        
        if ($apiKey !== "RektqczqKGTQaXwbiwnzRDWNhOdHgUmh") {
            // 無效Key
            header('HTTP/1.1 401 Unauthorized');
            exit('Invalid API Key');
        }
        */
        
        $this->load->model('notification_model');
    }

    // CLI 批次群發入口
    /*
    public function send_batch() {
        
        global $argv;

        $msgLogID = isset($argv[1]) ? $argv[1] : '';
        $message = isset($argv[2]) ? $argv[2] : 'Default Message';
        $type = isset($argv[3]) ? $argv[3] : 'TEXT';
        $users_json = isset($argv[4]) ? $argv[4] : '[]';

        $users = json_decode($users_json);
        
        
        
        // Get the raw POST data (JSON string)
        $json = file_get_contents('php://input');
        // Decode JSON into associative array
        $data = json_decode($json, true);
    
        if (json_last_error() !== JSON_ERROR_NONE) {
            // JSON decode error
            header('HTTP/1.1 400 Bad Request');
            echo json_encode(['error' => true, 'message' => 'Invalid JSON']);
            return;
        }
    
        // Extract data, use null coalescing operator in PHP 7+
        $msgLogID = isset($data['msgLogID']) ? $data['msgLogID'] : null;
        $message = isset($data['message']) ? $data['message'] : '';
        $type = isset($data['type']) ? $data['type'] : 'TEXT';
        $users = isset($data['users']) ? json_decode($data['users'], true) : [];
        $file = isset($data['file']) ? json_decode($data['file'], true) : [];

        if (!is_array($users)) {
            // users must be an array
            header('HTTP/1.1 400 Bad Request');
            echo json_encode(['error' => true, 'message' => 'Users field must be an array']);
            return;
        }
        

        $batchSize = 100;
        $delay = 5;

        $this->notification_model->waha_send_wts_message_batch($msgLogID, $type, $users, $message, $batchSize, $delay, $file ?? null);

        echo "Batch message sending done.\n";
    }
    */
    
    public function send_batch() {
        // 強制只能 CLI 執行
        if (!$this->input->is_cli_request()) {
            exit("This script can only be run from CLI.\n");
        }
    
        global $argv;
    
        // argv[2]: msgLogID
        // argv[3]: message
        // argv[4]: type
        // argv[5]: json encoded users
        // argv[6]: json encoded file (可選)
        if (count($argv) < 6) {
            exit("Usage: php index.php admin/notification_cli/send_batch <msgLogID> <message_file_path> <type> <users_json_file_path> [file_json_file_path]\n");
        }
    
        $msgLogID = $argv[2];
        $message = null;
        if (file_exists($argv[3])) {
            $message = base64_decode(file_get_contents($argv[3]));
        } else {
            exit("Missing Message Data File");
        }
        $type = $argv[4];
        $users = null;
        $file = null;
        
        if (file_exists($argv[5])) {
            $users = json_decode(base64_decode(file_get_contents($argv[5])), true);
        } else {
            exit("Missing Users Data File");
        }
        
        if (file_exists($argv[6])) {
            $fileData = base64_decode(file_get_contents($argv[6]));
            $file = $fileData != "null" ? json_decode($fileData, true) : null;
        }

        //exit("\nmsgLogID:" . $msgLogID . "\nmsg:".$message . "\ntype:" . $type . "\nuser: ". $users . "\nfile: " . $file);
    
        if (!is_array($users)) {
            exit("Users data invalid, must be json array.\n");
        }
    
        $batchSize = 100;
        $delay = 5;
    
        $this->notification_model->waha_send_wts_message_batch($msgLogID, $type, $users, $message, $batchSize, $delay, $file);
    
        echo "Batch message sending done.\n";
    }
}

application/models/Notification_model.php
//執行API呼叫以發送訊息及Log記錄
public function waha_send_seen($chatID, $session = "default"){
        $base_url = 'https://waha.citrusinno.com/api/sendSeen/';
        $ch = curl_init();
        $postFields = array(
          "chatId" => $chatID,
          "session" => $session,
        );
        curl_setopt($ch , CURLOPT_URL , $base_url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-Api-Key: WxInANTm9KrRe685wXJFg7dw'));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postFields));
        
        $result = curl_exec($ch);
        curl_close($ch);
    }
    
    public function waha_start_typing($chatID, $session = "default"){
        $base_url = 'https://waha.citrusinno.com/api/startTyping/';
        $ch = curl_init();
        $postFields = array(
          "chatId" => $chatID,
          "session" => $session,
        );
        curl_setopt($ch , CURLOPT_URL , $base_url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-Api-Key: WxInANTm9KrRe685wXJFg7dw'));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postFields));
        
        $result = curl_exec($ch);
        curl_close($ch);
    }
    
    public function waha_stop_typing($chatID, $session = "default"){
        $base_url = 'https://waha.citrusinno.com/api/stopTyping/';
        $ch = curl_init();
        $postFields = array(
          "chatId" => $chatID,
          "session" => $session,
        );
        curl_setopt($ch , CURLOPT_URL , $base_url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-Api-Key: WxInANTm9KrRe685wXJFg7dw'));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postFields));
        
        $result = curl_exec($ch);
        curl_close($ch);
    }

    public function waha_send_wts_message($msgLogID, $type, $user, $message, $doc = null, $link_preview = true, $session = "default", $randMin = 3, $randMax = 6, $isGroup = true){
        
        $tel = $user->tel;
        if(!$isGroup) 
            $curLogRID = $this->log_create_status($msgLogID, $user->memberID, $user->tel, false, null, null);
        
        $chatID = (strlen($tel) == 8 ? "852".$tel : $tel)."@c.us";
        $this->waha_send_seen($chatID, $session);
        $this->waha_start_typing($chatID, $session);
        $randWaitTime = mt_rand ($randMin*10, $randMax*10) / 10;
        sleep(ceil($randWaitTime));
        $this->waha_stop_typing($chatID, $session);
        
        $base_url = 'https://waha.citrusinno.com/api/sendText/';
        $ch = curl_init();
        $postFields = array(
          "chatId" => $chatID,
          "text" => $message,
          "linkPreview" => $link_preview,
          "session" => $session,
        );
        if($type == "IMAGE" || $type == "DOCS"){
            
            if($type == "IMAGE") $base_url = 'https://waha.citrusinno.com/api/sendImage/';
            if($type == "DOCS") $base_url = 'https://waha.citrusinno.com/api/sendFile/';
            
            //$base_url = 'https://waha.citrusinno.com/api/sendImage/';
            unset($postFields['text']);
            unset($postFields['linkPreview']);
            $postFields['caption'] = $message;
            $postFields['file'] = array(
                "mimetype" => getFileMimeType($doc['name']),
                "filename" => $doc['name'],
                "url" => $doc['url']
            );
        }
        curl_setopt($ch , CURLOPT_URL , $base_url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-Api-Key: WxInANTm9KrRe685wXJFg7dw'));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postFields));
        
        //$response = json_decode(curl_exec($ch), true);
        $result = curl_exec($ch);
        //exit(print_r($response));
        curl_close($ch);
        sleep(1);
        if(!$isGroup) 
            $this->update_send_record_status($curLogRID, 'Success');
    }
	
	
	public function waha_send_wts_message_batch($msgLogID, $type, $chatIDs, $message, $batchSize, $delay, $doc = null, $link_preview = true, $session = "default", $randMin = 10, $randMax = 20){
        $totalUsers = count($chatIDs);
        $users = array();
        $errorCount = 0;

        foreach ($chatIDs as $user) {
            $users[] = (object) ["tel" => $user['tel'], "memberID" => $user['memberID']];
        }
    
        for ($i = 0; $i < $totalUsers; $i += $batchSize) {
            $randWaitTime = mt_rand($randMin*10, $randMax*10) / 10;
    
            $currentBatch = array_slice($users, $i, $batchSize);
            $batchIndex = 1;
            $batchMsgCount = 0;
    
            foreach ($currentBatch as $user) {
                $curLogRID = $this->log_create_status($msgLogID, $user->memberID, $user->tel, true, $batchIndex, $batchMsgCount);
                if ($user->tel == "" || $user->tel == null){
                    $this->update_send_record_status($curLogRID, "Failed", "Missing Tel No.");
                } else {
                    try {
                        $this->waha_send_wts_message($msgLogID, $type, $user, $message, $doc, $link_preview, $session);
                        $this->update_send_record_status($curLogRID, 'Success');
                    } catch (Exception $e) {
                        $errorCount++;
                        $this->update_send_record_status($curLogRID, 'Failed', $e->getMessage());
                    }
                }
                $batchMsgCount++;
            }
    
            if ($i + $batchSize < $totalUsers) {
                
                sleep(ceil($randWaitTime));
            }
            $batchIndex++;
            $batchMsgCount = 0;
        }
        
        if($errorCount == 0)
            $this->msg_log_update($msgLogID, "Completed", (object)['c_success_user' => $totalUsers, 'c_fail_user' => 0]);
        else
            $this->msg_log_update($msgLogID, "Completed with Failed Case(s)", (object)['c_success_user' => $totalUsers - $errorCount, 'c_fail_user' => $errorCount]);
    
        return true;
    }
    
    protected function log_create_status($msgID, $userID, $tel, $isGroup, $batchGroupIndex, $batchMsgIndex, $status = "pending", $msg = '') {
        $insert_data = [
            'messageLogID' => $msgID,
            'user' => $userID,
            'tel' => $tel,
            'isGroup' => $isGroup,
            'batchGroupIndex' => $batchGroupIndex,
            'batchMsgIndex' => $batchMsgIndex,
            'status' => $status,
            'log_msg' => $msg,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s')
        ];
        
        $this->db->insert('waha_message_send_records', $insert_data);
        
        if ($this->db->affected_rows() > 0) {
            return $this->db->insert_id();
        }

        return false; 
    }
    
    public function update_send_record_status($record_id, $status, $error_msg = null) {
        $data = [
            'status' => $status,
            'log_msg' => $error_msg,
            'updated_at' => date('Y-m-d H:i:s')
        ];

        $this->db->where('id', $record_id);
        return $this->db->update('waha_message_send_records', $data);
    }
    
    
    protected function msg_log_update($msgID, $status, $others = null) {
        $data = [
            'status' => $status,
            'c_success_user' => $others->c_success_user ?? null,
            'c_fail_user' => $others->c_fail_user ?? null,
        ];
        $this->db->where('id', $msgID);
        return $this->db->update('logs', $data);
    }
	
	
以下是我認為系統需要改動的地方:
1. 將所有待發放訊息先加至waha_message_send_records, 後續在發送訊息時更新訊息 (現時做法為當發送該訊息時建立Log並寫入status為Pending及在即時發送訊息後更新狀態)
2. 群發訊息的過程中需將無法成功發送的訊息（失敗的訊息/超時的訊息）加至重試清單，並在所有該次群發的排程完成後，將失敗的訊息再以群發訊息功能透過CLI方式再次分批發送。
3. 我需要設立PHP Cron Job來定時將發送記錄(waha_message_send_records)中的失敗訊息(status=="Pending"||status=="Failed")透過透過CLI方式呼叫群發訊息功能分批發送

訊息群發更新後的步驟：
1. 用戶提交表單發出請求，系統由waha_send_wts_group_message處理上傳文件及Log請求，建立包含轉化為base64 的 File 及 Message 訊息的JSON檔案，並記錄相關資訊至waha_message_root
2. 根據batchsize分割批次，並建立及記錄所有當前請求的批次至Database，並初始化記錄每則訊息的發送Log。
3. 建立及發出CLI命令，執行首個批次的發送任務
4. 執行發送任務過程中，更新每則訊息的發送狀態(waha_message_send_records)，成功為Success, 失敗為Failed. 如訊息發送執行時間超出時限(例如30秒)且沒有任何回傳結果，則記錄為Timeout，並繼續發送下一條訊息。
5. 當批次內的所有發送已完成(不論成功與否)，更新批次狀態(waha_message_processors)，沒有任何失敗／超時為若有則為Completed， 若有則為Completed with fail

失敗/超時訊息處理：
1. 先逐一記錄狀態並更新retryCount(步驟4)，待所有批次完成後再運行步驟2-5重新發送所有當前請求的失敗/超時訊息。
2. 每則訊息最多重試5次(retryCount)，若超出上限則無需再嘗試，但需記錄於waha_message_retryfail_records內。
3. 允許設立定時排程(cron job)重新發送所有未超出重試上限的訊息，並按步驟2-5執行。

請列出你的建議，並列出所有需要新增、修改的程式碼，以達成訊息群發更新後的步驟及失敗/超時訊息處理。