从Laravel(NodeJS)外部推送到Laravel队列

时间:2017-02-26 06:26:33

标签: javascript node.js laravel redis queue

我将Laravel 5.3安装作为纯API应用程序运行,需要从多个不同的应用程序进行连接。

一切都运转良好(毕竟我们正在讨论的是Laravel:P),除了我无法弄清楚一件事:

我有一个MQTT服务器正在侦听来自多个设备的消息(无关紧要)。这些消息包含有关需要在后端调用的Job类和方法的信息。

我不能直接调用API,设备根本不支持这个(他们这样做,但是比使用MQTT传输数据要费力更多)。我的想法是将一个新工作推入队列,定义要调用哪个Laravel Job Class(以及哪个方法)。问题是JSON序列化......

MQTT服务器在NodeJS上运行,我的队列在Redis上运行。我记得泰勒发来的一条推文,他提到理论上可以将所需的JSON序列化并从Laravel外部推送到队列中,并将工作由Laravel处理。

任何人都知道如何处理这个问题?是否有关于JSON结构的文档?

我还应该提一下,这个解决方案NodeJS push queue, consumed by Laravel worker对我不起作用。与上面相同的结果是,作业被放置在队列中但被丢弃而不被处理或抛出任何错误。

Redis中排队事件的示例数据结构如下所示:

