laravel - 如何从 Laravel 8.x 上传文件到 Rackspace
问题描述
我正在为我的新项目使用 Laravel 8.x,并且需要支持 Zend 中的旧版 Live Web 应用程序,该应用程序使用 Rackspace CDN 来存储文件。因此,我需要从 Laravel 8.x 中的新应用程序上传 Rackspace CDN 中的文件,我可以成功地将文件上传到 Amazon S3,但无法将它们上传到 Rackspace。我尝试使用 League/flysystem-rackspace,但当前的 Laravel 版本不支持它。
控制器
public function store(Request $request)
{
$uploadImage = $request->file('file');
$filename = time().str_replace(' ', '_',
$uploadImage->getClientOriginalName());
$path = $request->file('file')->storePubliclyAs(
config('app.cdn_dir'),
$filename,
'rackspace'
);
}
配置/文件系统
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'rackspace' => [
'driver' => 'rackspace',
'username' => env('CDN_USERNAME'),
'key' => env('CDN_KEY'),
'container' => env('CDN_CONTAINER'),
'endpoint' => env('CDN_ENDPOINT', 'https://identity.api.rackspacecloud.com/v2.0/'),
'region' => 'IAD',
'url_type' => 'publicURL',
'url' => env('CDN_URL'),
],
],
错误
错误:第 10 行的文件 D:\laragon\www\crm\vendor\guzzle\guzzle\src\Guzzle\Common\Event.php 中找不到类“Symfony\Component\EventDispatcher\Event”
解决方案
升级到 Laravel 8 后我遇到了同样的问题。我设法根据这个公关评论让它工作,当他们从 Laravel 和这个草稿中删除它时,它开始使用更新的 php-opencloud sdk。
机架空间提供商:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use App\Services\Rackspace\RackspaceAdapter;
use OpenStack\OpenStack;
use OpenStack\Common\Transport\Utils as TransportUtils;
use OpenStack\Identity\v2\Service;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
class RackspaceServiceProvider extends ServiceProvider {
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Storage::extend('rackspace', function ($app, $config) {
$httpClient = new Client([
'base_uri' => TransportUtils::normalizeUrl($config['authUrl']),
'handler' => HandlerStack::create(),
]);
$options = [
'authUrl' => $config['authUrl'],
'region' => $config['region'],
'username' => $config['username'],
'password' => $config['password'],
'tenantId' => $config['tenantid'],
'identityService' => Service::factory($httpClient),
];
$openstack = new OpenStack($options);
$store = $openstack->objectStoreV1([
'catalogName' => 'cloudFiles',
]);
$account = $store->getAccount();
$container = $store->getContainer($config['container']);
return new Filesystem(
new RackspaceAdapter($container, $account), $config
);
});
}
}
和适配器:
<?php
declare(strict_types=1);
namespace App\Services\Rackspace;
use Carbon\Carbon;
use Exception;
use GuzzleHttp\Psr7\Utils;
use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\Config;
use League\Flysystem\Util;
use OpenStack\Common\Error\BadResponseError;
use OpenStack\ObjectStore\v1\Models\Account;
use OpenStack\ObjectStore\v1\Models\Container;
use OpenStack\ObjectStore\v1\Models\StorageObject;
use Throwable;
// Based off https://github.com/thephpleague/flysystem-rackspace/pull/26
final class RackspaceAdapter extends AbstractAdapter
{
use StreamedCopyTrait;
use NotSupportingVisibilityTrait;
private $container;
private $prefix;
private $account;
public function __construct(Container $container, Account $account, string $prefix = '')
{
$this->setPathPrefix($prefix);
$this->account = $account;
$this->container = $container;
}
public function getContainer(): Container
{
return $this->container;
}
protected function getObject(string $path): StorageObject
{
$location = $this->applyPathPrefix($path);
return $this->container->getObject($location);
}
protected function getPartialObject(string $path): StorageObject
{
$location = $this->applyPathPrefix($path);
return $this->container->getObject($location);
}
public function write($path, $contents, Config $config)
{
$location = $this->applyPathPrefix($path);
$headers = $config->get('headers', []);
$response = $this->container->createObject([
'name' => $location,
'content' => $contents,
'headers' => $headers,
]);
return $this->normalizeObject($response);
}
/**
* {@inheritdoc}
*/
public function update($path, $contents, Config $config)
{
$object = $this->getObject($path);
$object->setContent($contents);
$object->setEtag(null);
$response = $object->update();
if (!$response->lastModified) {
return false;
}
return $this->normalizeObject($response);
}
/**
* {@inheritdoc}
*/
public function rename($path, $newpath)
{
$object = $this->getObject($path);
$newlocation = $this->applyPathPrefix($newpath);
$destination = sprintf('/%s/%s', $this->container->name, ltrim($newlocation, '/'));
try {
$object->copy(['destination' => $destination]);
} catch (Throwable $exception) {
return false;
}
$object->delete();
return true;
}
/**
* {@inheritdoc}
*/
public function delete($path)
{
try {
$location = $this->applyPathPrefix($path);
$this->container->getObject($location)->delete();
} catch (Throwable $exception) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function deleteDir($dirname)
{
$location = $this->applyPathPrefix($dirname);
$objects = $this->container->listObjects(['prefix' => $location]);
try {
foreach ($objects as $object) {
/* @var $object StorageObject */
$object->delete();
}
} catch (Throwable $exception) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function createDir($dirname, Config $config)
{
$headers = $config->get('headers', []);
$headers['Content-Type'] = 'application/directory';
$extendedConfig = (new Config())->setFallback($config);
$extendedConfig->set('headers', $headers);
return $this->write($dirname, '', $extendedConfig);
}
/**
* {@inheritdoc}
*/
public function writeStream($path, $resource, Config $config)
{
$location = $this->applyPathPrefix($path);
$headers = $config->get('headers', []);
$response = $this->container->createObject([
'name' => $location,
'stream' => Utils::streamFor($resource),
'headers' => $headers,
]);
return $this->normalizeObject($response);
}
/**
* {@inheritdoc}
*/
public function updateStream($path, $resource, Config $config)
{
return $this->update($path, $resource, $config);
}
/**
* {@inheritdoc}
*/
public function has($path)
{
try {
$location = $this->applyPathPrefix($path);
$exists = $this->container->objectExists($location);
} catch (BadResponseError | Exception $exception) {
return false;
}
return $exists;
}
/**
* {@inheritdoc}
*/
public function read($path)
{
$object = $this->getObject($path);
$data = $this->normalizeObject($object);
$stream = $object->download();
$data['contents'] = $stream->read($object->contentLength);
$stream->close();
return $data;
}
/**
* {@inheritdoc}
*/
public function readStream($path)
{
$object = $this->getObject($path);
$responseBody = $object->download();
$responseBody->rewind();
return ['stream' => $responseBody->detach()];
}
/**
* {@inheritdoc}
*/
public function listContents($directory = '', $recursive = false)
{
$response = [];
$marker = null;
$location = $this->applyPathPrefix($directory);
while (true) {
$objectList = $this->container->listObjects(['prefix' => $location, 'marker' => $marker]);
if (null === $objectList->current()) {
break;
}
$response = array_merge($response, iterator_to_array($objectList));
$marker = end($response)->name;
}
return Util::emulateDirectories(array_map([$this, 'normalizeObject'], $response));
}
/**
* {@inheritdoc}
*/
protected function normalizeObject(StorageObject $object)
{
$name = $object->name;
$name = $this->removePathPrefix($name);
$mimetype = explode('; ', $object->contentType);
return [
'type' => ((in_array('application/directory', $mimetype)) ? 'dir' : 'file'),
'dirname' => Util::dirname($name),
'path' => $name,
'timestamp' => $object->lastModified,
'mimetype' => reset($mimetype),
'size' => $object->contentLength,
];
}
/**
* {@inheritdoc}
*/
public function getMetadata($path)
{
$object = $this->getPartialObject($path);
return $this->normalizeObject($object);
}
/**
* {@inheritdoc}
*/
public function getSize($path)
{
return $this->getMetadata($path);
}
/**
* {@inheritdoc}
*/
public function getMimetype($path)
{
return $this->getMetadata($path);
}
/**
* {@inheritdoc}
*/
public function getTimestamp($path)
{
return $this->getMetadata($path);
}
/**
* @param string $path
*/
public function applyPathPrefix($path): string
{
$encodedPath = join('/', array_map('rawurlencode', explode('/', $path)));
return parent::applyPathPrefix($encodedPath);
}
/**
* Get the URL for the file at the given path.
*
* @param string $path
* @return string
*/
protected function getUrl($path)
{
return (string) $this->container->getObject($path)->getPublicUri();
}
/**
* Get a temporary URL for the file at the given path.
*
* @param string $path
* @param \DateTimeInterface $expiration
* @param array $options
* @return string
*/
public function getTemporaryUrl($path, $expiration, array $options = [])
{
$object = $this->container->getObject($path);
$url = $object->getPublicUri();
$expires = Carbon::now()->diffInSeconds($expiration);
$method = strtoupper($options['method'] ?? 'GET');
$expiry = time() + (int) $expires;
// check for proper method
if ($method != 'GET' && $method != 'PUT') {
throw new Exception(sprintf(
'Bad method [%s] for TempUrl; only GET or PUT supported',
$method
));
}
if (!($secret = $this->account->getMetadata()['Temp-Url-Key'])) {
throw new Exception('Cannot produce temporary URL without an account secret.');
}
// if ($forcePublicUrl === true) {
// $url->setHost($this->getService()->getEndpoint()->getPublicUrl()->getHost());
// }
$urlPath = urldecode($url->getPath());
$body = sprintf("%s\n%d\n%s", $method, $expiry, $urlPath);
$hash = hash_hmac('sha1', $body, $secret);
return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry);
}
}
机架空间配置filesystems.php
'rackspace' => [
'driver' => 'rackspace',
'username' => env('RACKSPACE_USERNAME'),
'key' => env('RACKSPACE_KEY'),
'container' => env('RACKSPACE_CONTAINER'),
'password' => env('RACKSPACE_PASSWORD'),
'authUrl' => 'https://lon.identity.api.rackspacecloud.com/v2.0/',
'region' => env('RACKSPACE_REGION', 'LON'),
'tenantid' => env('RACKSPACE_TENANTID', '1'),
'url_type' => 'publicURL',
],
然后在app.php
.
这是一个粗略的实现,因为只测试了创建、删除和获取临时 url。
使用 Identity v2.0 进行身份验证,因为 Rackspace 不支持 v3 https://php-openstack-sdk.readthedocs.io/en/latest/services/identity/v2/authentication.html
并且 temp url 方法基于旧仓库中的函数https://github.com/rackspace/php-opencloud
推荐阅读
- apache-spark - 小型数据集的最佳(低延迟)火花设置
- jekyll - 在 GitHub 页面上找不到资源 Jekyll 博客
- vimeo - 以 Pro 帐户请求 Vimeo 私人视频文件
- python - TensorFlow 2 custom loss: "No gradients provided for any variable" error
- ruby-on-rails - NameError(未初始化的常量 Modules::CronJob::Job):
- clang - 将 lambda 编译为 Objective-C++ 会导致块转换
- python - Modifying multiple .csv files from same directory in python
- javascript - Puppeteer 无法进入 iframe
- typescript - ESLint 与 TypeScript,如何禁用严格的空检查?
- reactjs - React hooks 我应该在哪里执行函数?父母还是孩子?