Php 如何使用Laravel队列拦截S3上的新文件?

Php 如何使用Laravel队列拦截S3上的新文件?,php,laravel,amazon-web-services,amazon-s3,amazon-sqs,Php,Laravel,Amazon Web Services,Amazon S3,Amazon Sqs,我有一个S3 bucket,mybucket,当一个新文件被复制到该bucket中时,我想执行一些操作。对于通知,我想使用SQS队列,notifiqueue,因为我的目标是使用Laravel 由于我正在CloudFormation中创建我的基础架构,因此资源的创建方式如下: NotificationQueue: Type: AWS::SQS::Queue Properties: VisibilityTimeout: 120 QueueName: 'Notification

我有一个S3 bucket,
mybucket
,当一个新文件被复制到该bucket中时,我想执行一些操作。对于通知,我想使用SQS队列,
notifiqueue
,因为我的目标是使用
Laravel

由于我正在
CloudFormation
中创建我的基础架构,因此资源的创建方式如下:

NotificationQueue:
  Type: AWS::SQS::Queue
  Properties:
    VisibilityTimeout: 120
    QueueName: 'NotificationQueue'

DataGateBucket:
  Type: AWS::S3::Bucket
  Properties:
    AccessControl: BucketOwnerFullControl
    BucketName: 'mybucket'
    NotificationConfiguration:
      QueueConfigurations:
        - Event: 's3:ObjectCreated:*'
          Queue: !GetAtt NotificationQueue.Arn
<?php

namespace App\Jobs\SqsNotifications;

use Illuminate\Queue\Jobs\JobName;

/**
 * Class SqsJob
 * @package App\Jobs\SqsNotifications
 *
 * Alternate SQS job that is used in case of S3 notifications
 */
class SqsJob extends \Illuminate\Queue\Jobs\SqsJob
{

    /**
     * Get the name of the queued job class.
     *
     * @return string
     */
    public function getName()
    {

        $bucketName = '';

        // Define the name of the Process based on the bucket name
        switch($this->payload()['Records'][0]['s3']['bucket']['name']){
            case 'mybucket':
                $bucketName = 'NewMyBucketFileJob';
                break;
        }

        return $bucketName;
    }

   /**
    * Fire the job.
    *
    * @return void
    */
    public function fire()
    {
        // Mimic the original behavior with a different payload
        $payload = $this->payload();
        [$class, $method] = JobName::parse('\App\Jobs\\' . $this->getName() . '@handle');
        ($this->instance = $this->resolve($class))->{$method}($payload);

        // The Job wasn't automatically deleted, so we need to delete it manually once the process went fine
        $this->delete();
    }
}
每次在bucket上保存一个新文件时,S3都会自动在SQS中创建一个通知

遗憾的是,有效负载的格式与Laravel标准作业有效负载不兼容,如果我在
NotificationQueue
上运行工作进程,则会出现以下错误:

