设计一个功能全面的PHP在线图片压缩与处理工具方案。
一、系统架构设计
1. 技术栈
- 后端: PHP 7.4+ (使用GD库、Imagick)
- 前端: HTML5, CSS3, JavaScript (jQuery + 现代JS)
- 存储: 本地文件系统 + 可选云存储
- 队列: Redis + Beanstalkd (处理批量任务)
2. 目录结构
image-tool/
├── index.php // 入口文件
├── config/ // 配置文件
├── src/ // 核心源码
│ ├── Controllers/ // 控制器
│ ├── Models/ // 数据模型
│ ├── Services/ // 业务逻辑
│ │ ├── ImageProcessor.php // 图片处理核心
│ │ ├── CompressionService.php // 压缩服务
│ │ ├── CropService.php // 裁剪服务
│ │ └── BatchService.php // 批量处理
│ └── Utils/ // 工具类
├── public/ // 公共资源
├── uploads/ // 上传目录
├── temp/ // 临时文件
└── vendor/ // Composer依赖二、核心功能实现
1. 图片处理核心类
<?php
// src/Services/ImageProcessor.php
class ImageProcessor {
private $image;
private $imageInfo;
private $imagick;
public function __construct($filePath) {
if (extension_loaded('imagick')) {
$this->imagick = new \Imagick($filePath);
} else {
$this->imageInfo = getimagesize($filePath);
$this->loadImageWithGD($filePath);
}
}
/**
* 压缩图片
* @param int $quality 压缩质量 1-100
* @param int $targetSize 目标文件大小(KB)
* @param int $format 输出格式
*/
public function compress($quality = 80, $targetSize = null, $format = null) {
if ($this->imagick) {
return $this->compressWithImagick($quality, $targetSize, $format);
}
return $this->compressWithGD($quality, $targetSize, $format);
}
/**
* 智能压缩 - 根据目标大小自动调整质量
*/
public function smartCompress($targetSizeKB) {
$quality = 90;
$outputPath = $this->compress($quality);
$currentSize = filesize($outputPath) / 1024;
while ($currentSize > $targetSizeKB && $quality > 10) {
$quality -= 10;
$outputPath = $this->compress($quality);
$currentSize = filesize($outputPath) / 1024;
}
return $outputPath;
}
/**
* 裁剪图片
*/
public function crop($x, $y, $width, $height) {
if ($this->imagick) {
$this->imagick->cropImage($width, $height, $x, $y);
return $this->imagick->getImageBlob();
} else {
$newImage = imagecreatetruecolor($width, $height);
imagecopy($newImage, $this->image, 0, 0, $x, $y, $width, $height);
return $newImage;
}
}
/**
* 缩放图片
*/
public function resize($width, $height, $maintainRatio = true) {
if ($maintainRatio) {
$ratio = min($width / $this->getWidth(), $height / $this->getHeight());
$width = round($this->getWidth() * $ratio);
$height = round($this->getHeight() * $ratio);
}
if ($this->imagick) {
$this->imagick->resizeImage($width, $height, \Imagick::FILTER_LANCZOS, 1);
return $this->imagick;
} else {
$newImage = imagecreatetruecolor($width, $height);
imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
return $newImage;
}
}
}2. 压缩比例控制类
<?php
// src/Services/CompressionService.php
class CompressionService {
private $compressionLevels = [
'lossless' => ['quality' => 100, 'method' => 'PNG'],
'high' => ['quality' => 85, 'method' => 'JPEG'],
'medium' => ['quality' => 65, 'method' => 'JPEG'],
'low' => ['quality' => 45, 'method' => 'JPEG'],
'tiny' => ['quality' => 25, 'method' => 'JPEG']
];
/**
* 应用压缩级别
*/
public function applyCompression($filePath, $level = 'medium', $format = null) {
if (!isset($this->compressionLevels[$level])) {
$level = 'medium';
}
$settings = $this->compressionLevels[$level];
$processor = new ImageProcessor($filePath);
return $processor->compress($settings['quality'], null, $format);
}
/**
* 计算压缩率
*/
public function calculateRatio($originalSize, $compressedSize) {
$ratio = round((1 - ($compressedSize / $originalSize)) * 100, 2);
return [
'original' => $this->formatBytes($originalSize),
'compressed' => $this->formatBytes($compressedSize),
'saved' => $this->formatBytes($originalSize - $compressedSize),
'ratio' => $ratio,
'ratio_text' => "压缩了 {$ratio}%"
];
}
private function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
}3. 批量处理类
<?php
// src/Services/BatchService.php
class BatchService {
private $queue;
private $uploadDir;
private $outputDir;
private $batchId;
public function __construct() {
$this->queue = new \Beanstalkd\Client();
$this->queue->connect();
$this->batchId = uniqid('batch_', true);
}
/**
* 添加批量任务
*/
public function addBatchJob($files, $settings) {
$jobData = [
'batch_id' => $this->batchId,
'files' => $files,
'settings' => $settings,
'timestamp' => time()
];
// 创建任务目录
$this->createBatchDirectory($this->batchId);
// 将任务添加到队列
$this->queue->put(json_encode($jobData));
return [
'batch_id' => $this->batchId,
'total_files' => count($files),
'status_url' => "/status?batch_id={$this->batchId}"
];
}
/**
* 处理批量任务(由队列Worker执行)
*/
public function processBatch($jobData) {
$jobData = json_decode($jobData, true);
$results = [];
foreach ($jobData['files'] as $index => $file) {
try {
// 保存上传文件
$filePath = $this->saveFile($file, $jobData['batch_id'], $index);
// 处理图片
$processor = new ImageProcessor($filePath);
// 应用设置
if (isset($jobData['settings']['compress'])) {
$processor->compress($jobData['settings']['quality']);
}
if (isset($jobData['settings']['crop'])) {
$processor->crop(
$jobData['settings']['crop']['x'],
$jobData['settings']['crop']['y'],
$jobData['settings']['crop']['width'],
$jobData['settings']['crop']['height']
);
}
// 保存处理后的图片
$outputPath = $this->getOutputPath($file, $jobData['batch_id'], $index);
$processor->save($outputPath, $jobData['settings']['format'] ?? null);
// 计算压缩率
$ratio = $this->calculateRatio($filePath, $outputPath);
$results[] = [
'success' => true,
'original' => basename($file['name']),
'output' => basename($outputPath),
'ratio' => $ratio
];
} catch (Exception $e) {
$results[] = [
'success' => false,
'original' => basename($file['name']),
'error' => $e->getMessage()
];
}
}
// 保存处理结果
$this->saveResults($jobData['batch_id'], $results);
return $results;
}
/**
* 获取批量任务状态
*/
public function getBatchStatus($batchId) {
$statusFile = $this->getBatchDir($batchId) . '/status.json';
if (file_exists($statusFile)) {
return json_decode(file_get_contents($statusFile), true);
}
return [
'batch_id' => $batchId,
'status' => 'processing',
'progress' => $this->getProgress($batchId)
];
}
}4. 前端界面(简化版)
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>在线图片压缩处理工具</title>
<link rel="stylesheet" href="css/style.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/blueimp-file-upload@10.32.0/js/jquery.fileupload.min.js"></script>
</head>
<body>
<div class="container">
<h1>在线图片压缩处理工具</h1>
<!-- 上传区域 -->
<div class="upload-area" id="dropzone">
<i class="fas fa-cloud-upload-alt"></i>
<p>拖拽图片到此处或点击上传</p>
<input type="file" id="fileupload" name="files[]" multiple accept="image/*">
</div>
<!-- 处理选项 -->
<div class="options-panel">
<h3>处理选项</h3>
<div class="option-group">
<label>压缩级别:</label>
<select id="compressionLevel">
<option value="lossless">无损压缩</option>
<option value="high">高质量 (85%)</option>
<option value="medium" selected>中等质量 (65%)</option>
<option value="low">低质量 (45%)</option>
<option value="tiny">极小文件 (25%)</option>
</select>
</div>
<div class="option-group">
<label>目标大小:</label>
<input type="number" id="targetSize" placeholder="KB" min="10" max="10240">
<button id="autoOptimize">智能优化</button>
</div>
<div class="option-group">
<label>输出格式:</label>
<select id="outputFormat">
<option value="auto">自动</option>
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
<option value="webp">WebP</option>
<option value="gif">GIF</option>
</select>
</div>
<!-- 裁剪工具 -->
<div class="crop-tools">
<h4>裁剪设置</h4>
<button id="enableCrop">启用裁剪</button>
<div class="crop-options" style="display:none;">
<label>X: <input type="number" id="cropX" value="0"></label>
<label>Y: <input type="number" id="cropY" value="0"></label>
<label>宽度: <input type="number" id="cropWidth"></label>
<label>高度: <input type="number" id="cropHeight"></label>
<label>比例:
<select id="aspectRatio">
<option value="free">自由</option>
<option value="1:1">1:1</option>
<option value="4:3">4:3</option>
<option value="16:9">16:9</option>
</select>
</label>
</div>
</div>
<button id="processBtn" class="btn-primary">开始处理</button>
</div>
<!-- 文件列表 -->
<div class="file-list">
<table id="fileTable">
<thead>
<tr>
<th>预览</th>
<th>文件名</th>
<th>原始大小</th>
<th>处理后大小</th>
<th>压缩率</th>
<th>操作</th>
</tr>
</thead>
<tbody id="fileListBody"></tbody>
</table>
</div>
<!-- 批量操作 -->
<div class="batch-actions">
<button id="selectAll">全选</button>
<button id="downloadAll">下载全部</button>
<button id="clearAll">清空</button>
</div>
<!-- 进度条 -->
<div class="progress-bar" style="display:none;">
<div class="progress-fill"></div>
<span class="progress-text">0%</span>
</div>
</div>
<script src="js/main.js"></script>
</body>
</html>5. JavaScript 交互
// public/js/main.js
$(document).ready(function() {
let uploadedFiles = [];
let batchId = null;
// 文件上传配置
$('#fileupload').fileupload({
url: '/api/upload',
dataType: 'json',
dropZone: $('#dropzone'),
add: function(e, data) {
data.submit();
},
done: function(e, data) {
if (data.result.success) {
addFileToList(data.result.file);
uploadedFiles.push(data.result.file);
}
},
progressall: function(e, data) {
let progress = parseInt(data.loaded / data.total * 100, 10);
$('.progress-bar').show();
$('.progress-fill').css('width', progress + '%');
$('.progress-text').text(progress + '%');
}
});
// 处理按钮点击
$('#processBtn').click(function() {
if (uploadedFiles.length === 0) {
alert('请先上传文件');
return;
}
let settings = {
compressionLevel: $('#compressionLevel').val(),
targetSize: $('#targetSize').val(),
format: $('#outputFormat').val(),
crop: $('#enableCrop').is(':checked') ? {
x: $('#cropX').val(),
y: $('#cropY').val(),
width: $('#cropWidth').val(),
height: $('#cropHeight').val()
} : null
};
$.ajax({
url: '/api/batch/process',
method: 'POST',
data: {
files: uploadedFiles,
settings: settings
},
success: function(response) {
batchId = response.batch_id;
pollBatchStatus(batchId);
}
});
});
// 轮询批量任务状态
function pollBatchStatus(batchId) {
let interval = setInterval(function() {
$.get('/api/batch/status?batch_id=' + batchId, function(data) {
if (data.status === 'completed') {
clearInterval(interval);
updateResults(data.results);
}
updateProgress(data.progress);
});
}, 2000);
}
// 更新文件列表显示结果
function updateResults(results) {
results.forEach(function(result, index) {
let row = $('#fileListBody tr').eq(index);
if (result.success) {
row.find('.compressed-size').text(result.compressedSize);
row.find('.ratio').text(result.ratio.ratio_text);
row.find('.download-btn').show();
} else {
row.find('.status').text('处理失败: ' + result.error);
}
});
}
// 启用裁剪功能
$('#enableCrop').change(function() {
if ($(this).is(':checked')) {
$('.crop-options').slideDown();
// 初始化裁剪区域预览
initCropPreview();
} else {
$('.crop-options').slideUp();
}
});
// 裁剪比例选择
$('#aspectRatio').change(function() {
let ratio = $(this).val();
if (ratio !== 'free') {
let parts = ratio.split(':');
let aspect = parts[0] / parts[1];
// 根据比例调整裁剪框
adjustCropBox(aspect);
}
});
// 智能优化
$('#autoOptimize').click(function() {
let targetSize = $('#targetSize').val();
if (!targetSize) {
alert('请输入目标大小');
return;
}
$.ajax({
url: '/api/auto-optimize',
method: 'POST',
data: {
files: uploadedFiles,
targetSize: targetSize
},
success: function(response) {
// 更新压缩级别建议
$('#compressionLevel').val(response.suggested_level);
alert('建议使用 ' + response.suggested_level + ' 压缩级别');
}
});
});
// 全选/取消全选
$('#selectAll').click(function() {
let checkboxes = $('.file-checkbox');
let allChecked = checkboxes.filter(':checked').length === checkboxes.length;
checkboxes.prop('checked', !allChecked);
});
// 下载全部
$('#downloadAll').click(function() {
if (batchId) {
window.location.href = '/api/batch/download?batch_id=' + batchId;
}
});
});6. API 路由处理
<?php
// index.php - 路由处理
require_once 'vendor/autoload.php';
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
// 简单路由
switch ($uri) {
case '/api/upload':
if ($method === 'POST') {
handleUpload();
}
break;
case '/api/batch/process':
if ($method === 'POST') {
handleBatchProcess();
}
break;
case '/api/batch/status':
if ($method === 'GET') {
handleBatchStatus();
}
break;
case '/api/auto-optimize':
if ($method === 'POST') {
handleAutoOptimize();
}
break;
default:
// 返回前端页面
readfile('public/index.html');
}
function handleUpload() {
$uploader = new \App\Services\UploadService();
$result = $uploader->upload($_FILES['files']);
header('Content-Type: application/json');
echo json_encode($result);
}
function handleBatchProcess() {
$input = json_decode(file_get_contents('php://input'), true);
$batchService = new \App\Services\BatchService();
$result = $batchService->addBatchJob(
$input['files'],
$input['settings']
);
header('Content-Type: application/json');
echo json_encode($result);
}
function handleBatchStatus() {
$batchId = $_GET['batch_id'];
$batchService = new \App\Services\BatchService();
$status = $batchService->getBatchStatus($batchId);
header('Content-Type: application/json');
echo json_encode($status);
}
function handleAutoOptimize() {
$input = json_decode(file_get_contents('php://input'), true);
$optimizer = new \App\Services\OptimizationService();
$suggestion = $optimizer->suggestCompressionLevel(
$input['files'],
$input['targetSize']
);
header('Content-Type: application/json');
echo json_encode($suggestion);
}7. 数据库设计(可选,用于记录历史)
-- 创建用户上传记录表
CREATE TABLE `uploads` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`batch_id` VARCHAR(64) NOT NULL,
`original_name` VARCHAR(255) NOT NULL,
`file_path` VARCHAR(500) NOT NULL,
`file_size` INT NOT NULL,
`file_type` VARCHAR(50) NOT NULL,
`compressed_size` INT,
`compression_ratio` DECIMAL(5,2),
`settings` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`download_count` INT DEFAULT 0,
INDEX idx_batch (`batch_id`)
);
-- 创建批量任务表
CREATE TABLE `batch_jobs` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`batch_id` VARCHAR(64) NOT NULL UNIQUE,
`status` ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending',
`total_files` INT NOT NULL,
`processed_files` INT DEFAULT 0,
`settings` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`completed_at` TIMESTAMP NULL,
INDEX idx_status (`status`)
);三、部署配置
1. PHP 配置要求
; php.ini 必要配置
upload_max_filesize = 100M
post_max_size = 100M
max_execution_time = 300
memory_limit = 512M2. Nginx 配置
server {
listen 80;
server_name image-tool.local;
root /var/www/image-tool/public;
client_max_body_size 100M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# 上传文件目录
location /uploads {
internal; # 禁止直接访问
alias /var/www/image-tool/uploads;
}
}3. 队列 Worker 脚本
<?php
// worker.php - 后台处理批量任务
require_once 'vendor/autoload.php';
use Pheanstalk\Pheanstalk;
$pheanstalk = Pheanstalk::create('127.0.0.1');
$batchService = new \App\Services\BatchService();
while (true) {
$job = $pheanstalk
->watch('image_tasks')
->ignore('default')
->reserve();
try {
$result = $batchService->processBatch($job->getData());
$pheanstalk->delete($job);
// 记录日志
error_log("Processed job: " . $job->getId());
} catch (Exception $e) {
$pheanstalk->bury($job);
error_log("Failed job: " . $job->getId() . " - " . $e->getMessage());
}
sleep(1);
}四、功能特点总结
- 多格式支持: JPEG, PNG, GIF, WebP, BMP
- 灵活压缩: 支持质量调节、目标大小智能压缩
- 批量处理: 队列系统支持大量图片并发处理
- 实时预览: 前端实时显示处理效果
- 裁剪功能: 支持自由裁剪和固定比例裁剪
- 压缩统计: 显示压缩率、节省空间等数据
- 下载选项: 单文件下载、批量打包下载
- 安全性: 文件类型验证、上传目录保护
这个方案提供了一个完整的在线图片处理工具,可根据实际需求进行调整和扩展。
暂无评论