上传服务
基于 app/System/Service/Upload.php
的上传服务,提供文件上传配置管理和安全检查功能。
🚀 快速开始
基础使用
php
use App\System\Service\Upload;
// 获取上传配置
$config = Upload::getUploadConfig();
// 检查文件扩展名
$isAllowed = Upload::checkExtension('jpg');
// 验证文件安全性
$isSafe = Upload::validateFile($uploadedFile);
📋 主要功能
1. 获取上传配置
php
use App\System\Service\Upload;
// 获取完整的上传配置
$config = Upload::getUploadConfig();
/*
返回数据结构:
[
'upload_ext' => ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt'], // 允许的扩展名
'upload_size' => 5, // 最大文件大小(MB)
// ... 其他系统配置
]
*/
2. 文件扩展名检查
基于实际代码的扩展名验证:
php
use App\System\Service\Upload;
// 检查单个扩展名
$isAllowed = Upload::checkExtension('jpg'); // true
$isBlocked = Upload::checkExtension('php'); // false (在黑名单中)
// 检查文件对象的扩展名
$uploadedFile = $request->getUploadedFiles()['file'];
$extension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
$isValid = Upload::checkExtension($extension);
3. 黑名单扩展名
系统内置的危险文件扩展名黑名单:
php
// 系统禁止的文件扩展名
private const BLACK_EXTENSIONS = [
// PHP 相关
'php', 'php3', 'php4', 'php5', 'phtml', 'pht',
// Web 脚本
'jsp', 'asp', 'aspx', 'cer', 'asa', 'cdx',
// 客户端脚本
'js', 'vbs', 'bat', 'cmd', 'com', 'exe', 'scr', 'msi',
// 系统脚本
'sh', 'py', 'pl', 'rb', 'jar', 'class',
// 配置文件
'htaccess', 'htpasswd', 'ini', 'dll', 'so'
];
🔧 实际应用示例
1. 控制器中的文件上传
php
<?php
namespace App\Content\Admin;
use App\System\Service\Upload;
use App\System\Service\Storage;
use Core\Resources\Action\Resources;
use Core\Resources\Attribute\Resource;
#[Resource(app: "admin", route: "/content/media", name: "content.media")]
class Media extends Resources
{
public function upload(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
try {
// 获取上传文件
$uploadedFile = $request->getUploadedFiles()['file'] ?? null;
if (!$uploadedFile) {
return error($response, '请选择要上传的文件');
}
// 获取上传配置
$config = Upload::getUploadConfig();
// 验证文件扩展名
$filename = $uploadedFile->getClientFilename();
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($extension, $config['upload_ext'])) {
return error($response, '不支持的文件类型');
}
// 验证文件大小
$maxSize = $config['upload_size'] * 1024 * 1024; // 转换为字节
if ($uploadedFile->getSize() > $maxSize) {
return error($response, "文件大小不能超过 {$config['upload_size']}MB");
}
// 使用存储服务上传文件
$storage = Storage::getObject();
$path = 'uploads/' . date('Y/m/d') . '/' . uniqid() . '.' . $extension;
$stream = $uploadedFile->getStream();
$storage->writeStream($path, $stream->detach());
return success($response, '上传成功', [
'url' => $storage->url($path),
'path' => $path,
'size' => $uploadedFile->getSize(),
'name' => $filename
]);
} catch (\Exception $e) {
return error($response, '上传失败:' . $e->getMessage());
}
}
}
2. 批量文件验证
php
<?php
namespace App\System\Service;
class FileValidator
{
/**
* 批量验证文件
*/
public static function validateFiles(array $files): array
{
$config = Upload::getUploadConfig();
$results = [];
$maxSize = $config['upload_size'] * 1024 * 1024;
foreach ($files as $index => $file) {
$result = [
'index' => $index,
'name' => $file->getClientFilename(),
'size' => $file->getSize(),
'valid' => true,
'errors' => []
];
// 检查扩展名
$extension = strtolower(pathinfo($file->getClientFilename(), PATHINFO_EXTENSION));
if (!in_array($extension, $config['upload_ext'])) {
$result['valid'] = false;
$result['errors'][] = '不支持的文件类型';
}
// 检查文件大小
if ($file->getSize() > $maxSize) {
$result['valid'] = false;
$result['errors'][] = "文件大小超过限制({$config['upload_size']}MB)";
}
// 检查文件内容
if ($file->getSize() === 0) {
$result['valid'] = false;
$result['errors'][] = '文件内容为空';
}
$results[] = $result;
}
return $results;
}
}
3. 图片上传专用服务
php
<?php
namespace App\System\Service;
class ImageUpload
{
/**
* 上传并处理图片
*/
public static function uploadImage($uploadedFile, array $options = []): array
{
// 获取配置
$config = Upload::getUploadConfig();
// 验证是否为图片
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$extension = strtolower(pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION));
if (!in_array($extension, $imageExtensions)) {
throw new \Exception('只支持图片文件');
}
// 验证图片尺寸
$tempPath = $uploadedFile->getStream()->getMetadata('uri');
$imageInfo = getimagesize($tempPath);
if (!$imageInfo) {
throw new \Exception('无效的图片文件');
}
[$width, $height] = $imageInfo;
// 检查最小尺寸
$minWidth = $options['min_width'] ?? 100;
$minHeight = $options['min_height'] ?? 100;
if ($width < $minWidth || $height < $minHeight) {
throw new \Exception("图片尺寸不能小于 {$minWidth}x{$minHeight}");
}
// 检查最大尺寸
$maxWidth = $options['max_width'] ?? 2000;
$maxHeight = $options['max_height'] ?? 2000;
if ($width > $maxWidth || $height > $maxHeight) {
throw new \Exception("图片尺寸不能大于 {$maxWidth}x{$maxHeight}");
}
// 上传文件
$storage = Storage::getObject();
$path = 'images/' . date('Y/m/d') . '/' . uniqid() . '.' . $extension;
$stream = $uploadedFile->getStream();
$storage->writeStream($path, $stream->detach());
return [
'url' => $storage->url($path),
'path' => $path,
'width' => $width,
'height' => $height,
'size' => $uploadedFile->getSize(),
'name' => $uploadedFile->getClientFilename()
];
}
}
💡 最佳实践
1. 安全检查
php
// ✅ 完整的文件安全检查
public function validateUploadSecurity($uploadedFile): bool
{
// 1. 检查扩展名
$extension = strtolower(pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION));
$config = Upload::getUploadConfig();
if (!in_array($extension, $config['upload_ext'])) {
return false;
}
// 2. 检查 MIME 类型
$allowedMimes = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'pdf' => 'application/pdf'
];
$expectedMime = $allowedMimes[$extension] ?? null;
if ($expectedMime && $uploadedFile->getClientMediaType() !== $expectedMime) {
return false;
}
// 3. 检查文件头
$tempPath = $uploadedFile->getStream()->getMetadata('uri');
$fileHeader = file_get_contents($tempPath, false, null, 0, 10);
// 检查是否包含可执行代码特征
$dangerousPatterns = ['<?php', '<%', '<script', '#!/'];
foreach ($dangerousPatterns as $pattern) {
if (stripos($fileHeader, $pattern) !== false) {
return false;
}
}
return true;
}
2. 配置管理
php
// ✅ 动态配置管理
class UploadConfigManager
{
public static function updateConfig(array $newConfig): void
{
$currentConfig = \App\System\Service\Config::getJsonValue('system', []);
// 合并配置
$updatedConfig = array_merge($currentConfig, [
'upload_ext' => implode(',', $newConfig['extensions'] ?? []),
'upload_size' => $newConfig['max_size'] ?? 5
]);
\App\System\Service\Config::setValue('system', $updatedConfig);
}
public static function addAllowedExtension(string $extension): void
{
$config = Upload::getUploadConfig();
$extensions = $config['upload_ext'];
if (!in_array($extension, $extensions)) {
$extensions[] = $extension;
self::updateConfig(['extensions' => $extensions]);
}
}
}
3. 错误处理
php
// ✅ 统一的错误处理
class UploadException extends \Exception
{
public const ERROR_INVALID_TYPE = 1001;
public const ERROR_SIZE_EXCEEDED = 1002;
public const ERROR_SECURITY_RISK = 1003;
public static function invalidType(string $extension): self
{
return new self("不支持的文件类型: {$extension}", self::ERROR_INVALID_TYPE);
}
public static function sizeExceeded(int $size, int $maxSize): self
{
$sizeMB = round($size / 1024 / 1024, 2);
$maxSizeMB = round($maxSize / 1024 / 1024, 2);
return new self("文件大小 {$sizeMB}MB 超过限制 {$maxSizeMB}MB", self::ERROR_SIZE_EXCEEDED);
}
public static function securityRisk(): self
{
return new self("文件存在安全风险", self::ERROR_SECURITY_RISK);
}
}
🎉 总结
上传服务的特点:
- 🛡️ 安全可靠:内置黑名单扩展名,防止恶意文件上传
- ⚙️ 配置灵活:基于数据库配置,支持动态调整
- 🔧 易于集成:简单的静态方法调用
- 📊 功能完整:支持扩展名检查、大小验证、安全检查
通过合理使用上传服务,可以构建安全可靠的文件上传功能!