local.ERROR: Undefined index: job {"exception":"[object] (ErrorException(code: 0): Undefined index: job at .../vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php:273)
为了提供更完整的指示,以下是我在通知中得到的信息(将JSON转换为PHP数组后)


使用Laravel访问通知的有效/最佳/正确方法是什么,以便我可以触发其他选项以响应文件上载?

我找到了一种获得所需行为的方法,但我不确定这是最佳方法,因此我将其发布在这里,也许可以给我反馈

当我们谈论Laravel队列时,很多配置来自
app.php
,特别是
提供者
部分。我设法添加了覆盖原始
QueueServiceProvider
类并替换它所需的行为:

// Here is the original Provider Class
//Illuminate\Queue\QueueServiceProvider::class,
// Here is the overridden Provider
\App\Providers\QueueServiceProvider::class, 
新的
QueueServiceProvider
类如下所示:

<?php

namespace App\Providers;

use App\Jobs\SqsNotifications\SqsConnector;

class QueueServiceProvider extends \Illuminate\Queue\QueueServiceProvider
{

    /**
     * Register the Amazon SQS queue connector.
     *
     * @param  \Illuminate\Queue\QueueManager  $manager
     * @return void
     */
    protected function registerSqsNotifConnector($manager)
    {
        $manager->addConnector('sqsNotif', function () {
            return new SqsConnector();
        });
    }


    public function registerConnectors($manager){
        parent::registerConnectors($manager);

        // Add the custom SQS notification connector
        $this->registerSqsNotifConnector($manager);
    }
}
在新的
QueueServiceProvider
中,我们只需注册一个额外的连接器,其代码为:

<?php

namespace App\Jobs\SqsNotifications;

use Aws\Sqs\SqsClient;
use Illuminate\Support\Arr;

class SqsConnector extends \Illuminate\Queue\Connectors\SqsConnector
{

    /**
     * Establish a queue connection.
     *
     * @param  array  $config
     * @return \Illuminate\Contracts\Queue\Queue
     */
    public function connect(array $config)
    {
         $config = $this->getDefaultConfiguration($config);

        if ($config['key'] && $config['secret']) {
            $config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
        }

        return new SqsQueue(
            new SqsClient($config), $config['queue'], $config['prefix'] ?? ''
        );
    }
}
这个过程是有效的,所以这是一个解决方案,但涉及到大量的类扩展,如果内部队列实现在将来的版本中发生更改,那么它是非常脆弱的。老实说,我想知道是否有更简单或更强大的东西

<?php

namespace App\Jobs\SqsNotifications;

use Aws\Sqs\SqsClient;
use Illuminate\Support\Arr;

class SqsConnector extends \Illuminate\Queue\Connectors\SqsConnector
{

    /**
     * Establish a queue connection.
     *
     * @param  array  $config
     * @return \Illuminate\Contracts\Queue\Queue
     */
    public function connect(array $config)
    {
         $config = $this->getDefaultConfiguration($config);

        if ($config['key'] && $config['secret']) {
            $config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
        }

        return new SqsQueue(
            new SqsClient($config), $config['queue'], $config['prefix'] ?? ''
        );
    }
}
<?php

namespace App\Jobs\SqsNotifications;

class SqsQueue extends \Illuminate\Queue\SqsQueue
{
   /**
    * Pop the next job off of the queue.
    *
    * @param  string  $queue
    * @return \Illuminate\Contracts\Queue\Job|null
    */
    public function pop($queue = null)
    {
        $response = $this->sqs->receiveMessage([
            'QueueUrl' => $queue = $this->getQueue($queue),
            'AttributeNames' => ['ApproximateReceiveCount'],
        ]);

        if (! is_null($response['Messages']) && count($response['Messages']) > 0) {
            return new SqsJob(
                $this->container, $this->sqs, $response['Messages'][0],
                $this->connectionName, $queue
            );
        }
    }
}
<?php

namespace App\Jobs\SqsNotifications;

use Illuminate\Queue\Jobs\JobName;

/**
 * Class SqsJob
 * @package App\Jobs\SqsNotifications
 *
 * Alternate SQS job that is used in case of S3 notifications
 */
class SqsJob extends \Illuminate\Queue\Jobs\SqsJob
{

    /**
     * Get the name of the queued job class.
     *
     * @return string
     */
    public function getName()
    {

        $bucketName = '';

        // Define the name of the Process based on the bucket name
        switch($this->payload()['Records'][0]['s3']['bucket']['name']){
            case 'mybucket':
                $bucketName = 'NewMyBucketFileJob';
                break;
        }

        return $bucketName;
    }

   /**
    * Fire the job.
    *
    * @return void
    */
    public function fire()
    {
        // Mimic the original behavior with a different payload
        $payload = $this->payload();
        [$class, $method] = JobName::parse('\App\Jobs\\' . $this->getName() . '@handle');
        ($this->instance = $this->resolve($class))->{$method}($payload);

        // The Job wasn't automatically deleted, so we need to delete it manually once the process went fine
        $this->delete();
    }
}
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class ProcessDataGateNewFile implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct()
    {
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle($data)
    {        
        // Print the whole data structure
        print_r($data);
        // Or just the name of the uploaded file
        print_r($data['Records'][0]['s3']['object']['key']);
    }
}