Files
chatroom/app/Services/WechatBot/KafkaConsumerService.php
lkddi fc57f97c9e feat(wechat): 微信机器人全链路集成与稳定性修复
- 新增:管理员后台的微信机器人双向收发参数设置页面及扫码绑定能力。
- 新增:WechatBotApiService 与 KafkaConsumerService 模块打通过往僵尸进程导致的拒绝连接问题。
- 新增:下发所有群发/私聊通知时统一带上「[和平聊吧]」标注前缀。
- 优化:前端个人中心绑定逻辑支持一键生成及复制动态口令。
- 修复:闭环联调修补各个模型中产生的变量警告如 stdClass 对象获取等异常预警。
2026-04-02 14:56:51 +08:00

134 lines
4.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能Kafka 消费者服务
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Services\WechatBot;
use App\Models\SysParam;
use Illuminate\Support\Facades\Log;
use longlang\phpkafka\Consumer\Consumer;
use longlang\phpkafka\Consumer\ConsumerConfig;
class KafkaConsumerService
{
/**
* Kafka Broker 地址
*/
protected string $brokers = '';
/**
* 消费 Topic
*/
protected string $topic = '';
/**
* 消费者组 ID
*/
protected string $groupId = '';
/**
* 构造函数 — 从 SysParam 获取配置
*/
public function __construct()
{
$param = SysParam::where('alias', 'wechat_bot_config')->first();
if ($param && ! empty($param->body)) {
$config = json_decode($param->body, true);
$this->brokers = $config['kafka']['brokers'] ?? '';
$this->topic = $config['kafka']['topic'] ?? '';
$this->groupId = $config['kafka']['group_id'] ?? 'chatroom_wechat_bot';
}
}
/**
* 创建 Kafka 消费者实例
*/
public function createConsumer(): ?Consumer
{
if (empty($this->brokers) || empty($this->topic)) {
Log::warning('WechatBot Kafka: brokers or topic is empty. Consumer not started.');
return null;
}
$config = new ConsumerConfig;
$config->setBroker($this->brokers);
$config->setTopic($this->topic);
$config->setGroupId($this->groupId);
$config->setClientId('chatroom_wechat_bot_'.getmypid().'_'.uniqid());
$config->setGroupInstanceId('chatroom_wechat_bot_instance_'.getmypid().'_'.uniqid());
$config->setInterval(0.5); // 拉取间隔(秒)
$config->setGroupRetry(5); // 组协调重试次数
$config->setGroupRetrySleep(1); // 组协调重试间隔(秒)
return new Consumer($config);
}
/**
* 解析 Kafka 原始消息,提取有效的聊天消息列表
*
* @param string $rawJson Kafka 消息体 JSON 字符串
* @return array 解析后的消息数组
*/
public function parseKafkaMessage(string $rawJson): array
{
try {
$data = json_decode($rawJson, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
Log::warning('微信机器人 Kafka 消息 JSON 解析失败', ['error' => $e->getMessage()]);
return [];
}
// 只处理包含 AddMsgs 的消息
$addMsgs = $data['AddMsgs'] ?? [];
if (empty($addMsgs)) {
return [];
}
$messages = [];
foreach ($addMsgs as $msg) {
$fromUser = $msg['from_user_name']['str'] ?? '';
$toUser = $msg['to_user_name']['str'] ?? '';
$msgType = $msg['msg_type'] ?? 0;
$content = $msg['content']['str'] ?? '';
// 判断是否来自群聊
$isChatroom = str_contains($fromUser, '@chatroom');
// 群聊消息中from_user 是群ID实际发送者在 content 中以 "wxid:\n内容" 格式出现
$actualSender = $fromUser;
$actualContent = $content;
$chatroomId = null;
if ($isChatroom) {
$chatroomId = $fromUser;
// 解析群聊消息格式: "发送者wxid:\n实际内容"
if (preg_match('/^(.+?):\n(.*)$/s', $content, $matches)) {
$actualSender = $matches[1];
$actualContent = $matches[2];
}
}
$messages[] = [
'msg_id' => $msg['new_msg_id'] ?? $msg['msg_id'] ?? 0,
'from_user' => $actualSender,
'to_user' => $toUser,
'chatroom_id' => $chatroomId,
'msg_type' => $msgType,
'content' => $actualContent,
'raw_content' => $content,
'is_chatroom' => $isChatroom,
];
}
return $messages;
}
}