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

2022-01-21 00:00:00 queue redis node.js javascript laravel

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

I have a Laravel 5.3 installation running as a pure API application and need to connect from several different applications.

一切正常(毕竟我们谈论的是 Laravel :P),除了我无法弄清楚一件事:

Everything is working fine (after all it's Laravel we're talking about :P), except I can't figure out one thing:

我有一个 MQTT 服务器,它正在侦听来自多个设备的消息(不管是什么).这些消息包含有关需要在后端调用的作业类和方法的信息.

I have a MQTT server that is listening for messages from several devices (doesn't matter what). These messages contain information about a Job Class and method that need to be called on the backend.

我不能直接调用 API,设备根本不支持这个(他们支持,但比使用 MQTT 传输数据要努力得多).我的想法是将新作业推送到定义要调用哪个 Laravel 作业类(以及哪个方法)的队列中.问题在于 JSON 序列化...

I can't call the API directly, the devices simply don't support this (they do, but it's a lot more effort than using MQTT to transmit data). My thought was to push a new job onto the queue defining which Laravel Job Class to call (and which method). The problem is the JSON serialization...

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

The MQTT server is running on NodeJS, my queues are running on Redis. I remember a Tweet from Taylor where he mentioned that theoretically it could be possible to serialize the JSON needed and push to the queue from outside Laravel, and have the job processed by Laravel.

有人知道如何解决这个问题吗?是否有关于 JSON 结构的文档?

Anyone any idea how to approach this? Is there a documentation available about the structure of the JSON?

我还应该提到这个解决方案 NodeJS 推送队列,Laravel 工人消耗的 对我不起作用.与上面的结果相同,作业被放入队列但没有被处理或抛出任何错误就被丢弃了.

I should also mention that this solution NodeJS push queue, consumed by Laravel worker did not work for me. Same result as above, the job is placed on the queue but discarded without being processed or any errors thrown.

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

The sample data structure for a queued Event in Redis looks like this:

"{"job":"Illuminate\\Broadcasting\\BroadcastEvent","data":{"event":"O:28:\"App\\Events\\NotificationEvent\":5:{s:7:\"\u0000*\u0000name\";s:12:\"通知\\";s:4:\\"数据\\";a:4:{s:4:\"testkey\\";s:14:\\"testval\";s:9:\"时间戳\";s:19:\"2017-02-24 11:07:48\";s:5:\"事件\";s:12:\\"通知\\";s:5:\\"类\\";s:28:\\"应用程序\\事件\\NotificationEvent\\";}s:10:\\"\u0000*\u0000channel\\";N;s:7:\\"\u0000*\u0000user\\";O:45:\"照亮\\Contracts\\Database\\ModelIdentifier\\":2:{s:5:\"class\";s:8:\"App\\User\";s:2:\"id\";i:2;}s:6:\"socket\";N;}"},"id":"XuUKRTf8CTSdzaVgp2gRcvmxQqLcpBUG","attempts":1}"

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

Based on that structure, I would think the object (that needs to be serialized) should look similar to this:

{
"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 实际放入队列的内容中包含其他信息(如时间戳、用户模型标识符等),但我认为这不是触发工作所必需的.

There is additional information included in what Laravel actually puts on the queue (like a timestamp, user model identifier, etc), but I don't think that's necessary to trigger the job.

数据需要在 JS 中序列化,以实现与 php 类似的输出 serialize() (或者更好的是,获取一个可以通过 php 的 unserialize().

The data needs to be serialized in JS to achieve a similar output than with php serialize() (or better, to get a string that can be unserialized by php's unserialize().

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

I achieved to do so with the php-serialization NPM module (Thanks to Simon Svensson), but the job still isn't being consumed by Laravel (discarded but not executed)

提前感谢您的帮助:)

编辑解决方案

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

Thanks to Simon's answer, here's the solution on how to serialize job data in Javascript and push onto a Laravel queue (and have Laravel process the whole thing automatically).

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

Note that this is an example for using queues with Redis. When using Beanstalkd or Database based queues, this might look different (or not).

这是我成功使用的代码:

This is the code I successfully used:

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 个字符,我认为保持这个长度是个好主意).

I haven't figured out yet what the ID is exactly for, I successfully pushed jobs onto the queue with always the same Id. I guess if you're pushing jobs at a fast pace and they are stored at the same time it could be a problem. Since it's a string you could substitute it with any random ID you like (the random ID generated by Laravel is 32 characters, I think it's a good idea to keep this length).

最初推送作业时应将尝试次数设置为 1.如果 Laravel 无法处理该作业,它会将其推回队列并增加尝试次数.

Attempts should be set to 1 when initially pushing the job. If Laravel can't process the job, it will push it back onto the queue and increase the attempts value.

推荐答案

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

First, note that this is the format of the jobs in the database-based queue in Laravel 5.3. Newer versions of Laravel contains changes.

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

The payload column should contain a json object of the following format. The job (...\CallQueuedHandler@call) can be hard-coded in this scenario. I believe the commandName key is for display-purposes only. The command key, however, is the harder part, it should be a valid object that unserialize() supports. It looks like there are packages available on npm for this purpose, a quick search turned up php-serialization.

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

<小时>

您提供的 json 有效负载会生成以下对象.作业和数据键都很重要.


The json payload you've provided results in the following object. Both the job and the data keys are important.

{
  "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
}

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

The problematic part, I presume, is the serialized object. Reformatted in a way that is easier to read (but totally breaks it) ...

O:28:"AppEventsNotificationEvent":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' => AppEventsNotificationEvent::class;
        s:5:"class";s:28:"AppEventsNotificationEvent";
    }

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

    // protected $user = (instance of ModelIdentifier)
    s:7:"*user";O:45:"IlluminateContractsDatabaseModelIdentifier":2:{
        // public $class = AppUser::class;
        s:5:"class";s:8:"AppUser";

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

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

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

This format exposes the fact that your job uses the SerializesModels trait that replaces references to models as a simple entry containing class+identifier, and will restore them during __wakeup.

我相信您的问题在于对 json 的心理解析和序列化格式;你猜测的结构是……错误的.

I believe your issue is with the mental parsing of the json and the serialize format; your guessed structure is ... wrong.

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

The next steps would not be to guess anything. 1. Duplicate this exact test notification you already have the payload for. Just copy-paste it. (You may need to change the id, I guess it is used for deduplication.) 2. Build the event data using php-serialization and aim to build something identical to the original event payload. No changes at all. 3. If it works this far, feel free to change the serialized event data to see what happens.

相关文章