2026-04-02 14:56:51 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件功能:微信机器人 Kafka 消费命令
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author ChatRoom Laravel
|
|
|
|
|
|
*
|
|
|
|
|
|
* @version 1.0.0
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
|
use App\Services\WechatBot\KafkaConsumerService;
|
|
|
|
|
|
use App\Services\WechatBot\WechatBotApiService;
|
|
|
|
|
|
use Illuminate\Console\Command;
|
|
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
|
|
|
|
|
|
|
class ConsumeWechatMessages extends Command
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @var string
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected $signature = 'wechat-bot:consume';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @var string
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected $description = '消费 Kafka 微信机器人消息(守护进程)';
|
|
|
|
|
|
|
|
|
|
|
|
protected KafkaConsumerService $kafkaService;
|
|
|
|
|
|
|
|
|
|
|
|
public function __construct(KafkaConsumerService $kafkaService)
|
|
|
|
|
|
{
|
|
|
|
|
|
parent::__construct();
|
|
|
|
|
|
$this->kafkaService = $kafkaService;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function handle(): int
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->info('正在启动微信机器人 Kafka 消费者...');
|
|
|
|
|
|
|
|
|
|
|
|
$consumer = $this->kafkaService->createConsumer();
|
|
|
|
|
|
if (! $consumer) {
|
|
|
|
|
|
$this->error('Kafka 配置不完整或加载失败,请在后台检查机器人设置。');
|
|
|
|
|
|
|
|
|
|
|
|
return self::FAILURE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->info('消费者已启动,等待消息...');
|
|
|
|
|
|
|
|
|
|
|
|
$apiService = new WechatBotApiService;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
$messageJson = $consumer->consume();
|
|
|
|
|
|
if ($messageJson) {
|
|
|
|
|
|
$rawJson = $messageJson->getValue();
|
|
|
|
|
|
$this->info('--> 收到新的 Kafka 消息 (Raw Length: '.strlen($rawJson).')');
|
|
|
|
|
|
|
|
|
|
|
|
$messages = $this->kafkaService->parseKafkaMessage($rawJson);
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($messages)) {
|
|
|
|
|
|
$this->info('--> 解析后:无匹配的 AddMsgs 内容');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($messages as $msg) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
$this->processMessage($msg, $apiService);
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('处理单条微信消息失败', [
|
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
|
'msg' => $msg,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$consumer->ack($messageJson);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Log::error('Kafka 消费异常', ['error' => $e->getMessage()]);
|
|
|
|
|
|
// 延迟重试避免死循环 CPU 空转
|
|
|
|
|
|
sleep(2);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return self::SUCCESS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理单条消息逻辑
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function processMessage(array $msg, WechatBotApiService $apiService): void
|
|
|
|
|
|
{
|
|
|
|
|
|
// 仅处理文本消息 (msg_type = 1)
|
|
|
|
|
|
if ($msg['msg_type'] != 1) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$content = trim($msg['content']);
|
|
|
|
|
|
$fromUser = $msg['from_user'];
|
|
|
|
|
|
$isChatroom = $msg['is_chatroom'];
|
|
|
|
|
|
|
2026-04-02 15:38:26 +08:00
|
|
|
|
// 绑定逻辑:必须是私聊(防止在群内绑定导致未来系统无法直接通过私聊推送个人通知)
|
|
|
|
|
|
if (! $isChatroom && preg_match('/^BD-\d{6}$/i', $content)) {
|
|
|
|
|
|
$this->info("收到潜在绑定请求: {$content} from {$fromUser}");
|
|
|
|
|
|
$this->handleBindRequest(strtoupper($content), $fromUser, $apiService);
|
2026-04-02 14:56:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理账号绑定请求
|
|
|
|
|
|
*/
|
2026-04-02 15:38:26 +08:00
|
|
|
|
protected function handleBindRequest(string $code, string $wxid, WechatBotApiService $apiService): void
|
2026-04-02 14:56:51 +08:00
|
|
|
|
{
|
|
|
|
|
|
$cacheKey = 'wechat_bind_code:'.$code;
|
|
|
|
|
|
$username = Cache::get($cacheKey);
|
|
|
|
|
|
|
|
|
|
|
|
if (! $username) {
|
2026-04-02 15:38:26 +08:00
|
|
|
|
$apiService->sendTextMessage($wxid, '❌ 绑定失败:该验证码无效或已过有效期(5分钟)。请在个人中心重新生成。');
|
2026-04-02 14:56:51 +08:00
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::where('username', $username)->first();
|
|
|
|
|
|
if (! $user) {
|
2026-04-02 15:38:26 +08:00
|
|
|
|
$apiService->sendTextMessage($wxid, '❌ 绑定失败:找不到对应的用户账号。');
|
2026-04-02 14:56:51 +08:00
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断该微信号是否已经被其他用户绑定(防止碰撞或安全隐患)
|
|
|
|
|
|
$existing = User::where('wxid', $wxid)->where('id', '!=', $user->id)->first();
|
|
|
|
|
|
if ($existing) {
|
2026-04-02 15:38:26 +08:00
|
|
|
|
$apiService->sendTextMessage($wxid, "❌ 绑定失败:当前微信号已经被其他账号 [{$existing->username}] 绑定。请先解绑后再试。");
|
2026-04-02 14:56:51 +08:00
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$user->wxid = $wxid;
|
|
|
|
|
|
$user->save();
|
|
|
|
|
|
|
|
|
|
|
|
// 验证成功后立即销毁验证码
|
|
|
|
|
|
Cache::forget($cacheKey);
|
|
|
|
|
|
|
|
|
|
|
|
$this->info("用户 [{$username}] 成功绑定微信: {$wxid}");
|
|
|
|
|
|
|
|
|
|
|
|
$successMsg = "🎉 绑定成功!\n"
|
|
|
|
|
|
."您已成功绑定聊天室账号:[{$username}]。\n"
|
|
|
|
|
|
.'现在您可以接收重要系统通知了。';
|
|
|
|
|
|
|
2026-04-02 15:38:26 +08:00
|
|
|
|
$apiService->sendTextMessage($wxid, $successMsg);
|
2026-04-02 14:56:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|