update entiity parse
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Hyperf\Collection\LazyCollection;
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Psr\Container\ContainerInterface;
|
||||
@@ -19,46 +20,52 @@ class AppMessageQueuePushTmall extends HyperfCommand
|
||||
{
|
||||
parent::__construct('app:mq-push:tmall');
|
||||
}
|
||||
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('Test push message with Tmall km data');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
// 从 raw 数据库连接获取数据
|
||||
$orders = Db::connection('raw')
|
||||
->table('wpic_taobao_order')
|
||||
->orderBy('id', 'desc')
|
||||
->limit(10)
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($orders)) {
|
||||
->table('wpic_taobao_order')->orderBy('id', 'desc')
|
||||
->limit(10)->get('order_raw')->lazy();
|
||||
|
||||
// dump($orders->first());
|
||||
// return;
|
||||
|
||||
if ($orders->isEmpty()) {
|
||||
$this->warn('No orders found in wpic_taobao_order table');
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info(sprintf('Found %d orders, processing...', count($orders)));
|
||||
|
||||
|
||||
$this->info(sprintf('Found %d orders, processing...', $orders->count()));
|
||||
|
||||
// 获取 Producer 实例
|
||||
$producer = $this->container->get(Producer::class);
|
||||
|
||||
// 每 2 条记录组成一条消息
|
||||
$chunks = array_chunk($orders, 2);
|
||||
|
||||
// 每 2 条记录组成一条消息 - 实际生产环境需要增大这个值
|
||||
// $orders->chunk(2)->each(function($collection) use ($producer) {
|
||||
|
||||
$messageCount = 0;
|
||||
|
||||
$orders->chunk(2)->each(function (LazyCollection $collection) use ($producer, &$messageCount) {
|
||||
|
||||
foreach ($chunks as $index => $chunk) {
|
||||
// 构造消息数据(根据实际表结构调整字段映射)
|
||||
$order_data = $collection->pluck('order_raw')->map(function ($item) {
|
||||
return json_decode($item, true);
|
||||
})->toArray();
|
||||
|
||||
//@ATTENTION 生产环境需要注意, 暂时使用 KM 进行测试
|
||||
$messageData = [
|
||||
'company_id' => $chunk[0]->company_id ?? 'default_company',
|
||||
'platform_id' => $chunk[0]->platform_id ?? 'tmall',
|
||||
'store_id' => $chunk[0]->store_id ?? 'default_store',
|
||||
'unique_id' => implode('_', array_column($chunk, 'id')),
|
||||
'raw_data' => $chunk, // 包含 2 条原始记录
|
||||
'company_id' => 188,
|
||||
'platform_id' => 2,
|
||||
'store_id' => 292,
|
||||
'unique_id' => uniqid() . '_' . time(),
|
||||
'raw_data' => $order_data, // 包含 2 条原始记录
|
||||
];
|
||||
|
||||
// 创建并发送消息
|
||||
@@ -66,19 +73,21 @@ class AppMessageQueuePushTmall extends HyperfCommand
|
||||
$producer->produce($message);
|
||||
|
||||
$messageCount++;
|
||||
|
||||
$this->line(sprintf('Sent message %d with order IDs: %s',
|
||||
$messageCount,
|
||||
$messageData['unique_id']
|
||||
$messageData['unique_id'],
|
||||
));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
$this->info(sprintf('Successfully sent %d messages to RabbitMQ', $messageCount));
|
||||
return 0;
|
||||
|
||||
return;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error('Error pushing messages: ' . $e->getMessage());
|
||||
$this->error($e->getTraceAsString());
|
||||
return 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
#[Command]
|
||||
class AppMqClear extends HyperfCommand
|
||||
{
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
parent::__construct('app:mq:clear');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('Clear all messages from a specified queue (development mode only)');
|
||||
$this->addArgument('queue', InputArgument::REQUIRED, 'The queue name to clear');
|
||||
$this->addOption('force', 'f', null, 'Force clear without confirmation');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$queueName = $this->input->getArgument('queue');
|
||||
$force = $this->input->getOption('force');
|
||||
|
||||
$this->warn("You are about to clear all messages from queue: {$queueName}");
|
||||
|
||||
// 如果不是强制模式,需要确认
|
||||
if (!$force) {
|
||||
$confirm = $this->confirm('Are you sure you want to continue?', false);
|
||||
if (!$confirm) {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
$consumerConfig = $config->get('amqp.default_consumer');
|
||||
|
||||
// 创建连接
|
||||
$connection = new AMQPStreamConnection(
|
||||
$consumerConfig['host'],
|
||||
$consumerConfig['port'],
|
||||
$consumerConfig['user'],
|
||||
$consumerConfig['password'],
|
||||
$consumerConfig['vhost'],
|
||||
false,
|
||||
'AMQPLAIN',
|
||||
null,
|
||||
'en_US',
|
||||
$consumerConfig['params']['connection_timeout'] ?? 3.0,
|
||||
$consumerConfig['params']['read_write_timeout'] ?? 3.0,
|
||||
null,
|
||||
$consumerConfig['params']['keepalive'] ?? false,
|
||||
$consumerConfig['params']['heartbeat'] ?? 0
|
||||
);
|
||||
|
||||
$channel = $connection->channel();
|
||||
|
||||
// 先检查队列是否存在并获取当前消息数
|
||||
try {
|
||||
[$queue, $messageCount, $consumerCount] = $channel->queue_declare(
|
||||
$queueName,
|
||||
true, // passive - 只检查,不创建
|
||||
true, // durable
|
||||
false, // exclusive
|
||||
false // auto_delete
|
||||
);
|
||||
|
||||
$this->line("Queue '{$queueName}' has {$messageCount} messages before clearing.", 'info');
|
||||
|
||||
if ($messageCount === 0) {
|
||||
$this->info('Queue is already empty. Nothing to clear.');
|
||||
$channel->close();
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 清除队列中的所有消息
|
||||
$channel->queue_purge($queueName);
|
||||
|
||||
// 再次检查确认已清除
|
||||
[$queue, $remainingCount, $consumerCount] = $channel->queue_declare(
|
||||
$queueName,
|
||||
true, // passive
|
||||
true, // durable
|
||||
false, // exclusive
|
||||
false // auto_delete
|
||||
);
|
||||
|
||||
$this->line('');
|
||||
$this->info("Successfully cleared {$messageCount} messages from queue '{$queueName}'.");
|
||||
$this->line("Remaining messages: {$remainingCount}", 'comment');
|
||||
|
||||
} catch (\PhpAmqpLib\Exception\AMQPProtocolChannelException $e) {
|
||||
$this->error("Queue '{$queueName}' does not exist.");
|
||||
$this->line('Available queues can be found using: php bin/hyperf.php app:mq:status', 'comment');
|
||||
$channel->close();
|
||||
$connection->close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 关闭连接
|
||||
$channel->close();
|
||||
$connection->close();
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to clear queue: ' . $e->getMessage());
|
||||
$this->line('Trace: ' . $e->getTraceAsString(), 'comment');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
|
||||
#[Command]
|
||||
class AppMqStatus extends HyperfCommand
|
||||
{
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
parent::__construct('app:mq:status');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('Display message counts for all accessible queues');
|
||||
$this->addOption('watch', 'w', null, 'Watch mode - refresh every N seconds (default: 3)');
|
||||
$this->addOption('interval', 'i', \Symfony\Component\Console\Input\InputOption::VALUE_OPTIONAL, 'Refresh interval in seconds', '3');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$watchMode = $this->input->getOption('watch');
|
||||
$interval = (int) $this->input->getOption('interval');
|
||||
|
||||
if ($watchMode) {
|
||||
$this->info("Watch mode enabled. Refreshing every {$interval} seconds. Press Ctrl+C to exit.");
|
||||
$this->line('');
|
||||
|
||||
while (true) {
|
||||
// 清屏(对于支持 ANSI 的终端)
|
||||
$this->output->write("\033[2J\033[H");
|
||||
$this->displayQueueStatus();
|
||||
sleep($interval);
|
||||
}
|
||||
} else {
|
||||
return $this->displayQueueStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private function displayQueueStatus(): int
|
||||
{
|
||||
$this->line('Fetching queue status... ' . date('Y-m-d H:i:s'), 'info');
|
||||
|
||||
try {
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
$consumerConfig = $config->get('amqp.default_consumer');
|
||||
|
||||
// 创建连接
|
||||
$connection = new AMQPStreamConnection(
|
||||
$consumerConfig['host'],
|
||||
$consumerConfig['port'],
|
||||
$consumerConfig['user'],
|
||||
$consumerConfig['password'],
|
||||
$consumerConfig['vhost'],
|
||||
false,
|
||||
'AMQPLAIN',
|
||||
null,
|
||||
'en_US',
|
||||
$consumerConfig['params']['connection_timeout'] ?? 3.0,
|
||||
$consumerConfig['params']['read_write_timeout'] ?? 3.0,
|
||||
null,
|
||||
$consumerConfig['params']['keepalive'] ?? false,
|
||||
$consumerConfig['params']['heartbeat'] ?? 0
|
||||
);
|
||||
|
||||
$channel = $connection->channel();
|
||||
|
||||
// 获取所有队列信息
|
||||
$queues = $this->getQueuesFromAnnotations();
|
||||
|
||||
$tableData = [];
|
||||
$totalMessages = 0;
|
||||
|
||||
foreach ($queues as $queueName) {
|
||||
try {
|
||||
// 使用 passive=true 来获取队列信息而不创建队列
|
||||
// queue_declare 返回 [queue_name, message_count, consumer_count]
|
||||
[$queue, $messageCount, $consumerCount] = $channel->queue_declare(
|
||||
$queueName,
|
||||
true, // passive
|
||||
true, // durable
|
||||
false, // exclusive
|
||||
false // auto_delete
|
||||
);
|
||||
|
||||
$tableData[] = [
|
||||
'queue' => $queueName,
|
||||
'messages' => $messageCount,
|
||||
'consumers' => $consumerCount,
|
||||
'status' => $messageCount > 0 ? '<fg=yellow>Has Messages</>' : '<fg=green>Empty</>',
|
||||
];
|
||||
|
||||
$totalMessages += $messageCount;
|
||||
} catch (\Exception $e) {
|
||||
$tableData[] = [
|
||||
'queue' => $queueName,
|
||||
'messages' => 'N/A',
|
||||
'consumers' => 'N/A',
|
||||
'status' => '<fg=red>Error: ' . $e->getMessage() . '</>',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭连接
|
||||
$channel->close();
|
||||
$connection->close();
|
||||
|
||||
// 显示表格
|
||||
if (empty($tableData)) {
|
||||
$this->warn('No queues found.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->table(
|
||||
['Queue Name', 'Messages', 'Consumers', 'Status'],
|
||||
array_map(function($row) {
|
||||
return [$row['queue'], $row['messages'], $row['consumers'], $row['status']];
|
||||
}, $tableData)
|
||||
);
|
||||
|
||||
$this->line('');
|
||||
$this->info("Total messages across all queues: {$totalMessages}");
|
||||
|
||||
return 0;
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to fetch queue status: ' . $e->getMessage());
|
||||
$this->line('Trace: ' . $e->getTraceAsString(), 'comment');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描所有使用 Consumer 注解的类,获取队列名称
|
||||
*/
|
||||
private function getQueuesFromAnnotations(): array
|
||||
{
|
||||
$queues = [];
|
||||
|
||||
// 扫描 Platform 目录下的所有 Consumer
|
||||
$platformPath = BASE_PATH . '/app/Platform';
|
||||
|
||||
if (is_dir($platformPath)) {
|
||||
$this->scanDirectory($platformPath, $queues);
|
||||
}
|
||||
|
||||
// 如果没有找到任何队列,返回一些默认的队列名称
|
||||
if (empty($queues)) {
|
||||
$queues = ['orders.queue'];
|
||||
}
|
||||
|
||||
return array_unique($queues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归扫描目录,查找包含 Consumer 注解的类
|
||||
*/
|
||||
private function scanDirectory(string $path, array &$queues): void
|
||||
{
|
||||
$files = scandir($path);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filePath = $path . '/' . $file;
|
||||
|
||||
if (is_dir($filePath)) {
|
||||
$this->scanDirectory($filePath, $queues);
|
||||
} elseif (is_file($filePath) && pathinfo($filePath, PATHINFO_EXTENSION) === 'php') {
|
||||
$content = file_get_contents($filePath);
|
||||
|
||||
// 查找 Consumer 注解中的 queue 参数
|
||||
if (preg_match('/#\[Consumer\([^)]*queue:\s*["\']([^"\']+)["\']/', $content, $matches)) {
|
||||
$queues[] = $matches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user