31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 ); $xvars = array(); // 共用變數 // 共用記憶體 $mem = new Memcache; if (! $mem->pconnect(MEMCACHE_HOST, MEMCACHE_PORT)) { echo 'Could not connect memcache'; Worker::stopAll(); } // 連接總管理處資料庫 $pdo = new PDO($dbs['dsn'], $dbs['user_name'], $dbs['password']); // 讀取info場站共用資訊 $sql_info = 'select station_no, station_name, station_full_name, company_no, station_ip, hq_url, origin_url, period_name, park_time,member_attr_list, period_list from info where seqno = 1'; $info_arr = $pdo->query($sql_info)->fetch(PDO::FETCH_ASSOC); // global variable $pdo = null; $query_syncs = null; $query_sync2hq_ok = null; // 未同步者批次同步至總管理處 $sql_syncs = 'select st_sync_no, station_no, act, hq_tname, st_tname, st_seqno, sync_data from syncs where synced = 0 and st_sync_no = ?'; // $query_syncs = $pdo->prepare($sql_syncs); // 同步成功 $sql_sync2hq_ok = 'update syncs set synced = 1, hq_sync_no = ? where st_sync_no = ?'; // $query_sync2hq_ok = $pdo->prepare($sql_sync2hq_ok); // 預設curl參數 $curl_ch = curl_init(); $curl_options = array ( CURLOPT_URL => $info_arr['hq_url'], CURLOPT_HEADER => 0, CURLOPT_RETURNTRANSFER => 1, // 返回值不顯示, 只做變數用 CURLOPT_POST => 1, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 5 ); init_station_xvars(); // 建立一個Worker監聽60133埠,不使用任何應用層協定 $worker = new Worker("http://0.0.0.0:60133"); // 啟動N個進程對外提供服務 $worker->count = 4; // ----- program start ----- // 當用戶端發來數據(主程式) $worker->onMessage = function($connection, $msg_in) { global $dbs, $pdo, $sql_syncs, $sql_sync2hq_ok, $query_syncs, $query_sync2hq_ok; // 無連線或status != Localhost via UNIX socket, 重連線之 if (empty($pdo) || preg_match('/socket$/', $pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS)) != 1 ) { $pdo = new PDO($dbs['dsn'], $dbs['user_name'], $dbs['password'], array(PDO::ATTR_PERSISTENT => true)); $query_syncs = $pdo->prepare($sql_syncs); $query_sync2hq_ok = $pdo->prepare($sql_sync2hq_ok); trigger_error('new pdo'); } if (!empty($_REQUEST['cmd'])) { trigger_error('worker.onMessage: ' . print_r($_REQUEST, true)); $funcs = @$_REQUEST['cmd']; $funcs($connection); } }; // 執行worker Worker::runAll(); // ----- end of program ----- // 同步化, 場站 -> 總管理處 , 參數: // http://hq_ip:60133/?cmd=st_syncs&station_no=12110&act=U&tname=members&key=member_no&kval=12110101&jdata=json_str&ck=str32 function st_syncs($connection) { global $pdo_hq, $query_hq_sync; // 暫不驗證 $syncs_sql = parms2sql($_REQUEST); // exec:傳回筆數, query:傳回資料 $rows_affected = $pdo_hq->exec($syncs_sql); // 如果新增資料, 回傳insert ID if ($_REQUEST['act'] == 'A') $rows_affected = $pdo_hq->lastInsertId(); @$connection->send($rows_affected); // 回應當前序號或筆數 $confirms = $rows_affected > 0 ? 1 : 0; $query_hq_sync->execute(array($_REQUEST['station_no'], $_REQUEST['act'], $_REQUEST['tname'], $_REQUEST['data'], $confirms)); } // 讀取序號 // http://localhost:60133/?cmd=seqno&seqname=members&init_no=1025 function seqno($connection) { $seqno_fname = SEQNO_PATH . "{$_REQUEST['seqname']}.txt"; $fp = fopen($seqno_fname, 'r+'); // lock, 讀入序號, 加1寫回, close if ($fp) { flock($fp, LOCK_EX); $seqno = fread($fp, 80); $next_no = $seqno + 1; rewind($fp); fwrite($fp, $next_no); flock($fp, LOCK_UN); fclose($fp); } else // 如無此序號檔, 新建之, 並傳回初始值 { $seqno = empty($_REQUEST['init_no']) ? 1 : $_REQUEST['init_no']; $next_no = $seqno + 1; file_put_contents($seqno_fname, $next_no, FILE_APPEND); } @$connection->send($seqno); // 回應當前序號 } // 檢查是否有連線 ? function check_connect($connection) { $connected = @fsockopen($_REQUEST['ip'], $_REQUEST['port'], $errno, $errstr, $_REQUEST['timeout']); if ($connected) { fclose($connected); $is_connected = 1; //action when connected } else { $is_connected = 0; //action in connection failure } return $is_connected; } // 取得下一租期期最後一日 function last_date_next($connection) { @cross_header(); @$connection->send(last_date_next_period($_REQUEST['last_date_curr'], $_REQUEST['fee_period'])); // 參數:本期截止日,繳期 } // 取得下一租期期最後一日, 參數: 本期截止日, 繳期 function last_date_next_period($last_date_curr, $fee_period) { global $last_date_month; $arr = explode('-', $last_date_curr); $yy = (int) $arr[0]; // 取年份 $mm = (int) $arr[1]; // 取月份 $mm += $fee_period; if ($mm > 12) // 超過12月, 年度+1, 折算明年度月份 { ++$yy; $mm -= 12; } $dd = $mm == 2 && ($yy % 4) == 0 ? 29 : $last_date_month[$mm]; if ($mm < 10) $mm = "0{$mm}"; // 個位數前面補0 return "{$yy}-{$mm}-{$dd}"; } // web跨網域header設定 function cross_header() { global $info_arr; \Workerman\Protocols\Http::header('Access-Control-Allow-Origin: ' . "{$info_arr['origin_url']}"); \Workerman\Protocols\Http::header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); \Workerman\Protocols\Http::header('Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept'); } // 將參數轉成sql命令字串 function parms2sql($parms) { $act = $parms['act']; switch($act) { case 'U': // 更新場站資料庫 ???? $sql = "update {$parms['tname']} set {$parms['data']} where {$parms['key']} = '{$parms['kval']}';"; break; case 'A': // 新增資料 $fields_str = implode(',', array_keys($data)); $values_str = "'".implode("',", array_values($data))."'"; $sql = "insert into {$parms['tname']} ({$fields_str}) values({$values_str});"; break; case 'D': // 刪除資料 $sql = "delete {$parms['tname']} where {$parms['key']} = '{$parms['kval']}';"; break; } } // 場站初始化參數 function init_station_xvars() { global $mem, $info_arr, $xvars; $data = array ( 'cmd' => 'init_station', 'station_no' => $info_arr['station_no'], 'park_time' => $info_arr['park_time'] ); $jdata = worker_tx($data); if(empty($jdata)) { trigger_error('中控已斷線, 建立預設值..'.print_r($jdata, true)); // 中控已斷線, 建立預設值 $xvars = array(); $xvars['info']['period_name'] = array(1 => "月繳", 2 => "雙月繳", 3 => "季繳", 6 => "雙季繳", 12 => "四季繳"); $xvars['info']['member_attr'] = array(1 => "一般", 2 => "里民", 3 => "身障", 4 => "員工", 5 => "里長", 250 => "VIP"); $xvars['pt'] = array( 'RE' => array( 'seqno' => 1000, 'timex' => array( '0' => array('type' => 1, 'w_start' => 0, 'w_end' => 6, 'time_start' => '00:00:00', 'time_end' => '23:59:59') ), 'remarks' => '全天全時段' ), 'NF' => array( 'seqno' => 1010, 'timex' => array( '0' => array('type' => 1, 'w_start' => 1, 'w_end' => 5, 'time_start' => '18:00:00', 'time_end' => '23:59:59'), '1' => array('type' => 1, 'w_start' => 1, 'w_end' => 5, 'time_start' => '00:00:00', 'time_end' => '07:59:59') ), 'remarks' => '週一至週五: 00:00:00 - 07:59:59
週一至週五: 18:00:00 - 23:59:59' ), 'WK66' => array( 'seqno' => 1026, 'timex' => array( '0' => array('type' => 1, 'w_start' => 1, 'w_end' => 5, 'time_start' => '06:00:00', 'time_end' => '17:59:59') ), 'remarks' => '週一至週五: 06:00:00 - 17:59:59' ), 'WK' => array( 'seqno' => 1020, 'timex' => array( '0' => array('type' => 1, 'w_start' => 1, 'w_end' => 5, 'time_start' => '07:30:00', 'time_end' => '18:29:59') ), 'remarks' => '週一至週五: 07:30:00 - 18:29:59' ), 'HO' => array( 'seqno' => 1030, 'timex' => array( '0' => array('type' => 1, 'w_start' => 6, 'w_end' => 6, 'time_start' => '00:00:00', 'time_end' => '23:59:59'), '1' => array('type' => 1, 'w_start' => 0, 'w_end' => 0, 'time_start' => '00:00:00', 'time_end' => '23:59:59') ), 'remarks' => '週六日全時段' ) ); } else { $xvars = json_decode($jdata, true); // 篩選繳期名稱 $arr = array(); $arr_list = explode(',', $info_arr['period_list']); foreach($arr_list as $idx) $arr[$idx] = $xvars['info']['period_name'][$idx]; $xvars['info']['period_name'] = $arr; // 篩選會員身份 $arr = array(); $arr_list = explode(',', $info_arr['member_attr_list']); foreach($arr_list as $idx) $arr[$idx] = $xvars['info']['member_attr'][$idx]; $xvars['info']['member_attr'] = $arr; } $mem->set('st_info', $info_arr); $mem->set('info', $xvars['info']); $mem->set('pt', $xvars['pt']); trigger_error('st_info: '.print_r($info_arr, true)); trigger_error('xvars: '.print_r($xvars, true)); } // 顯示場站初始化參數 function show_all_vars($connection) { global $info_arr, $xvars; $connection->send(json_encode($xvars, JSON_UNESCAPED_UNICODE)); } // 場站初始化參數 function get_var($connection) { global $xvars; $str = gettype($xvars[$_REQUEST['var']]) == 'array' ? json_encode($xvars[$_REQUEST['var']], JSON_UNESCAPED_UNICODE) : $xvars[$_REQUEST['var']]; $connection->send($str); } // 顯示場站初始化參數 function set_var($connection) { global $xvars; $xvars[$_REQUEST['var']] = $_REQUEST['val']; $connection->send('1'); } // 批次同步資料表至總管理處 function sync_batch($connection) { global $query_syncs, $query_sync2hq_ok; //trigger_error(__FUNCTION__ . '..start..'); $connection->close(); // 先斷線再繼續處理 foreach(explode(',', $_REQUEST['sync_seqnos']) as $st_sync_no) { if(empty($st_sync_no)) continue; $query_syncs->execute(array($st_sync_no)); $rows_syncs = $query_syncs->fetch(PDO::FETCH_ASSOC); $tx = $rows_syncs; if(empty($tx)) continue; $tx['cmd'] = 'sync_st2hq'; // 場站同步至總管理處 $hq_sync_no = worker_tx($tx); //trigger_error(__FUNCTION__ . '| 1. st_sync_no: ' . $st_sync_no . ', 2. rows_syncs: ' . $rows_syncs . ', 3. hq_sync_no: ' . $hq_sync_no); // 同步成功, 記錄之 ( note: 總管理處的 hq_sync 記錄完成就視為完成,各場站 sync.synced = 1 ) if ($hq_sync_no > 0) { $query_sync2hq_ok->execute(array($hq_sync_no, $st_sync_no)); } else { trigger_error('sync err:'.json_encode($tx, JSON_UNESCAPED_UNICODE)); // TODO: sync 失敗的處理方式 } } //trigger_error(__FUNCTION__ . '..end..'); } // 月租金額計算 function calculate_rents_amt($connection) { $_REQUEST['rents_amt1'] = 100; $_REQUEST['rents_amt2'] = 200; cross_header(); $connection->send(json_encode($_REQUEST, JSON_UNESCAPED_UNICODE)); } // worker connect至總管理處 function worker_tx($data) { global $curl_ch, $curl_options; $curl_options[CURLOPT_POSTFIELDS] = $data; trigger_error('curl:'. print_r($curl_options, true)); curl_setopt_array($curl_ch, $curl_options); return(curl_exec($curl_ch)); } // 發生錯誤時集中在此處理 function error_handler($errno, $errstr, $errfile, $errline, $errcontext) { $str = date('H:i:s')."|{$errstr}|{$errfile}|{$errline}|{$errno}\n"; error_log($str, 3, LOG_PATH.APP_NAME . '.' . date('Ymd').'.log.txt'); // 3代表參考後面的檔名 }