1. 程式人生 > >laravel 新增 七牛雲 儲存驅動

laravel 新增 七牛雲 儲存驅動

接著上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,發現了好多七牛相關的包,被打臉了!!!這篇筆記有點長,接下篇,終結下七牛雲端儲存的問題!!!