masbug / flysystem-google-drive-ext
支持无缝虚拟路径转换的Google Drive Flysystem适配器
Requires
- php: ^7.2 | ^8.0
- ext-mbstring: *
- google/apiclient: ^2.2
- guzzlehttp/guzzle: ^6.3 | ^7.0
- guzzlehttp/psr7: ^1.7|^2.0
- league/flysystem: ^2.1.1|^3.0
Requires (Dev)
- league/flysystem-adapter-test-utilities: ^2.0|^3.0
- phpunit/phpunit: ^8.0 | ^9.3
README
Google为每个文件夹和文件使用唯一的ID。这使得与其他使用常规路径的存储服务集成变得困难。
此Flysystem适配器通过无缝地将路径从“显示路径”转换为“虚拟路径”,反之亦然,来解决这个问题。
例如:虚拟路径 /Xa3X9GlR6EmbnY1RLVTk5VUtOVkk/0B3X9GlR6EmbnY1RLVTk5VUtOVkk
变为 /My Nice Dir/myFile.ext
,并且所有ID处理都是隐藏的。
安装
- 对于 Flysystem V2/V3 或 Laravel >= 9.x.x
composer require masbug/flysystem-google-drive-ext
- 对于 Flysystem V1 或 Laravel <= 8.x.x 使用 1.x.x 版本的包
composer require masbug/flysystem-google-drive-ext:"^1.0.0"
获取Google密钥
请按照 Google文档 获取您的 客户端ID、客户端密钥 & 刷新令牌
。
此外,您还可以通过 @ivanvermeyen 跟踪这些易于遵循的教程
使用
$client = new \Google\Client(); $client->setClientId([client_id]); $client->setClientSecret([client_secret]); $client->refreshToken([refresh_token]); $client->setApplicationName('My Google Drive App'); $service = new \Google\Service\Drive($client); // variant 1 $adapter = new \Masbug\Flysystem\GoogleDriveAdapter($service, 'My_App_Root'); // variant 2: with extra options and query parameters $adapter2 = new \Masbug\Flysystem\GoogleDriveAdapter( $service, 'My_App_Root', [ 'useDisplayPaths' => true, /* this is the default */ /* These are global parameters sent to server along with per API parameters. Please see https://cloud.google.com/apis/docs/system-parameters for more info. */ 'parameters' => [ /* This example tells the remote server to perform quota checks per unique user id. Otherwise the quota would be per client IP. */ 'quotaUser' => (string)$some_unique_per_user_id ] ] ); // variant 3: connect to team drive $adapter3 = new \Masbug\Flysystem\GoogleDriveAdapter( $service, 'My_App_Root', [ 'teamDriveId' => '0GF9IioKDqJsRGk9PVA' ] ); // variant 4: connect to a folder shared with you $adapter4 = new \Masbug\Flysystem\GoogleDriveAdapter( $service, 'My_App_Root', [ 'sharedFolderId' => '0GF9IioKDqJsRGk9PVA' ] ); $fs = new \League\Flysystem\Filesystem($adapter, new \League\Flysystem\Config([\League\Flysystem\Config::OPTION_VISIBILITY => \League\Flysystem\Visibility::PRIVATE]));
// List selected root folder contents $contents = $fs->listContents('', true /* is_recursive */); // List specific folder contents $contents = $fs->listContents('MyFolder', true /* is_recursive */);
文件上传
// Upload a file $local_filepath = '/home/user/downloads/file_to_upload.ext'; $remote_filepath = 'MyFolder/file.ext'; $localAdapter = new \League\Flysystem\Local\LocalFilesystemAdapter('/'); $localfs = new \League\Flysystem\Filesystem($localAdapter, [\League\Flysystem\Config::OPTION_VISIBILITY => \League\Flysystem\Visibility::PRIVATE]); try { $time = Carbon::now(); $fs->writeStream($remote_filepath, $localfs->readStream($local_filepath), new \League\Flysystem\Config()); $speed = !(float)$time->diffInSeconds() ? 0 :filesize($local_filepath) / (float)$time->diffInSeconds(); echo 'Elapsed time: '.$time->diffForHumans(null, true).PHP_EOL; echo 'Speed: '. number_format($speed/1024,2) . ' KB/s'.PHP_EOL; } catch(\League\Flysystem\UnableToWriteFile $e) { echo 'UnableToWriteFile!'.PHP_EOL.$e->getMessage(); } // NOTE: Remote folders are automatically created.
文件下载
// Download a file $remote_filepath = 'MyFolder/file.ext'; $local_filepath = '/home/user/downloads/file.ext'; $localAdapter = new \League\Flysystem\Local\LocalFilesystemAdapter('/'); $localfs = new \League\Flysystem\Filesystem($localAdapter, [\League\Flysystem\Config::OPTION_VISIBILITY => \League\Flysystem\Visibility::PRIVATE]); try { $time = Carbon::now(); $localfs->writeStream($local_filepath, $fs->readStream($remote_filepath), new \League\Flysystem\Config()); $speed = !(float)$time->diffInSeconds() ? 0 :filesize($local_filepath) / (float)$time->diffInSeconds(); echo 'Elapsed time: '.$time->diffForHumans(null, true).PHP_EOL; echo 'Speed: '. number_format($speed/1024,2) . ' KB/s'.PHP_EOL; } catch(\League\Flysystem\UnableToWriteFile $e) { echo 'UnableToWriteFile!'.PHP_EOL.$e->getMessage(); }
如何获取TeamDrive列表和ID
$drives = $fs->getAdapter()->getService()->teamdrives->listTeamdrives()->getTeamDrives(); foreach ($drives as $drive) { echo 'TeamDrive: ' . $drive->name . PHP_EOL; echo 'ID: ' . $drive->id . PHP_EOL. PHP_EOL; }
如何永久删除所有用户回收站中的文件
$fs->getAdapter()->emptyTrash([]);
与Laravel框架一起使用
更新 .env
文件以包含google密钥
将您创建的密钥添加到您的 .env
文件中,并将 google
设置为您默认的云存储。您可以复制 .env.example
文件并填写空白。
FILESYSTEM_CLOUD=google
GOOGLE_DRIVE_CLIENT_ID=xxx.apps.googleusercontent.com
GOOGLE_DRIVE_CLIENT_SECRET=xxx
GOOGLE_DRIVE_REFRESH_TOKEN=xxx
GOOGLE_DRIVE_FOLDER=
#GOOGLE_DRIVE_TEAM_DRIVE_ID=xxx
#GOOGLE_DRIVE_SHARED_FOLDER_ID=xxx
# you can use more accounts, only add more configs
#SECOND_GOOGLE_DRIVE_CLIENT_ID=xxx.apps.googleusercontent.com
#SECOND_GOOGLE_DRIVE_CLIENT_SECRET=xxx
#SECOND_GOOGLE_DRIVE_REFRESH_TOKEN=xxx
#SECOND_GOOGLE_DRIVE_FOLDER=backups
#SECOND_DRIVE_TEAM_DRIVE_ID=xxx
#SECOND_DRIVE_SHARED_FOLDER_ID=xxx
在 config/filesystems.php
中添加磁盘
'disks' => [ // ... 'google' => [ 'driver' => 'google', 'clientId' => env('GOOGLE_DRIVE_CLIENT_ID'), 'clientSecret' => env('GOOGLE_DRIVE_CLIENT_SECRET'), 'refreshToken' => env('GOOGLE_DRIVE_REFRESH_TOKEN'), 'folder' => env('GOOGLE_DRIVE_FOLDER'), // without folder is root of drive or team drive //'teamDriveId' => env('GOOGLE_DRIVE_TEAM_DRIVE_ID'), //'sharedFolderId' => env('GOOGLE_DRIVE_SHARED_FOLDER_ID'), ], // you can use more accounts, only add more disks and configs on .env // also you can use the same account and point to a diferent folders for each disk /*'second_google' => [ 'driver' => 'google', 'clientId' => env('SECOND_GOOGLE_DRIVE_CLIENT_ID'), 'clientSecret' => env('SECOND_GOOGLE_DRIVE_CLIENT_SECRET'), 'refreshToken' => env('SECOND_GOOGLE_DRIVE_REFRESH_TOKEN'), 'folder' => env('SECOND_GOOGLE_DRIVE_FOLDER'), ],*/ // ... ],
在 app/Providers/
路径上的 ServiceProvider
中添加存储驱动
示例
namespace App\Providers; use Illuminate\Support\Facades\Storage; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { // can be a custom ServiceProvider // ... public function boot(){ // ... try { \Storage::extend('google', function($app, $config) { $options = []; if (!empty($config['teamDriveId'] ?? null)) { $options['teamDriveId'] = $config['teamDriveId']; } if (!empty($config['sharedFolderId'] ?? null)) { $options['sharedFolderId'] = $config['sharedFolderId']; } $client = new \Google\Client(); $client->setClientId($config['clientId']); $client->setClientSecret($config['clientSecret']); $client->refreshToken($config['refreshToken']); $service = new \Google\Service\Drive($client); $adapter = new \Masbug\Flysystem\GoogleDriveAdapter($service, $config['folder'] ?? '/', $options); $driver = new \League\Flysystem\Filesystem($adapter); return new \Illuminate\Filesystem\FilesystemAdapter($driver, $adapter); }); } catch(\Exception $e) { // your exception handling logic } // ... } // ... }
现在您可以像这样访问驱动器
$googleDisk = Storage::disk('google'); //$secondDisk = Storage::disk('second_google'); //others disks
请注意,默认云存储驱动器只能有一个,由您的 .env
(或配置)文件中的 FILESYSTEM_CLOUD
定义。如果您将其设置为 google
,则它将是云驱动器
Storage::cloud(); // refers to Storage::disk('google')
限制
将显示路径用作文件夹和文件的标识符要求它们是唯一的。不幸的是,Google Drive 允许用户创建具有相同(显示)名称的文件和文件夹。在这种情况下,当无法确定唯一路径时,此适配器将选择最旧的(第一个)实例。如果较新的重复项是文件夹,并且用户在内部放置了唯一的文件或文件夹,则适配器将能够正确访问它(因为完整路径是唯一的)。
同一Google Drive的并发使用可能会导致由于文件/文件夹标识符和文件对象的重度缓存而出现意外问题。
致谢
此适配器基于Naoki Sawada的出色的 flysystem-google-drive。
它还包括Google的 Google_Http_MediaFileUpload 的改编。我添加了对直接从流中支持可续传上传的支持(避免将数据复制到内存中)。
TeamDrive支持由Maximilian Ruta - Deltachaos 实现。
为Flysystem V2重写适配器和各种修复由Erik Niebla - erikn69 实现。