因為某些原因,到了k開頭的“準一線城市”一家外包公司工作,拿著每月不到7k的工資996奮鬥,不想今天還要挨領導的吐槽,沒心情做專案了,於是乎想搗鼓一個自己的部落格,然而GitHub上搜到的包上傳檔案到cos居然失敗了…把作者的原始碼翻出來看了一下,原來我神經兮兮的弄了一個子賬號上傳檔案,而原作者可能被騰訊雲的文件誤導,bucket使用了賬號拼接。
說實在的,自己的程式設計技術是有點菜,還好我還能copy。話有點多了,少說多做!
1.新建一個儲存系統介面卡類 CosAdapter.php
我直接放在了app/Cos目錄下。至於具體放哪個位置,我覺得隨個人喜好吧,本想歸類到Services目錄,但是覺得也不合適。
這個檔案主要是繼承AbstractAdapter並實現裡面的方法就好了
程式碼如下:
<?php
namespace App\Cos;
use Carbon\Carbon;
use DateTimeInterface;
use Exception;
use JetBrains\PhpStorm\ArrayShape;
use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\AdapterInterface;
use Qcloud\Cos\Client;
use League\Flysystem\Config;
class CosAdapter extends AbstractAdapter
{
protected Client $cosClient;
protected string $bucket;
/**
* 雲端儲存初始化
* @throws \Exception
*/
public function __construct($config)
{
if (!isset($config['cos_region'], $config['cos_secret_id'], $config['cos_secret_key'], $config['cos_bucket'])) {
throw new Exception('缺少騰訊云云儲存配置');
}
$config['cos_schema'] = $config['cos_schema'] ?? 'http';
$this->cosClient = new Client([
'region' => $config['cos_region'],
'schema' => $config['cos_schema'],
'credentials' => [
'secretId' => $config['cos_secret_id'],
'secretKey' => $config['cos_secret_key'],
]
]);
$this->bucket = $config['cos_bucket'];
}
/**
* 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): bool|array
{
try {
$stream = fopen($contents, 'rb');
$this->cosClient->Upload($this->bucket, $path, $stream);
} catch (Exception) {
return false;
}
$size = filesize($contents);
$type = 'file';
$result = compact('contents', 'type', 'size', 'path');
if ($visibility = $config->get('visibility')) {
$result['visibility'] = $visibility;
$this->setVisibility($path, $visibility);
}
return $result;
}
/**
* 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): bool|array
{
try {
$this->cosClient->Upload($this->bucket, $path, $resource);
} catch (Exception) {
return false;
}
$type = 'file';
$result = compact('type', 'path');
if ($visibility = $config->get('visibility')) {
$this->setVisibility($path, $visibility);
$result['visibility'] = $visibility;
}
return $result;
}
/**
* 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): bool|array
{
return $this->write($path, $contents, $config);
}
/**
* 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): bool|array
{
return $this->writeStream($path, $resource, $config);
}
/**
* Rename a file.
*
* @param string $path
* @param string $newPath
*
* @return bool
*/
public function rename($path, $newPath): bool
{
try {
if ($result = $this->copy($path, $newPath)) {
$this->delete($path);
}
return $result;
} catch (Exception) {
return false;
}
}
/**
* Copy a file.
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function copy($path, $newpath): bool
{
try {
$this->cosClient->copy($this->bucket, $newpath, $path);
} catch (Exception) {
return false;
}
return true;
}
/**
* Delete a file.
*
* @param string $path
*
* @return bool
*/
public function delete($path): bool
{
try {
$this->cosClient->deleteObject([
'Bucket' => $this->bucket,
'Key' => $path,
]);
} catch (Exception) {
return false;
}
return true;
}
/**
* Delete a directory.
*
* @param string $dirname
*
* @return bool
*/
public function deleteDir($dirname): bool
{
$nextMarker = '';
$isTruncated = true;
while ($isTruncated) {
try {
$result = $this->cosClient->listObjects([
'Bucket' => $this->bucket,
'Delimiter' => '',
'EncodingType' => 'url',
'Marker' => $nextMarker,
'Prefix' => $dirname,
'MaxKeys' => 1000
]);
$isTruncated = $result['IsTruncated'];
$nextMarker = $result['NextMarker'];
foreach ($result['Contents'] as $content) {
$cosFilePath = $content['Key'];
try {
$this->cosClient->deleteObject([
'Bucket' => $this->bucket,
'Key' => $cosFilePath,
]);
} catch (Exception) {
return false;
}
}
} catch (Exception) {
return false;
}
}
return true;
}
/**
* Create a directory.
*
* @param string $dirname directory name
* @param Config $config
*
* @return array|false
*/
public function createDir($dirname, Config $config): array|bool
{
try {
$this->cosClient->putObject(array(
'Bucket' => $this->bucket,
'Key' => $dirname,
'Body' => '',
));
} catch (Exception) {
return false;
}
return ['path' => $dirname, 'type' => 'dir'];
}
/**
* Set the visibility for a file.
*
* @param string $path
* @param string $visibility
*
* @return array|false file meta data
*/
public function setVisibility($path, $visibility): bool|array
{
$cosAcl = strtolower($visibility) === 'public' ? 'public-read' : 'private';
try {
$this->cosClient->putObjectAcl([
'Bucket' => $this->cosClient,
'Key' => $path,
'ACL' => $cosAcl,
]);
} catch (Exception) {
return false;
}
return compact('path', 'visibility');
}
/**
* Check whether a file exists.
*
* @param string $path
*
* @return array|bool|null
*/
public function has($path): bool|array|null
{
return $this->getMetadata($path);
}
/**
* Read a file.
*
* @param string $path
*
* @return array|false
*/
public function read($path): bool|array
{
try {
$result = $this->cosClient->getObject(array(
'Bucket' => $this->bucket,
'Key' => $path,
));
return ['type' => 'file', 'path' => $path, 'contents' => $result['Body']];
} catch (Exception) {
return false;
}
}
/**
* Read a file as a stream.
*
* @param string $path
*
* @return array|false
*/
#[ArrayShape(['type' => "string", 'path' => "string", 'stream' => "null|resource"])]
public function readStream($path): bool|array
{
$tmpUrl = $this->SignUrl($path);
if (!$tmpUrl) {
return false;
}
$curlClient = new \GuzzleHttp\Client();
$stream = $curlClient->get($tmpUrl, [
'stream' => true,
])->getBody()
->detach();
return ['type' => 'file', 'path' => $path, 'stream' => $stream];
}
/**
* 獲取下載簽名
* @param string $path
* @param \DateTimeInterface|null $expiration
* @return bool|string
*/
public function SignUrl(string $path, ?DateTimeInterface $expiration = null): bool|string
{
$expiration = $expiration ?? now()->addMinutes(30);
try {
$signedUrl = $this->cosClient->getObjectUrl($this->bucket, $path, $expiration);
} catch (Exception) {
return false;
}
return $signedUrl;
}
/**
* List contents of a directory.
*
* @param string $directory
* @param bool $recursive
*
* @return array
*/
public function listContents($directory = '', $recursive = false): array
{
$list = [];
$marker = '';
while (true) {
$response = $this->listObjects($directory, $recursive, $marker);
foreach ((array)$response['Contents'] as $content) {
$list[] = $this->normalizeFileInfo($content);
}
if (!$response['IsTruncated']) {
break;
}
$marker = $response['NextMarker'] ?: '';
}
return $list;
}
/**
* @param string $directory
* @param bool $recursive
* @param string $marker
* @return array|object
*/
protected function listObjects(string $directory = '', bool $recursive = false, string $marker = ''): object|array
{
try {
return $this->cosClient->listObjects([
'Bucket' => $this->bucket,
'Prefix' => $directory === '' ? '' : $directory . '/',
'Delimiter' => $recursive ? '' : '/',
'Marker' => $marker,
'MaxKeys' => 1000,
]);
} catch (Exception) {
return [
'Contents' => [],
'IsTruncated' => false,
'NextMarker' => '',
];
}
}
/**
* @param array $content
*
* @return array
*/
#[ArrayShape([
'type' => "string",
'path' => "mixed",
'timestamp' => "int",
'size' => "int",
'dirname' => "string",
'basename' => "string",
'extension' => "mixed|string",
'filename' => "string"
])]
protected function normalizeFileInfo(array $content): array
{
$path = pathinfo($content['Key']);
return [
'type' => substr($content['Key'], -1) === '/' ? 'dir' : 'file',
'path' => $content['Key'],
'timestamp' => Carbon::parse($content['LastModified'])->getTimestamp(),
'size' => (int)$content['Size'],
'dirname' => $path['dirname'] === '.' ? '' : (string)$path['dirname'],
'basename' => (string)$path['basename'],
'extension' => $path['extension'] ?? '',
'filename' => (string)$path['filename'],
];
}
/**
* Get all the meta data of a file or directory.
*
* @param string $path
*
* @return array|false
*/
public function getMetadata($path): bool|array
{
try {
$result = $this->cosClient->headObject([
'Bucket' => $this->bucket,
'Key' => $path,
]);
return [
'type' => substr($result['Key'], -1) === '/' ? 'dir' : 'file',
'path' => $path,
'timestamp' => Carbon::parse($result['LastModified'])->getTimestamp(),
'size' => (int)$result['ContentLength'],
'mimetype' => $result['ContentType']
];
} catch (Exception) {
return false;
}
}
/**
* Get the size of a file.
*
* @param string $path
*
* @return array|false
*/
public function getSize($path): bool|array
{
return $this->getMetadata($path);
}
/**
* Get the mimetype of a file.
*
* @param string $path
*
* @return array|false
*/
public function getMimetype($path): bool|array
{
return $this->getMetadata($path);
}
/**
* Get the last modified time of a file as a timestamp.
*
* @param string $path
*
* @return array|false
*/
public function getTimestamp($path): bool|array
{
return $this->getMetadata($path);
}
/**
* Get the visibility of a file.
*
* @param string $path
*
* @return array|false
*/
public function getVisibility($path): bool|array
{
try {
$meta = $this->cosClient->getObjectAcl([
'Bucket' => $this->bucket,
'Key' => $path,
]);
foreach ($meta['Grants'] as $grant) {
if (isset($grant['Grantee']['URI'])
&& $grant['Permission'] === 'READ'
&& str_contains($grant['Grantee']['URI'], 'global/AllUsers')
) {
return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
}
}
return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
} catch (Exception) {
return false;
}
}
}
這些方法作用把基類的英文註釋翻譯一下就好了。更主要的是,不會也沒有太大的關係。可以參考vendor\league\flysystem\src\Adapter\Local.php來寫。(^o^)/還可以直接去copyGitHub上別人的。
2.新建一個服務提供者
執行一下命令建立Provider
php artisan make:provider CosServiceProvider
3.開啟app\Providers\CosServiceProvider.php寫入以下方法
<?php
namespace App\Providers;
use App\Cos\CosAdapter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
class CosServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Storage::extend('cos', function ($app, $config) {
return new Filesystem(new CosAdapter($config));
});
}
}
4.在config/app.php 配置檔案中註冊服務提供者
'providers' => [
// ...
App\Providers\CosServiceProvider::class,
];
5.在config\filesystems.php配置檔案disks項中追加cos配置
'cos' => [
'driver' => 'cos',
'cos_region' => env('COS_REGION', 'ap-guangzhou'),
'cos_secret_id' => env('COS_SECRET_ID'),
'cos_secret_key' => env('COS_SECRET_KEY'),
'cos_bucket' => env('COS_BUCKET'),
'cos_schema' => env('COS_SCHEMA', 'http')
]
6.在環境變數.env檔案中填寫您的配置
COS_REGION= COS_SECRET_ID= COS_SECRET_KEY= COS_BUCKET= COS_SCHEMA=
7.引入官方sdk,一般情況下在寫之前就會先引入了
composer require qcloud/cos-sdk-v5
至此,自定義檔案系統完畢。由於本人是PHP8版本,一些寫法不相容PHP7。
寫個方法測試一下收工!
public function test(Request $request)
{
$file = $request->file('file');
$path = $file->store('test', 'cos');
dd($path);
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結