5月 07, 2024

PHP Image Reciever

不論多久之前或未來都可能會寫到的功能,剛好手邊遇到就先記錄一下
最主要的接收參數是透過 raw body 的方式接收(格式比較好處理+內容可以接收比較大)
至於 body 的內容怎麼設計就可以看自己,這裡主要就是個 demo

以下是程式碼片段

config.php

define('AVATAR_FOLDER', __DIR__ . '/static/avatar/');
define('AVATAR_URL', 'http://your.domain.name/static/avatar/');
define('MAX_AVATAR_SIZE', 3000); // 3MB
 
header('Content-Type: application/json');
require_once __DIR__ . '/config.php';

$data = file_get_contents('php://input');
// struct 
// {
//     avatar: base64 string = image
//     avatar_type: string = 'jpg/png'
// }
if (empty($data)) {
    display_error_message('未傳入資料');
}

$original_data = json_decode($data, true);
if (json_last_error() != JSON_ERROR_NONE) {
    display_error_message('錯誤的資料格式');
}
$extname = "abcd"; // 自訂,或從 data 取得
$avatar_data = $original_data['avatar'];
$avatar_type = $original_data['avatar_type'];

if (empty($avatar_data) || empty($avatar_type)) {
    display_error_message('缺少必要參數');
}
$avatar = base64_decode($avatar_data);

$avatar_file_size =  round(strlen($avatar) / 1000);
if ($avatar_file_size > MAX_AVATAR_SIZE) {
    display_error_message('檔案大小超過限制 (3MB)');
}
$tmp_name = 'tmp_' . microtime(true) . '_' . $extname . '.' . $avatar_type;
$tmp_orig_file = __DIR__ . '/tmp/' . $tmp_name;
$save_to_tmp = file_put_contents($tmp_orig_file, $avatar);
if (!$save_to_tmp) {
    display_error_message('檔案上傳失敗');
}

$mime_info = mime_content_type($tmp_orig_file);

if (!in_array($mime_info, array('image/jpeg', 'image/png', 'image/jpg'))) {
    unlink($tmp_orig_file);
    display_error_message('不接受的檔案格式');
}

if ($mime_info == 'image/jpeg' || $mime_info == 'image/jpg') {
    $avatar_type = 'jpg';
}
if ($mime_info == 'image/png') {
    $avatar_type = 'png';
}
$resized = resizeImage($tmp_orig_file, $avatar_type, 100, 100, $tmp_orig_file);
if (!$resized) {
    unlink($tmp_orig_file);
    display_error_message('圖片處理失敗,請重新上傳');
}

$new_name = 'a_' . sha1($ext_name) . '.' . $avatar_type;
$result = rename($tmp_orig_file, AVATAR_FOLDER . $new_name);
if (!$result) {
    unlink($tmp_orig_file);
    display_error_message('檔案移動失敗');
}
echo json_encode(array('status' => 'success', 'message' => AVATAR_URL . $new_name));

function display_error_message($message) {
    header('HTTP/1.1 400 Bad Request');
    echo json_encode(array('status' => 'error', 'message' => $message));
    exit();
}

function resizeImage($sourceImage, $imageType, $targetWidth, $targetHeight, $outputImage) {
    list($sourceWidth, $sourceHeight) = getimagesize($sourceImage);
    if ($imageType == 'png') {
        $sourceResource = imagecreatefrompng($sourceImage);
    } else if ($imageType == 'jpg') {
        $sourceResource = imagecreatefromjpeg($sourceImage);
    } else {
        return false;
    }
    $targetResource = imagecreatetruecolor($targetWidth, $targetHeight);
    imagecopyresampled($targetResource, $sourceResource, 0, 0, 0, 0, $targetWidth, $targetHeight, $sourceWidth, $sourceHeight);
    imagejpeg($targetResource, $outputImage);
    imagedestroy($sourceResource);
    imagedestroy($targetResource);
    return true;
}

接收並儲存資料最大的問題還是在於檢查接收的檔案是不是符合格式

並且存放在「僅瀏覽」的目錄,另外包含檔名也要做轉換,避免被隨意試出

僅瀏覽的目的是避免被上傳危險的檔案後,還能夠執行,不論是 .php、.js 甚至其他可以透過引用執行的方式都是

最後也要控制檔案大小,避免容量或流量被塞爆,但流量的問題可以再透過靜態 CDN 等工具來保護處理

大概就是這樣

備註一下,因為裡面有用到 resize 的功能再加上 PHP 版本可能有影響,所以針對較低版本的要注意 extension 有沒有打開或者使用的 function 是否要修改

沒有留言:

張貼留言