productManagementSv = new ProductManagementService(); $this->checkParamSv = new CheckParamService(); $this->s3Client = new S3Client([ 'credentials' => [ 'key' => env('AWS_S3_KEY'), 'secret' => env('AWS_S3_SECRET'), ], 'region' => env('AWS_S3_REGION'), 'version' => 'latest', ]); // 時區調整 date_default_timezone_set("Asia/Taipei"); } /** * 列表頁 * 本端點非 API,直接渲染到 view * @return array: 返回渲染頁 */ public function index() { // 渲染 return view('admin.EcManagement.ProductManagement', [ 'can_step_0' => (Redis::get('TLW_0_MAN') || Redis::get('TLW_0_STOP')) ? false : true, 'can_step_2' => (Redis::get('TLW_2_CSV')) ? false : true, 'step_2_s3' => env("AWS_S3_BUCKET"), 'step_2_env' => env("APP_ENV"), 'brands' => $this->productManagementSv->getBrands(), 'batchs' => $this->productManagementSv->getBatchs(), 'mstatuss' => $this->productManagementSv->getMStatuss(), ]); } /** * 列表API * 接收搜尋參數與分頁等信息,返回JSON * @return string: 返回JSON */ public function grid() { // 取得參數 $param = $_GET; if ($param == null) exit(); $draw = $param["draw"]; //客戶端傳來的查詢次數,無條件回傳用以核對 $orderColumn = $param["order"][0]["column"] + 1; //前端從 0 開始送,但 mysql 從 1 開始算 $orderDir = $param["order"][0]["dir"]; $start = $param["start"]; // 頁碼 $length = $param["length"]; // 一頁多大 $searchValue = $param["search"]["value"]; //客製化搜尋欄位 $brand_no = $param["columns"][1]["search"]["value"]; $batch_no = $param["columns"][2]["search"]["value"]; $g_title = $param["columns"][3]["search"]["value"]; $g_description = $param["columns"][4]["search"]["value"]; $m_comment = $param["columns"][5]["search"]["value"]; $m_status = $param["columns"][6]["search"]["value"]; // 驗證 if ($g_title != filter_var($g_title, FILTER_SANITIZE_SPECIAL_CHARS)) $g_title = "___CANNOT_FIND_STRING___"; if (!$this->checkParamSv->LenMToN($g_title, 0, 50)) $g_title = "___CANNOT_FIND_STRING___"; if ($g_description != filter_var($g_description, FILTER_SANITIZE_SPECIAL_CHARS)) $g_description = "___CANNOT_FIND_STRING___"; if (!$this->checkParamSv->LenMToN($g_description, 0, 50)) $g_description = "___CANNOT_FIND_STRING___"; if ($m_comment != filter_var($m_comment, FILTER_SANITIZE_SPECIAL_CHARS)) $m_comment = "___CANNOT_FIND_STRING___"; if (!$this->checkParamSv->LenMToN($m_comment, 0, 50)) $m_comment = "___CANNOT_FIND_STRING___"; //資料庫 $recordsTotal = 0; $result = $this->productManagementSv->getProdocts( $recordsTotal, $orderColumn, $orderDir, $start, $length, $searchValue, $brand_no, $batch_no, $g_title, $g_description, $m_comment, $m_status ); // 整理返回資料 $data = array(); for ($i = 0; $i < count($result); $i++) { $data[] = array( //一般資料 $result[ $i ]["serno"], $result[ $i ]["g_id"], $result[ $i ]["g_title"], $result[ $i ]["g_description"], $result[ $i ]["g_image_link"], $result[ $i ]["g_price"], $result[ $i ]["g_sale_price"], $result[ $i ]["m_status"], $result[ $i ]["m_comment"], ); } $json = array( "draw" => $draw, "recordsTotal" => $recordsTotal, "recordsFiltered" => $recordsTotal, //其實還是填入所有筆數,本次筆數可從陣列取得 "data" => $data, ); // 返回 return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true); } public function manualXml() { Redis::set('TLW_0_MAN', 'true'); return $this->manualXmlStatus(); } public function manualXmlStatus() { $json = [ 'can_step_0' => (Redis::get('TLW_0_MAN') || Redis::get('TLW_0_STOP')) ? false : true, ]; return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true); } public function importCsv(Request $request) { // 上傳錯誤偵測(沒有包含檔案格式) switch ($_FILES['csv']['error']) { case UPLOAD_ERR_OK: break; case UPLOAD_ERR_NO_FILE: return json_decode(json_encode(['code' => 0, 'err' => '檔案大小錯誤'], JSON_NUMERIC_CHECK), true); case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: return json_decode(json_encode(['code' => 1, 'err' => '檔案太大'], JSON_NUMERIC_CHECK), true); default: return json_decode(json_encode(['code' => 1, 'err' => '系統錯誤'], JSON_NUMERIC_CHECK), true); } if ($_FILES['csv']['size'] > 100 * 1024 * 1024) { return json_decode(json_encode(['code' => 1, 'err' => '檔案最大100MB'], JSON_NUMERIC_CHECK), true); } // 路徑命名 $path = '/tmp/tlwcsv/'; if (!file_exists($path)) mkdir($path, 0777, true); chmod($path, 0777); $date = date('Y-m-d'); if (!file_exists($path . 'tempImportCsv')) mkdir($path . 'tempImportCsv', 0777, true); chmod($path . 'tempImportCsv', 0777); if (!file_exists($path . 'tempImportCsv/' . $date)) mkdir($path . 'tempImportCsv/' . $date, 0777, true); chmod($path . 'tempImportCsv/' . $date, 0777); // 保存文件 $time = time(); move_uploaded_file($_FILES['csv']['tmp_name'], $path . 'tempImportCsv/' . $date . '/' . $time . '.csv'); chmod($path . 'tempImportCsv/' . $date . '/' . $time . '.csv', 0777); // 異步處裡文件 Redis::set('TLW_2_CSV', $path . 'tempImportCsv/' . $date . '/' . $time . '.csv' . '|' . $_POST['brand_no'] . '|' . $_POST['batch_no']); return json_decode(json_encode($_FILES, JSON_NUMERIC_CHECK), true); } public function importCsvStatus() { $json = [ 'can_step_2' => (Redis::get('TLW_2_CSV') || Redis::get('TLW_2_MAN')) ? false : true, ]; return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true); } public function importCsvHandle($info) { // 上鎖 Redis::set('TLW_2_MAN', 'true'); // 剖析資訊 $info = explode('|', $info); $csv = $info[0]; $brand_no = $info[1]; $batch_no = $info[2]; // 更新產品訊息 $fileR = fopen($csv, "r"); while (!feof($fileR)) { $line = fgets($fileR); $cell = str_getcsv($line, ',', '"'); if ($cell[0] != 'Item ID') { $g_id = $cell[0]; $m_comment = $cell[4] ?? ''; $m_status = $cell[5] ?? ''; switch ($m_status) { case GeneralConst::$mStatusMap[ GeneralConst::MSTATUS_WARNING ]['BNAME']: $detail = $this->productManagementSv->getProductDetail($brand_no, $batch_no, $g_id); $comment = (strpos($detail['m_comment'], $m_comment . ' / ') !== false) ? $detail['m_comment'] : $detail['m_comment'] . $m_comment . ' / '; if ($detail['m_status'] == GeneralConst::MSTATUS_DISAPPROVED) { // 原本是錯誤,但目前讀取到警告,就依然要保持錯誤狀態 $this->productManagementSv->updateProduct($brand_no, $batch_no, $g_id, GeneralConst::MSTATUS_DISAPPROVED, $comment); } else { // 原本是正常或警告,目前讀取到警告,就要將狀態設為警告 $this->productManagementSv->updateProduct($brand_no, $batch_no, $g_id, GeneralConst::MSTATUS_WARNING, $comment); } break; case GeneralConst::$mStatusMap[ GeneralConst::MSTATUS_DISAPPROVED ]['BNAME']: $detail = $this->productManagementSv->getProductDetail($brand_no, $batch_no, $g_id); $comment = (strpos($detail['m_comment'], $m_comment . ' / ') !== false) ? $detail['m_comment'] : $detail['m_comment'] . $m_comment . ' / '; // 原本不管是甚麼,目前讀取到錯誤,就要將狀態設為錯誤 $this->productManagementSv->updateProduct($brand_no, $batch_no, $g_id, GeneralConst::MSTATUS_DISAPPROVED, $comment); break; } } } fclose($fileR); // 刪除檔案(權限問題無法刪除) unlink($csv); } public function manualEndpoint() { $param = $_GET; Redis::set('TLW_4_MAN', $param['brand_no'] . '|' . $param['batch_no']); return $this->manualEndpointStatus(); } public function manualEndpointStatus() { $json = [ 'can_step_4' => (Redis::get('TLW_4_MAN') || Redis::get('TLW_4_STOP')) ? false : true, ]; return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true); } public function exportCsv(Request $request) { $title = [ '系統號', '商品號', '品名', '描述', '網頁', '圖片路徑', '定價', '售價', '掃描結果', '掃描問題', ]; $products = $this->productManagementSv->getExportProdocts($request); $this->productManagementSv->downloadExcel($title, $products, date("YmdHis") . '-REPORT-' . GeneralConst::$brandMap[ $request->brand_no ]['LB']); } // XML轉換腳本 public function xml() { // 防重鎖 if (!Redis::get('TLW_0_XML')) { Redis::set('TLW_0_XML', 'true'); Redis::expire('TLW_0_XML', 1200); // 20 分鐘 // 產生批次號 $batch_no = date("Y-m-d H:i:s"); $batch_no_digit = date("YmdHis"); // 產生刪除批次號 $del_batch_no = date('Y-m-d', strtotime(date("Y-m-d H:i:s") . "-14 days")); $del_batch_no_digit = date('Ymd', strtotime(date("Y-m-d") . "-14 days")); // 逐一品牌處理 foreach (GeneralConst::$brandMap as $k => $v) { for ($i = 1; $i <= 14; $i++) { } // 載入品牌原始 XML $original = simplexml_load_file($v['URL']); // 開始寫入檔案 $path = '/tmp/tlw/' . env('APP_ENV') . '/' . $v['LB'] . '/'; if (!file_exists($path)) mkdir($path, 0777, true); $fileW = fopen($path . $batch_no_digit . '.xml', "w"); fwrite($fileW, '特力屋' . $v['URL'] . '' . "\n"); // 原始商品逐筆處裡(保留符號已被CDATA處理) // 注意可能出現相同品牌之中有相同 g_id 的現象,但由於不影響資料流程所以不處理 $data = []; foreach ($original->Product as $p) { // custom_label_1 為客戶系統自定義欄位,Y代表可讓GMC上架,N代表不可讓GM上架 if ((string)$p->custom_label_1 == GeneralConst::PUBLISH_YES) { // XML fwrite($fileW, '' . "\n"); fwrite($fileW, '' . (string)$p->SKU . '' . "\n"); fwrite($fileW, 'Name . ']]>' . "\n"); fwrite($fileW, 'Description . ']]>' . "\n"); fwrite($fileW, 'URL . ']]>' . "\n"); fwrite($fileW, 'LargeImage . ']]>' . "\n"); fwrite($fileW, '' . (string)$p->Condition . '' . "\n"); fwrite($fileW, '' . (string)'in stock' . '' . "\n"); fwrite($fileW, '' . (string)$p->Price . ' TWD' . '' . "\n"); fwrite($fileW, '' . (string)$p->SalePrice . ' TWD' . '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . (string)'' . '' . "\n"); fwrite($fileW, 'Category . ']]>' . "\n"); fwrite($fileW, 'custom_label_0 . ']]>' . "\n"); fwrite($fileW, '' . "\n"); // 資料庫(保留符號已被框架處理) $data[] = [ 'brand_no' => $k, 'batch_no' => $batch_no, 'g_id' => (string)$p->SKU, 'g_title' => (string)$p->Name, 'g_description' => (string)$p->Description, 'g_link' => (string)$p->URL, 'g_image_link' => (string)$p->LargeImage, 'g_price' => (string)$p->Price . ' TWD', 'g_sale_price' => (string)$p->SalePrice . ' TWD', 'g_product_type' => (string)$p->Category, 'g_custom_label_0' => (string)$p->custom_label_0, ]; if (count($data) >= 100) { $this->productManagementSv->insertProduct($data); $data = []; } } } fwrite($fileW, '' . $batch_no . '' . "\n"); $this->productManagementSv->insertProduct($data); // 結束寫入檔案 fclose($fileW); // 檔案上傳至 S3 $fileR = fopen($path . $batch_no_digit . '.xml', "r"); $file_name = env('APP_ENV') . '/' . $v['LB'] . '/' . $batch_no_digit . '.xml'; $this->s3Client->putObject([ 'ACL' => 'public-read', 'Body' => $fileR, 'Bucket' => env('AWS_S3_BUCKET'), 'ContentType' => 'application/xml', 'Key' => $file_name, ]); fclose($fileR); // 刪除寫入的檔案 unlink($path . $batch_no_digit . '.xml'); // 刪除舊資料(XML) $objects = $this->s3Client->listObjects([ 'Bucket' => env('AWS_S3_BUCKET'), 'Prefix' => env('APP_ENV') . '/' . $v['LB'] . '/' . $del_batch_no_digit, ]); foreach ($objects['Contents'] ?? [] as $object) { $tmpkey = $object['Key'] ?? null; if ($tmpkey) { $this->s3Client->deleteObjects([ 'Bucket' => env('AWS_S3_BUCKET'), 'Delete' => ['Objects' => [['Key' => $tmpkey]]], ]); } } } // 刪除舊資料(SQL) $this->productManagementSv->deleteProductByBatchNo($del_batch_no); Redis::del('TLW_0_XML'); } } // XML轉換腳本(特力屋新版) public function xmlG() { // 防重鎖 if (!Redis::get('TLW_0_XML')) { Redis::set('TLW_0_XML', 'true'); Redis::expire('TLW_0_XML', 1200); // 20 分鐘 // 產生批次號 $batch_no = date("Y-m-d H:i:s"); $batch_no_digit = date("YmdHis"); // 產生刪除批次號 $del_batch_no = date('Y-m-d', strtotime(date("Y-m-d H:i:s") . "-14 days")); $del_batch_no_digit = date('Ymd', strtotime(date("Y-m-d") . "-14 days")); // 逐一品牌處理 foreach (GeneralConst::$brandMap as $k => $v) { for ($i = 1; $i <= 14; $i++) { } // 載入品牌原始 XML $original = simplexml_load_file($v['URLG']); // [新舊版本差異]來源修改 // 開始寫入檔案 $path = '/tmp/tlw/' . env('APP_ENV') . '/' . $v['LB'] . '/'; if (!file_exists($path)) mkdir($path, 0777, true); $fileW = fopen($path . $batch_no_digit . '.xml', "w"); fwrite($fileW, '特力屋' . $v['URL'] . '' . "\n"); // 原始商品逐筆處裡(保留符號已被CDATA處理) // 注意可能出現相同品牌之中有相同 g_id 的現象,但由於不影響資料流程所以不處理 $data = []; foreach ($original->channel->item as $p) { // [新舊版本差異]解析層級修改 // custom_label_1 為客戶系統自定義欄位,Y代表可讓GMC上架,N代表不可讓GM上架 if ((string)$p->custom_label_1 == GeneralConst::PUBLISH_YES) { // XML fwrite($fileW, '' . "\n"); fwrite($fileW, '' . (string)$p->children('g', true)->id . '' . "\n"); fwrite($fileW, 'children('g', true)->title . ']]>' . "\n"); fwrite($fileW, 'children('g', true)->description . ']]>' . "\n"); fwrite($fileW, 'children('g', true)->link . ']]>' . "\n"); fwrite($fileW, 'children('g', true)->image_link . ']]>' . "\n"); fwrite($fileW, '' . (string)$p->children('g', true)->condition . '' . "\n"); fwrite($fileW, '' . (string)$p->children('g', true)->availability . '' . "\n"); fwrite($fileW, '' . (string)$p->children('g', true)->price . '' . "\n"); fwrite($fileW, '' . (string)$p->children('g', true)->sale_price . '' . "\n"); fwrite($fileW, 'children('g', true)->brand . ']]>' . "\n"); fwrite($fileW, '' . (string)'' . '' . "\n"); fwrite($fileW, 'children('g', true)->product_type . ']]>' . "\n"); fwrite($fileW, 'custom_label_0 . ']]>' . "\n"); fwrite($fileW, '' . "\n"); // 資料庫(保留符號已被框架處理) $data[] = [ 'brand_no' => $k, 'batch_no' => $batch_no, 'g_id' => (string)$p->children('g', true)->id, 'g_title' => (string)$p->children('g', true)->title, 'g_description' => (string)$p->children('g', true)->description, 'g_link' => (string)$p->children('g', true)->link, 'g_image_link' => (string)$p->children('g', true)->image_link, 'g_price' => (string)$p->children('g', true)->price, 'g_sale_price' => (string)$p->children('g', true)->sale_price, 'g_product_type' => (string)$p->children('g', true)->product_type, 'g_custom_label_0' => (string)$p->custom_label_0, ]; if (count($data) >= 100) { $this->productManagementSv->insertProduct($data); $data = []; } } } fwrite($fileW, '' . $batch_no . '' . "\n"); $this->productManagementSv->insertProduct($data); // 結束寫入檔案 fclose($fileW); // 檔案上傳至 S3 $fileR = fopen($path . $batch_no_digit . '.xml', "r"); $file_name = env('APP_ENV') . '/' . $v['LB'] . '/' . $batch_no_digit . '.xml'; $this->s3Client->putObject([ 'ACL' => 'public-read', 'Body' => $fileR, 'Bucket' => env('AWS_S3_BUCKET'), 'ContentType' => 'application/xml', 'Key' => $file_name, ]); fclose($fileR); // 刪除寫入的檔案 unlink($path . $batch_no_digit . '.xml'); // 刪除舊資料(XML) $objects = $this->s3Client->listObjects([ 'Bucket' => env('AWS_S3_BUCKET'), 'Prefix' => env('APP_ENV') . '/' . $v['LB'] . '/' . $del_batch_no_digit, ]); foreach ($objects['Contents'] ?? [] as $object) { $tmpkey = $object['Key'] ?? null; if ($tmpkey) { $this->s3Client->deleteObjects([ 'Bucket' => env('AWS_S3_BUCKET'), 'Delete' => ['Objects' => [['Key' => $tmpkey]]], ]); } } } // 刪除舊資料(SQL) $this->productManagementSv->deleteProductByBatchNo($del_batch_no); Redis::del('TLW_0_XML'); } } // 更新動態饋給 public function endpoint() { // 防重鎖 if (!Redis::get('TLW_4_XML')) { Redis::set('TLW_4_XML', 'true'); Redis::expire('TLW_4_XML', 1200); // 20 分鐘 // 取出資料 $info = explode('|', Redis::get('TLW_4_MAN')); $brand_no = $info[0]; $batch_no = $info[1]; $products = $this->productManagementSv->getEndpointProdocts($brand_no, $batch_no); // 拼裝XML // 開始寫入檔案 $path = '/tmp/tlwendpoint/' . env('APP_ENV') . '/' . GeneralConst::$brandMap[ $brand_no ]['LB'] . '/'; if (!file_exists($path)) mkdir($path, 0777, true); $fileW = fopen($path . 'FINAL.xml', "w"); fwrite($fileW, '特力屋' . GeneralConst::$brandMap[ $brand_no ]['URL'] . '' . "\n"); foreach ($products as $p) { fwrite($fileW, '' . "\n"); fwrite($fileW, '' . (string)$p['g_id'] . '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . (string)$p['g_condition'] . '' . "\n"); fwrite($fileW, '' . (string)$p['g_availability'] . '' . "\n"); fwrite($fileW, '' . (string)$p['g_price'] . '' . "\n"); fwrite($fileW, '' . (string)$p['g_sale_price'] . '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . (string)$p['g_google_product_category'] . '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . "\n"); fwrite($fileW, '' . "\n"); } fwrite($fileW, '' . $batch_no . '' . "\n"); // 結束寫入檔案 fclose($fileW); // 上傳至S3取代 $fileR = fopen($path . 'FINAL.xml', "r"); $file_name = env('APP_ENV') . '/' . GeneralConst::$brandMap[ $brand_no ]['LB'] . '/FINAL.xml'; $this->s3Client->putObject([ 'ACL' => 'public-read', 'Body' => $fileR, 'Bucket' => env('AWS_S3_BUCKET'), 'ContentType' => 'application/xml', 'Key' => $file_name, ]); fclose($fileR); // 刪除檔案 unlink($path . 'FINAL.xml'); Redis::del('TLW_4_XML'); } } }