'API 連線正常', 'version' => '1.0.0' ]); } // 檢查是否為批次處理 if (!empty($input['batch']) && !empty($input['students'])) { $results = processBatch($input['students'], $input['actions'] ?? []); echo json_encode(['success' => true, 'results' => $results]); exit; } // 單筆處理 $result = processSingleStudent($input); sendSuccess($result); } catch (Exception $e) { sendError('EXCEPTION', $e->getMessage(), 'unknown'); } // ============ 處理函數 ============ /** * 處理單筆學員 */ function processSingleStudent($input) { // 驗證必要欄位 $required = ['class_prefix', 'student_no', 'name', 'email']; foreach ($required as $field) { if (empty($input[$field])) { throw new Exception("缺少必要欄位: {$field}"); } } $classPrefix = trim($input['class_prefix']); $studentNo = trim($input['student_no']); $name = trim($input['name']); $email = trim($input['email']); $phone = trim($input['phone'] ?? ''); $actions = $input['actions'] ?? []; // 1. 計算 Hash $qrcodeId = hashClassPrefixEmail($classPrefix, $email); $hashParameter = shortStr($name . $studentNo, 8); $hashFilename = shortStrEnc($hashParameter, 8, ENC_TABLE_64); // 2. 產生 QR Code $qrcodeUrl = QRCODE_BASE_URL . $qrcodeId; $filename = "{$studentNo}_{$hashFilename}.jpg"; $qrcodeImage = generateQRCode($qrcodeUrl); // 3. 儲存到 Server(如果啟用) if (!empty($actions['upload_to_server'])) { saveQRCodeToServer($qrcodeImage, $filename); } // 4. 寫入資料庫(如果啟用) if (!empty($actions['save_to_db'])) { saveToDatabase([ 'class_prefix' => $classPrefix, 'student_no' => $studentNo, 'name' => $name, 'email' => $email, 'phone' => $phone, 'qrcode_id' => $qrcodeId, 'filename' => $filename ]); } return [ 'qrcode_id' => $qrcodeId, 'qrcode_url' => $qrcodeUrl, 'qrcode_image_base64' => base64_encode($qrcodeImage), 'filename' => $filename, 'file_link' => QRCODE_ASSET_PATH . $filename ]; } /** * 批次處理 */ function processBatch($students, $actions) { $results = []; foreach ($students as $student) { try { $student['actions'] = $actions; $result = processSingleStudent($student); $results[] = [ 'success' => true, 'student_no' => $student['student_no'], 'data' => $result ]; } catch (Exception $e) { $results[] = [ 'success' => false, 'student_no' => $student['student_no'] ?? 'unknown', 'error' => [ 'code' => 'PROCESS_ERROR', 'message' => $e->getMessage() ] ]; } } return $results; } // ============ Hash 函數(與 Python 版本一致)============ /** * 計算 QR Code ID */ function hashClassPrefixEmail($classPrefix, $email) { $salt = 'newQRCODE' . $classPrefix . $email . 'from20201118byBaoYunIdea'; return md5($salt); } /** * 短 Hash(SHA512 前 N 字元) */ function shortStr($s, $charLength = 8) { if ($charLength > 128) { throw new Exception("char_length {$charLength} exceeds 128"); } $hashHex = hash('sha512', $s); return substr($hashHex, 0, $charLength); } /** * 自訂編碼的短 Hash */ function shortStrEnc($s, $charLength = 8, $encTable = ENC_TABLE_64) { if ($charLength > 128) { throw new Exception("char_length {$charLength} exceeds 128"); } $hashHex = hash('sha512', $s); $hashEnc = intToEnc(hexToInt($hashHex), $encTable); return substr($hashEnc, 0, $charLength); } /** * 十六進位轉大整數(使用 BCMath) */ function hexToInt($hex) { $dec = '0'; $len = strlen($hex); for ($i = 0; $i < $len; $i++) { $dec = bcadd(bcmul($dec, '16'), hexdec($hex[$i])); } return $dec; } /** * 大整數轉自訂編碼 */ function intToEnc($n, $encTable) { if ($n === '0') { return $encTable[0]; } $base = strlen($encTable); $digits = ''; while (bccomp($n, '0') > 0) { $remainder = bcmod($n, $base); $digits .= $encTable[(int)$remainder]; $n = bcdiv($n, $base, 0); } return strrev($digits); } // ============ QR Code 產生 ============ /** * 產生 QR Code 圖片 */ function generateQRCode($data) { // 方法 1:使用 endroid/qr-code(推薦,需要 composer 安裝) if (class_exists('Endroid\QrCode\QrCode')) { return generateQRCodeEndroid($data); } // 方法 2:使用 Google Charts API(備用) return generateQRCodeGoogleCharts($data); } /** * 使用 endroid/qr-code 產生 */ function generateQRCodeEndroid($data) { // 需要先安裝:composer require endroid/qr-code use Endroid\QrCode\QrCode; use Endroid\QrCode\Writer\PngWriter; use Endroid\QrCode\ErrorCorrectionLevel; $qrCode = new QrCode($data); $qrCode->setSize(300); $qrCode->setMargin(10); $qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::Low); $writer = new PngWriter(); $result = $writer->write($qrCode); return $result->getString(); } /** * 使用 Google Charts API 產生(備用方案) */ function generateQRCodeGoogleCharts($data) { $url = 'https://chart.googleapis.com/chart?' . http_build_query([ 'cht' => 'qr', 'chs' => '300x300', 'chl' => $data, 'choe' => 'UTF-8' ]); $context = stream_context_create([ 'http' => [ 'timeout' => 10 ] ]); $image = file_get_contents($url, false, $context); if ($image === false) { throw new Exception('無法從 Google Charts API 取得 QR Code'); } return $image; } // ============ 檔案儲存 ============ /** * 儲存 QR Code 到 Server */ function saveQRCodeToServer($imageData, $filename) { $savePath = QRCODE_SAVE_DIR . $filename; // 確保目錄存在 $dir = dirname($savePath); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // 儲存檔案 $result = file_put_contents($savePath, $imageData); if ($result === false) { throw new Exception('無法儲存 QR Code 檔案'); } return true; } // ============ 資料庫操作 ============ /** * 取得資料庫連線 */ function getDbConnection() { static $pdo = null; if ($pdo === null) { try { $dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4'; $pdo = new PDO($dsn, DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]); } catch (PDOException $e) { throw new Exception('資料庫連線失敗: ' . $e->getMessage()); } } return $pdo; } /** * 寫入資料庫 */ function saveToDatabase($data) { $pdo = getDbConnection(); // 檢查是否已存在 $stmt = $pdo->prepare('SELECT id FROM students WHERE student_no = ?'); $stmt->execute([$data['student_no']]); $existing = $stmt->fetch(); if ($existing) { // 更新 $sql = 'UPDATE students SET class_prefix = ?, name = ?, email = ?, phone = ?, qrcode_id = ?, qrcode_filename = ?, updated_at = NOW() WHERE student_no = ?'; $params = [ $data['class_prefix'], $data['name'], $data['email'], $data['phone'], $data['qrcode_id'], $data['filename'], $data['student_no'] ]; } else { // 新增 $sql = 'INSERT INTO students (class_prefix, student_no, name, email, phone, qrcode_id, qrcode_filename, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())'; $params = [ $data['class_prefix'], $data['student_no'], $data['name'], $data['email'], $data['phone'], $data['qrcode_id'], $data['filename'] ]; } $stmt = $pdo->prepare($sql); $stmt->execute($params); return true; } // ============ 回應函數 ============ /** * 送出成功回應 */ function sendSuccess($data) { echo json_encode([ 'success' => true, 'data' => $data ], JSON_UNESCAPED_UNICODE); exit; } /** * 送出錯誤回應 */ function sendError($code, $message, $stage, $httpCode = 400) { http_response_code($httpCode); echo json_encode([ 'success' => false, 'error' => [ 'code' => $code, 'message' => $message, 'stage' => $stage ] ], JSON_UNESCAPED_UNICODE); exit; }