首页 > 解决方案 > 如何从 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”

标签: laravellaravel-8rackspace-cloudrackspacerackspace-cloudfiles

解决方案


升级到 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


推荐阅读