Laravel 13 文件存储高级特性深度解析
文件存储是 Laravel 处理文件上传、存储和管理的强大工具。本文将深入探讨 Laravel 13 中文件存储的高级特性。
文件存储基础回顾
基本操作
1 2 3 4 5 6 7 8 9
| <?php
use Illuminate\Support\Facades\Storage;
Storage::put('file.txt', 'content'); Storage::get('file.txt'); Storage::exists('file.txt'); Storage::delete('file.txt'); Storage::url('file.txt');
|
多磁盘支持
配置磁盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?php
return [ 'default' => env('FILESYSTEM_DISK', 'local'),
'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), ],
'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL') . '/storage', 'visibility' => 'public', ],
's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), ],
'ftp' => [ 'driver' => 'ftp', 'host' => env('FTP_HOST'), 'username' => env('FTP_USERNAME'), 'password' => env('FTP_PASSWORD'), ], ], ];
|
使用不同磁盘
1 2 3 4 5 6 7
| <?php
use Illuminate\Support\Facades\Storage;
Storage::disk('local')->put('file.txt', 'content'); Storage::disk('s3')->put('file.txt', 'content'); Storage::disk('public')->url('file.txt');
|
文件上传
基本上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?php
namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage;
class FileController extends Controller { public function upload(Request $request) { $request->validate([ 'file' => 'required|file|max:10240', ]);
$path = $request->file('file')->store('uploads');
return response()->json([ 'path' => $path, 'url' => Storage::url($path), ]); }
public function uploadToS3(Request $request) { $path = $request->file('file')->store('uploads', 's3');
return response()->json([ 'path' => $path, 'url' => Storage::disk('s3')->url($path), ]); } }
|
自定义文件名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <?php
namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str;
class FileController extends Controller { public function uploadWithCustomName(Request $request) { $file = $request->file('file'); $extension = $file->getClientOriginalExtension(); $filename = Str::uuid() . '.' . $extension;
$path = $file->storeAs('uploads', $filename);
return response()->json([ 'path' => $path, 'filename' => $filename, ]); }
public function uploadWithOriginalName(Request $request) { $file = $request->file('file'); $originalName = $file->getClientOriginalName(); $safeName = $this->sanitizeFilename($originalName);
$path = $file->storeAs('uploads', $safeName);
return response()->json(['path' => $path]); }
protected function sanitizeFilename(string $filename): string { $extension = pathinfo($filename, PATHINFO_EXTENSION); $name = pathinfo($filename, PATHINFO_FILENAME);
$safeName = Str::slug($name); $uniqueName = $safeName . '-' . Str::random(8);
return $uniqueName . '.' . $extension; } }
|
文件可见性
设置可见性
1 2 3 4 5 6 7 8 9
| <?php
use Illuminate\Support\Facades\Storage;
Storage::put('file.txt', 'content', 'public'); Storage::put('file.txt', 'content', 'private');
Storage::setVisibility('file.txt', 'public'); Storage::getVisibility('file.txt');
|
预签名 URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
use Illuminate\Support\Facades\Storage;
$url = Storage::temporaryUrl('file.pdf', now()->addMinutes(5));
$url = Storage::disk('s3')->temporaryUrl( 'file.pdf', now()->addMinutes(30), [ 'ResponseContentType' => 'application/pdf', 'ResponseContentDisposition' => 'attachment; filename="document.pdf"', ] );
|
目录操作
创建目录
1 2 3 4 5 6
| <?php
use Illuminate\Support\Facades\Storage;
Storage::makeDirectory('photos/2024'); Storage::makeDirectory('photos/2024', 0755, true);
|
列出文件
1 2 3 4 5 6 7 8 9
| <?php
use Illuminate\Support\Facades\Storage;
$files = Storage::files('photos'); $allFiles = Storage::allFiles('photos');
$directories = Storage::directories('photos'); $allDirectories = Storage::allDirectories('photos');
|
删除目录
1 2 3 4 5
| <?php
use Illuminate\Support\Facades\Storage;
Storage::deleteDirectory('photos/2024');
|
文件元数据
获取文件信息
1 2 3 4 5 6 7 8 9
| <?php
use Illuminate\Support\Facades\Storage;
$size = Storage::size('file.txt'); $mimeType = Storage::mimeType('file.txt'); $lastModified = Storage::lastModified('file.txt');
$path = Storage::path('file.txt');
|
自定义元数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Storage;
class FileMetadataService { public function getMetadata(string $path): array { return [ 'path' => $path, 'size' => Storage::size($path), 'mime_type' => Storage::mimeType($path), 'last_modified' => Storage::lastModified($path), 'url' => Storage::url($path), 'exists' => Storage::exists($path), ]; }
public function getDetailedMetadata(string $path): array { $metadata = $this->getMetadata($path);
if (str_starts_with($metadata['mime_type'], 'image/')) { $imageInfo = $this->getImageInfo($path); $metadata = array_merge($metadata, $imageInfo); }
return $metadata; }
protected function getImageInfo(string $path): array { $fullPath = Storage::path($path); $info = getimagesize($fullPath);
return [ 'width' => $info[0] ?? null, 'height' => $info[1] ?? null, 'type' => $info[2] ?? null, 'aspect_ratio' => ($info[0] ?? 0) / ($info[1] ?? 1), ]; } }
|
文件复制和移动
复制文件
1 2 3 4 5 6 7 8 9
| <?php
use Illuminate\Support\Facades\Storage;
Storage::copy('old/file.txt', 'new/file.txt');
Storage::disk('local')->copy('file.txt', 'backup/file.txt');
Storage::disk('s3')->copy('file.txt', 'archive/file.txt');
|
移动文件
1 2 3 4 5 6 7
| <?php
use Illuminate\Support\Facades\Storage;
Storage::move('old/file.txt', 'new/file.txt');
Storage::disk('local')->move('file.txt', 'archive/file.txt');
|
跨磁盘操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Storage;
class FileTransferService { public function transferBetweenDisks( string $sourceDisk, string $sourcePath, string $targetDisk, string $targetPath ): bool { $content = Storage::disk($sourceDisk)->get($sourcePath);
return Storage::disk($targetDisk)->put($targetPath, $content); }
public function moveToS3(string $localPath, string $s3Path): bool { $content = Storage::disk('local')->get($localPath);
$success = Storage::disk('s3')->put($s3Path, $content);
if ($success) { Storage::disk('local')->delete($localPath); }
return $success; } }
|
流式处理
流式上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage;
class StreamUploadController extends Controller { public function upload(Request $request) { $file = $request->file('file');
$stream = fopen($file->getRealPath(), 'r');
Storage::disk('s3')->put( 'uploads/' . $file->getClientOriginalName(), $stream );
fclose($stream);
return response()->json(['success' => true]); } }
|
流式下载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Storage;
class StreamDownloadController extends Controller { public function download(string $path) { $stream = Storage::readStream($path);
return response()->stream(function () use ($stream) { fpassthru($stream); }, 200, [ 'Content-Type' => Storage::mimeType($path), 'Content-Length' => Storage::size($path), ]); }
public function downloadFromS3(string $path) { return Storage::disk('s3')->download($path); } }
|
自定义文件系统
自定义驱动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
namespace App\Filesystem;
use Illuminate\Filesystem\FilesystemAdapter; use League\Flysystem\FilesystemAdapter as FlysystemAdapter;
class CustomFilesystemAdapter extends FilesystemAdapter { public function __construct(FlysystemAdapter $adapter, array $config = []) { parent::__construct($adapter, $config); }
public function getUrl(string $path): string { return $this->config['url'] . '/' . $path; }
public function getTemporaryUrl(string $path, \DateTimeInterface $expiration, array $options = []): string { return $this->adapter->getTemporaryUrl($path, $expiration, $options); } }
|
注册自定义驱动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Storage; use App\Filesystem\CustomFilesystemAdapter;
class FilesystemServiceProvider extends ServiceProvider { public function boot(): void { Storage::extend('custom', function ($app, $config) { $adapter = new CustomFilesystemAdapter( new \App\Filesystem\CustomAdapter($config), $config );
return $adapter; }); } }
|
文件处理服务
图片处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Storage; use Intervention\Image\Facades\Image;
class ImageProcessingService { public function resize( string $path, int $width, int $height, ?string $destination = null ): string { $image = Image::make(Storage::path($path));
$image->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); });
$destinationPath = $destination ?? $this->getResizedPath($path, $width, $height);
Storage::put($destinationPath, $image->stream());
return $destinationPath; }
public function createThumbnail(string $path, int $size = 200): string { return $this->resize($path, $size, $size, $this->getThumbnailPath($path)); }
public function watermark(string $path, string $watermarkPath): string { $image = Image::make(Storage::path($path)); $watermark = Image::make(Storage::path($watermarkPath));
$image->insert($watermark, 'bottom-right', 10, 10);
Storage::put($path, $image->stream());
return $path; }
protected function getResizedPath(string $path, int $width, int $height): string { $info = pathinfo($path);
return sprintf( '%s/%s_%dx%d.%s', $info['dirname'], $info['filename'], $width, $height, $info['extension'] ); }
protected function getThumbnailPath(string $path): string { $info = pathinfo($path);
return sprintf( '%s/thumbnails/%s.%s', $info['dirname'], $info['filename'], $info['extension'] ); } }
|
文件压缩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Storage; use ZipArchive;
class FileCompressionService { public function compressDirectory(string $directory, string $zipPath): bool { $zip = new ZipArchive(); $fullZipPath = Storage::path($zipPath);
if ($zip->open($fullZipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { return false; }
$files = Storage::allFiles($directory);
foreach ($files as $file) { $zip->addFile( Storage::path($file), str_replace($directory . '/', '', $file) ); }
$zip->close();
return true; }
public function extractZip(string $zipPath, string $destination): bool { $zip = new ZipArchive(); $fullZipPath = Storage::path($zipPath);
if ($zip->open($fullZipPath) !== true) { return false; }
$fullDestination = Storage::path($destination);
$zip->extractTo($fullDestination); $zip->close();
return true; } }
|
测试文件存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?php
namespace Tests\Unit\Services;
use Tests\TestCase; use Illuminate\Support\Facades\Storage; use App\Services\FileMetadataService;
class StorageTest extends TestCase { public function test_file_upload(): void { Storage::fake('local');
Storage::disk('local')->put('file.txt', 'content');
Storage::disk('local')->assertExists('file.txt'); }
public function test_file_deletion(): void { Storage::fake('local');
Storage::disk('local')->put('file.txt', 'content'); Storage::disk('local')->delete('file.txt');
Storage::disk('local')->assertMissing('file.txt'); }
public function test_file_metadata(): void { Storage::fake('local');
Storage::disk('local')->put('file.txt', 'content');
$service = new FileMetadataService(); $metadata = $service->getMetadata('file.txt');
$this->assertEquals(7, $metadata['size']); $this->assertTrue($metadata['exists']); } }
|
最佳实践
1. 使用适当的磁盘
1 2 3 4 5
| <?php
Storage::disk('local')->put('temp/file.txt', $content); Storage::disk('s3')->put('public/file.txt', $content); Storage::disk('public')->put('avatar.jpg', $image);
|
2. 清理临时文件
1 2 3
| <?php
Storage::deleteDirectory('temp/' . $sessionId);
|
3. 验证文件类型
1 2 3 4 5
| <?php
$validated = $request->validate([ 'file' => 'required|file|mimes:pdf,doc,docx|max:10240', ]);
|
总结
Laravel 13 的文件存储系统提供了强大的文件管理功能。通过合理使用多磁盘、流式处理、自定义驱动等高级特性,可以构建灵活、可扩展的文件管理系统。