"{\"job\":\"Illuminate\\\\Broadcasting\\\\BroadcastEvent\",\"data\":{\"event\":\"O:28:\\\"App\\\\Events\\\\NotificationEvent\\\":5:{s:7:\\\"\\u0000*\\u0000name\\\";s:12:\\\"notification\\\";s:4:\\\"data\\\";a:4:{s:4:\\\"testkey\\\";s:14:\\\"testval\\\";s:9:\\\"timestamp\\\";s:19:\\\"2017-02-24 11:07:48\\\";s:5:\\\"event\\\";s:12:\\\"notification\\\";s:5:\\\"class\\\";s:28:\\\"App\\\\Events\\\\NotificationEvent\\\";}s:10:\\\"\\u0000*\\u0000channel\\\";N;s:7:\\\"\\u0000*\\u0000user\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":2:{s:5:\\\"class\\\";s:8:\\\"App\\\\User\\\";s:2:\\\"id\\\";i:2;}s:6:\\\"socket\\\";N;}\"},\"id\":\"XuUKRTf8CTSdzaVgp2gRcvmxQqLcpBUG\",\"attempts\":1}"

基于该结构,我认为对象(需要序列化)应该与此类似:

{
"job":"EventClass@method", //<-- Just a name
"data":{
    "event":"EventClass", //<-- Just a name
    "name":"EventName", //<-- Just a name
    "data":{
    "key":"value"
    "event":"EventName" //<-- Same as data.name
    "class":"EventClass@method" //<-- This is actually being called
    }
}

Laravel实际上在队列中包含的其他信息(如时间戳,用户模型标识符等),但我认为没有必要触发该作业。

需要在JS中序列化数据以实现与php serialize()类似的输出(或者更好,获取可以通过php unserialize()反序列化的字符串。

我使用php-serialization NPM模块实现了这一点(感谢Simon Svensson),但是Laravel仍然没有使用该作业(丢弃但未执行)

提前感谢您的任何帮助:)

编辑解决方案

感谢Simon的回答,这里有关于如何在Javascript中序列化作业数据并推送到Laravel队列的解决方案(并让Laravel自动处理整个事情)。

请注意,这是使用Redis队列的示例。使用Beanstalkd或基于数据库的队列时,这可能看起来不同(或不是)。

这是我成功使用的代码:

var serialize,Class,job,jobUser,jobData,serialized,result;

serialize = require('php-serialization').serialize;
Class = require('php-serialization').Class;

job = new Class("App\\Events\\NotificationEvent");

job.__addAttr__("name","string","notification","string","protected");

jobData = new Class();
jobData.__addAttr__("testkey","string","testval","string");
jobData.__addAttr__("timestamp","string","2017-02-24 11:07:48","string");
jobData.__addAttr__("event","string","notification","string");
jobData.__addAttr__("class","string","App\\Events\\NotificationEvent","string");
job.__addAttr__("data","string",jobData,"array","public");

job.__addAttr__("channel","string",null,"null","protected");

jobUser = new Class("Illuminate\\Contracts\\Database\\ModelIdentifier")
jobUser.__addAttr__("class","string","App\\User","string","public");
jobUser.__addAttr__("id","string",2,"integer","public");
job.__addAttr__("user","string",jobUser,"object","protected");

job.__addAttr__("socket","string",null,"null","public");

serialized = serialize(job,"object");

result = {
    job:"Illuminate\\Broadcasting\\BroadcastEvent",
    data:{
        event:serialized
    },
    id:"XuUKRTf8CTSdzaVgp2gRcvmxQqLcpBUG",
    attempts:1
};

queue.rpush('queues:default',JSON.stringify(result));

我还没有弄清楚ID到底是什么,我成功地将作业推送到队列中并且始终具有相同的Id。我想如果你正在快速推动工作并且他们同时存储它可能是一个问题。因为它是一个字符串,你可以用你喜欢的任何随机ID替换它(Laravel生成的随机ID是32个字符,我认为保持这个长度是个好主意。)

最初推动作业时,尝试应设置为1。如果Laravel无法处理作业,它会将其推回队列并增加尝试值。

4 个答案:

答案 0 :(得分:3)

首先,请注意这是Laravel 5.3中基于数据库的队列中作业的格式。较新版本的Laravel包含更改。

有效内容列应包含以下格式的json对象。在这种情况下,作业(...\\CallQueuedHandler@call)可以进行硬编码。我相信commandName键仅用于显示目的。但是,命令键是更难的部分,它应该是unserialize()支持的有效对象。看起来npm上有可用于此目的的软件包,快速搜索php-serialization

{
    "job": "Illuminate\\Queue\\CallQueuedHandler@call",
    "data": {
        "commandName": "App\\Jobs\\MyJobClass",
        "command": "O:19:\"App\\Jobs\\MyJobClass\"... /* stuff */"
    }
}

您提供的json有效内容会产生以下对象。作业和数据键都很重要。

{
  "job": "Illuminate\\Broadcasting\\BroadcastEvent",
  "data": {
    "event": "O:28:\"App\\Events\\NotificationEvent\":5:{s:7:\"\u0000*\u0000name\";s:12:\"notification\";s:4:\"data\";a:4:{s:4:\"testkey\";s:14:\"testval\";s:9:\"timestamp\";s:19:\"2017-02-24 11:07:48\";s:5:\"event\";s:12:\"notification\";s:5:\"class\";s:28:\"App\\Events\\NotificationEvent\";}s:10:\"\u0000*\u0000channel\";N;s:7:\"\u0000*\u0000user\";O:45:\"Illuminate\\Contracts\\Database\\ModelIdentifier\":2:{s:5:\"class\";s:8:\"App\\User\";s:2:\"id\";i:2;}s:6:\"socket\";N;}"
  },
  "id": "XuUKRTf8CTSdzaVgp2gRcvmxQqLcpBUG",
  "attempts": 1
}

我认为,有问题的部分是序列化对象。以更容易阅读的方式重新格式化(但完全打破了它)......

O:28:"App\Events\NotificationEvent":5:{
    // protected $name = 'notification'
    s:7:" * name";s:12:"notification";

    // public $data = array(...)
    s:4:"data";a:4:{
        // 'testkey => 'testval'
        s:4:"testkey";s:14:"testval";

        // 'timestamp' => '2017-02-24 11:07:48';
        s:9:"timestamp";s:19:"2017-02-24 11:07:48";

        // 'event' => 'notification';
        s:5:"event";s:12:"notification";

        // 'class' => App\Events\NotificationEvent::class;
        s:5:"class";s:28:"App\Events\NotificationEvent";
    }

    // protected $channel = null;
    s:10:"\0*\0channel";N;

    // protected $user = (instance of ModelIdentifier)
    s:7:"\0*\0user";O:45:"Illuminate\Contracts\Database\ModelIdentifier":2:{
        // public $class = App\User::class;
        s:5:"class";s:8:"App\User";

        // public $id = 2;
        s:2:"id";i:2;
    }

    // public $socket = null;
    s:6:"socket";N;
}

这种格式暴露了这样一个事实:你的作业使用SerializesModels特性将模型的引用替换为包含类+标识符的简单条目,并将在__wakeup期间恢复它们。

我相信你的问题在于json和serialize格式的心理解析;你推测的结构是错误的。

接下来的步骤不是猜测任何事情。 1.复制您已经拥有有效负载的确切测试通知。只需复制粘贴即可。 (您可能需要更改ID,我猜它用于重复数据删除。) 2.使用php-serialization构建事件数据,旨在构建与原始事件有效负载相同的内容。完全没有变化。 3.如果它工作到目前为止,请随意更改序列化事件数据以查看会发生什么。

答案 1 :(得分:0)

此解决方案相当脏,将来可能无法使用,但是...

您可以复制将作业发送到队列的“旧方法”:

\Queue::push('\Namespace\Of\Class@handlerMethod', ['foo' => 'bar']);

并在处理程序中:

public function handlerMethods($job, $data)
{
    $job->delete;
    // do whatever with data
}

在Node.js中,您将执行以下操作:

SQS.sendMessage({
    MessageBody: JSON.stringify({
        job: '\Namespace\Of\Class@handlerMethod',
        data: {
            foo: 'bar',
        }
    }),
    QueueUrl: yourQueueUrl
 }, err => { /* handle */ });

缺点

  • 您必须手动删除作业

  • 我不确定死信队列是否可以使用这种方法

  • 无法以这种方式序列化模型-如果您需要在处理程序中从数据中获取模型,则需要传递主键并以此方式进行检索

  • 还没有尝试过实际的工作,并且我的处理程序类相当“功利”,但是有可能,它可能不适用于实际的laravel工作

  • 此方法来自Laravel 4,因此可能已经不建议使用,即使将来可能也不会删除/更改

这样做需要您自担风险,但这是有可能的。

答案 2 :(得分:0)

对于需要从事“经典”工作的人,以下是基于 Jan Schuermanns 原始问题和解决方案的代码:

const php = require('php-serialization');

var job_class = 'App\\Jobs\\TestJob';
var data = {}

job = new php.Class(job_class);

job.__addAttr__("data","string", JSON.stringify(data),"string","private");

result = {
    id: 1,
    type: 'job',
    displayName: job_class,
    job: "Illuminate\\Queue\\CallQueuedHandler@call",
    maxTries: null,
    delay: null,
    timeout: null,
    timeoutAt: null,
    data: {
        commandName: job_class,
        command: php.serialize(job, "object")
    }
};

示例作业类别:

<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;

class TestJob implements ShouldQueue
{
    private $data;

    public function __construct($data)
    {
        $this->data = $data;
    }

    public function handle() {
        var_dump(json_decode($this->data));
        return;
    }
}

导致:

[2019-08-17 ...] Processing: App\Jobs\TestJob
object(stdClass)#1870 (0) {
}
[2019-08-17 ...] Processed:  App\Jobs\TestJob

编辑: 如果您必须处理变音符号或其他特殊字符,则可能需要将latin1与iconv-lite一起使用:

iconv.decode(Buffer.from(JSON.stringify(data)), 'latin1')

job.__addAttr__("data","string", JSON.stringify(data),"string","private");

...
queueChannel.sendToQueue(queuePublish, Buffer.from(JSON.stringify(result), 'latin1'));

答案 3 :(得分:0)

仅公开用于编写作业处理器的API。 如果我们在谈论节点js和laravel。需要公开一个API,该API将接受HTTP请求并放置作业,然后返回到节点js服务器。

稍后,如果您必须在安装了不同语言的另一台服务器上执行该作业,则其他人提供的解决方案将无法正常工作,并且将局限于特定的框架。