laravel 新增 七牛雲 儲存驅動
阿新 • • 發佈:2018-11-06
接著上2篇筆記: 使用 qshell 將本地檔案同步到七牛雲 https://blog.csdn.net/beyond__devil/article/details/83030702 將 laravel 專案內靜態檔案,css、js、images 部署到七牛雲 csdn https://blog.csdn.net/beyond__devil/article/details/83057313 本篇筆記,我們根據最後遺留的問題,擴充套件 laravel 檔案系統驅動,新增 七牛雲 儲存引擎。 參考文章: laravel 檔案儲存文件 https://laravelacademy.org/post/9567.html 基於七牛雲 PHP SDK + Laravel 檔案儲存實現 Laravel 學院靜態資源雲端儲存及 CDN 加速 https://laravelacademy.org/post/9486.html 昨晚因為一個小問題,讓我除錯程式碼,除錯了N久,看了好久的 filesystem 原始碼,稍微總結下: 檔案系統的 Facade,我們使用的是: Storage Storage Facade 的定義,我們在門面類列表檢視: https://laravelacademy.org/post/9536.html filesystem - Illuminate\Filesystem\FilesystemManager filesystem.disk - Illuminate\Contracts\Filesystem\Filesystem 開啟 Illuminate\Filesystem 目錄,簡單看下: Cache.php - 快取有關 Filesystem.php - 檔案、目錄處理類 FilesystemAdapter.php - 設配器(laravel) FilesystemManager.php - 儲存驅動管理 FilesystemServiceProvider.php - 服務提供者 laravel 檔案系統,底層使用的 league/flysystem 包,這裡我們有時間也可以簡單看下原始碼,大概知道是個什麼意思就行。 注意: 我們只需要知道我們平常使用: Storage::xxx() 呼叫檔案儲存方法 其實呼叫的就是: Illuminate\Filesystem\FilesystemManager 就可以。然後我們一點點看原始碼,基本就都能明白了(Laraval 有時候很複雜,半天找不到對應的到底是哪個檔案) Storage::extend() 就是 Illuminate\Filesystem\FilesystemManager->extend(),用於擴充套件我們自定義的 '檔案儲存' Storage::xxx() 方法有: FilesystemManager.php: drive() - 同 disk() disk() - 獲取檔案系統例項,如果沒有,使用預設驅動(config/filesystem.php 有2個預設配置,一個預設配置,一個預設雲端儲存配置) cloud() - 獲取預設雲端儲存(config/filesystem.php 有2個預設配置,一個預設配置,一個預設雲端儲存配置) createLocalDriver() - 建立一個 local 驅動 createFtpDriver() - 建立一個 ftp 驅動 createSftpDriver() - 建立一個 sftp 驅動 createS3Driver() - 建立一個 Amazon S3 驅動 createRackspaceDriver() - 建立一個 Rackspace 驅動 set() - 獲取給定的驅動例項 getDefaultDriver() - 獲取預設驅動名 getDefaultCloudDriver() - 獲取預設雲端儲存驅動名 forgetDisk() - unset 給定的驅動 extend() - 註冊一個自定義驅動 __call($method, $parameters) // Storage::xxx() 其他方法,都是通過魔術方法 __call() 來呼叫 '預設驅動' 的方法,$this->dist() 返回的是 'FilesystemAdapter.php' 類的例項 { return $this->disk()->$method(...$parameters); } FilesystemAdapter.php: 裡面經常會呼叫 $this->driver() 方法,而 $this->driver() 都是 'league/flysystem/Filesystem.php' 的例項。它裡面對應的所有方法,都是對引數進行處理,最終呼叫的就是我們具體的 '驅動介面卡'(local、public、s3、qiniu) 的方法了,所以我們就暫且忽略 'league/flysystem/Filesystem.php' 這個中間處理層了 assertExists() - PHPUnit 測試使用 assertMissing() - PHPUnit 測試使用 exists($path) - 檔案是否存在,呼叫 adaptar 的 has() path($path) - 獲取給定檔案的全路徑,呼叫 adaptar 的 getPathPrefix() . $path get($path) - 獲取檔案內容,呼叫 adaptar 的 read() response($path, $name = null, array $headers = [], $disposition = 'inline') - 建立給定檔案的流響應(stream),中間會呼叫 '自身的' readStream() 方法 download() - 藉助 response() 方法,下載 put($path, $contents, $options = []) - 將制定內容寫入到檔案中,根據不同情況,呼叫不同的寫入方法。有 '自身的 putFile()','adaptar 的 putStream()','adaptar 的 put()' putFile($path, $file, $options = []) - 將上傳檔案,寫入到儲存,呼叫 '自身的 putFileAs()' putFileAs($path, $file, $name, $options = []) - 將上傳檔案,寫入到儲存,呼叫上面寫的 '自身的 put()' getVisibility($path) - 獲取檔案的可見性,呼叫 'adaptar 的 getVisibility()' setVisibility($path) - 設定檔案的可見性,呼叫 'adaptar 的 setVisibility()' prepend($path, $data, $separator = PHP_EOL) - 向前追加指定內容到檔案,呼叫 '自身的 put()'(檔案存在,追加,不存在,新建) append($path, $data, $separator = PHP_EOL) - 向後追加指定內容到檔案,呼叫 '自身的 put()'(檔案存在,追加,不存在,新建) delete($paths) - 刪除指定檔案(支援多個,陣列,或者多個引數,會使用 file_get_args() 來獲取),呼叫 'adaptar 的 delete()' copy($from, $to) - 複製檔案,呼叫 'adaptar 的 coty()' move($from, $to) - 移動檔案,呼叫 'adaptar 的 rename()' size($path) - 獲取檔案大小,呼叫 'adaptar 的 getSize()' mimeType($path) - 獲取檔案的 MIME 型別,呼叫 'adaptar 的 getMimeType()' lastModified($path) - 獲取檔案的最後修改時間,呼叫 'adaptar 的 getTimestamp()' url($path) - 獲取檔案的訪問 URL,『我們自己需要定義一個 getUrl() 方法,這個方法很關鍵,我們可以在我們的 qiniuAdaptar 裡新增 getUrl() 方法,然後靜態檔案的訪問,我們可以直接使用 Storage::geturl() 來獲取』 readStream($path) writeStream($path, $resource, array $options = []) temporaryUrl($path, $expiration, array $options = []) getAwsTemporaryUrl($adapter, $path, $expiration, $options) getRackspaceTemporaryUrl files($directory = null, $recursive = false) - 獲取目錄下的所有檔案,型別只是 'file',呼叫 'adaptar 的 listContents()' allFiles($directory = null) - 獲取目錄下的所有檔案(遞迴),呼叫上面的 $this->files() Directories($directory = null, $recursive = false) - 獲取目錄下的所有目錄,型別只是 'dir',呼叫 'adaptar 的 listContents()' allDirectories($directory = null) - 獲取目錄下的所有目錄(遞迴),呼叫上面的 $this->Directories() makeDirectory($path) - 建立目錄,呼叫 'adaptar 的 createDir()' deleteDirectory($path) - 刪除目錄,呼叫 'adaptar 的 deleteDir()' flushCache() - 只有當該 adaptar 繼承了 'CacheAdapter',才可用 getDriver() - 獲取當前的 Filesystem 例項 __call($method, array $parameters) // Storage::xxx() 其他方法,還可以通過魔術方法 __call() 來呼叫 'Filesystem' 的方法(方法很少了...) { return call_user_func_array([$this->driver, $method], $parameters); } Filesystem.php 額外的方法(好多類似別名訪問): PluggableTrait 方法,外掛相關(未用到,我們不考慮) ConfigAwareTrait 方法,配置相關 getAdapter() - 獲取 adaptar,local、public、s3、qiniu 等 write() - 呼叫 'adaptar 的 write()' putStream() readAndDelete() - 讀取檔案內容,並刪除檔案,檔案不存在,返回 false,檔案儲存,返回檔案內容 update() - 呼叫 'adaptar 的 update()' updateStream() - 呼叫 'adaptar 的 updateStream() ' read() readStream() rename() deleteDir() createDir() listContents() getMimetype() getTimestamp() getSize() getMetadata() assertPresent() - 檔案不存在報錯(disable_asserts 配置) assertAbsent() - 檔案存在報錯(disable_asserts 配置) 自己語言表達能力有點差,可能自己也不是很明白,要講清楚很難,原始碼總結就說到這裡。 開始擴充套件七牛雲端儲存,主要參考上面的 '基於七牛雲 PHP SDK + Laravel 檔案儲存實現 Laravel 學院靜態資源雲端儲存及 CDN 加速' 這篇文章,七牛雲的原始碼封裝,我會走一遍,稍微新增或修改,最後整理處理。 安裝 '七牛雲 PHP SDK': composer require qiniu/php-sdk .env 新增配置: FILESYSTEM_CLOUD=qiniu QINIU_ACCESS_KEY=你的七牛雲AccessKey QINIU_SECRET_KEY=你的七牛雲SecretKey QINIU_DEFAULT_REGION=你的預設區域,比如z0 QINIU_BUCKET=你的bucket(通過在物件儲存中新增儲存空間獲取) QINIU_PREFIX=七牛雲空間內,檔案儲存的相對路徑(路徑字首) QINIU_URL=你的靜態 CDN URL config/filesystem.php,新增 'qiniu' 驅動: 'disks' => [ ... 'qiniu' => [ 'driver' => 'qiniu', 'key' => env('QINIU_ACCESS_KEY', 'xxx'), 'secret' => env('QINIU_SECRET_KEY', 'xxx'), 'region' => env('QINIU_DEFAULT_REGION', ''), 'bucket' => env('QINIU_BUCKET', 'xxx'), 'prefix' => env('QINIU_PREFIX', 'storage'), 'url' => env('QINIU_URL', 'xxx'), ], ], 編寫 '七牛雲端儲存介面卡': App/Services/FileSystem/QiniuAdapter.php 注: laravel 從 5.1 之後好像取消了 App/Services 目錄,我們自己來建立這個目錄,再多新增一層 FileSystem,可能以後會新增其他的檔案儲存。(也可以指定其他目錄) 註冊 '七牛雲端儲存介面卡': vim app/Providers/AppServiceProvider.php boot() 方法新增: use Storage; use League\Flysystem\Filesystem; use App\Services\FileSystem\QiniuAdapter; public function boot() { Storage::extend('qiniu', function ($app, $config) { return new Filesystem(new QiniuAdapter($config)); }) } App/Services/FileSystem/QiniuAdapter.php 介面卡程式碼: QiniuAdapter 必須實現 League\Flysystem\AdapterInterface 介面 我們可檢視官方的 Local 檔案儲存,仿照寫: league/flysystem/src/Adapter/Local.php 下面是 'laravel 學院' 的程式碼小修改: /********************************************* <?php namespace App\Services\FileSystem; use Illuminate\Contracts\Filesystem\FileNotFoundException; use League\Flysystem\Adapter\AbstractAdapter; use League\Flysystem\Config; use Qiniu\Auth; use Qiniu\Storage\BucketManager; use Qiniu\Storage\UploadManager; use Symfony\Component\HttpFoundation\File\Exception\UploadException; class QiniuAdapter extends AbstractAdapter { protected $uploadManager; protected $bucketManager; private $accessKey; private $accessSecret; private $bucketName; private $token; private $url; public function __construct($config) { $this->uploadManager = new UploadManager(); $this->accessKey = $config['key']; $this->accessSecret = $config['secret']; $this->bucketName = $config['bucket']; $auth = new Auth($this->accessKey, $this->accessSecret); $this->bucketManager = new BucketManager($auth); $this->token = $auth->uploadToken($this->bucketName); $this->setPathPrefix($config['prefix']); $this->url = $config['url']; } /** * Write a new file. * * @param string $path * @param string $contents * @param Config $config Config object * * @return array|false false on failure file meta data on success */ public function write($path, $contents, Config $config) { return $this->upload($path, $contents); } /** * Write a new file using a stream. * * @param string $path * @param resource $resource * @param Config $config Config object * * @return array|false false on failure file meta data on success */ public function writeStream($path, $resource, Config $config) { return $this->upload($path, $resource, true); } /** * Update a file. * * @param string $path * @param string $contents * @param Config $config Config object * * @return array|false false on failure file meta data on success */ public function update($path, $contents, Config $config) { return $this->upload($path, $contents); } /** * Update a file using a stream. * * @param string $path * @param resource $resource * @param Config $config Config object * * @return array|false false on failure file meta data on success */ public function updateStream($path, $resource, Config $config) { return $this->upload($path, $resource, true); } /** * Rename a file. * * @param string $path * @param string $newpath * * @return bool */ public function rename($path, $newpath) { $path = $this->applyPathPrefix($path); $newpath = $this->applyPathPrefix($newpath); $error = $this->bucketManager->rename($this->bucketName, $path, $newpath); return $error == null ? true : false; } /** * Copy a file. * * @param string $path * @param string $newpath * * @return bool */ public function copy($path, $newpath) { $path = $this->applyPathPrefix($path); $newpath = $this->applyPathPrefix($newpath); $error = $this->bucketManager->copy($this->bucketName, $path, $this->bucketName, $newpath); return $error == null ? true : false; } /** * Delete a file. * * @param string $path * * @return bool */ public function delete($path) { $this->applyPathPrefix($path); $error = $this->bucketManager->delete($this->bucketName, $path); return $error == null ? true : false; } /** * Delete a directory. * * @param string $dirname * * @return bool */ public function deleteDir($dirname) { throw new \BadFunctionCallException('暫不支援該操作'); } /** * Create a directory. * * @param string $dirname directory name * @param Config $config * * @return array|false */ public function createDir($dirname, Config $config) { throw new \BadFunctionCallException('暫不支援該操作'); } /** * Set the visibility for a file. * * @param string $path * @param string $visibility * * @return array|false file meta data */ public function setVisibility($path, $visibility) { throw new \BadFunctionCallException('暫不支援該操作'); } /** * Check whether a file exists. * * @param string $path * * @return array|bool|null */ public function has($path) { $path = $this->applyPathPrefix($path); $stat = $this->bucketManager->stat($this->bucketName, $path); if ($stat[0] == null) { return false; } else { return true; } } /** * Read a file. * * @param string $path * * @return array|false */ public function read($path) { $path = $this->applyPathPrefix($path); list($fileInfo, $error) = $this->bucketManager->stat($this->bucketName, $path); if ($fileInfo) { return $fileInfo; } else { throw new FileNotFoundException('對應檔案不存在'); } } /** * Read a file as a stream. * * @param string $path * * @return array|false */ public function readStream($path) { throw new \BadFunctionCallException('暫不支援該操作'); } /** * List contents of a directory. * * @param string $directory * @param bool $recursive * * @return array */ public function listContents($directory = '', $recursive = false) { return $this->bucketManager->listFiles($this->bucketName); } /** * Get all the meta data of a file or directory. * * @param string $path * * @return array|false */ public function getMetadata($path) { return $this->read($path); } /** * Get the size of a file. * * @param string $path * * @return array|false */ public function getSize($path) { $fileInfo = $this->read($path); return $fileInfo['fsize']; } /** * Get the mimetype of a file. * * @param string $path * * @return array|false */ public function getMimetype($path) { $fileInfo = $this->read($path); return $fileInfo['fileType']; } /** * Get the last modified time of a file as a timestamp. * * @param string $path * * @return array|false */ public function getTimestamp($path) { $fileInfo = $this->read($path); return $fileInfo['putTime']; } /** * Get the visibility of a file. * * @param string $path * * @return array|false */ public function getVisibility($path) { throw new \BadFunctionCallException('暫不支援該操作'); } protected function upload(string $path, $contents, $stream = false) { $path = $this->applyPathPrefix($path); try { if ($stream) { $response = $this->uploadManager->put($this->token, $path, $contents); } else { $response = $this->uploadManager->putFile($this->token, $path, $contents); } } catch (\Exception $ex) { throw $ex; } catch (\Exception $ex) { throw $ex; } list($uploadResult, $error) = $response; if ($uploadResult) { return $uploadResult; } else { throw new UploadException('上傳檔案到七牛失敗:' . $error->message()); } } public function getUrl($path) { $path = $this->applyPathPrefix($path); return $this->url . DIRECTORY_SEPARATOR . $path; } } *********************************************/ 這個 QiniuAdapter 有不少問題,因為好多方法其實未實現~~,打算好好看看 七牛雲 SDK & 七牛雲開發者文件,看看能不能把這個 QiniuAdapter 給寫的更好一點。看了會,發現有點不太好搞啊~~ 查詢資料過程中,在 packagist.org 輸入 qiniu,本想找下 qiniu/php-sdk,發現了好多七牛相關的包,被打臉了!!!這篇筆記有點長,接下篇,終結下七牛雲端儲存的問題!!!