feat(wechat): 微信机器人全链路集成与稳定性修复

- 新增:管理员后台的微信机器人双向收发参数设置页面及扫码绑定能力。
- 新增:WechatBotApiService 与 KafkaConsumerService 模块打通过往僵尸进程导致的拒绝连接问题。
- 新增:下发所有群发/私聊通知时统一带上「[和平聊吧]」标注前缀。
- 优化:前端个人中心绑定逻辑支持一键生成及复制动态口令。
- 修复:闭环联调修补各个模型中产生的变量警告如 stdClass 对象获取等异常预警。
This commit is contained in:
2026-04-02 14:56:51 +08:00
parent 8a809e3cc0
commit fc57f97c9e
19 changed files with 1552 additions and 6 deletions
+8
View File
@@ -208,6 +208,14 @@ class AutoSaveExp extends Command
$this->chatState->pushMessage($roomId, $sysMsg);
broadcast(new MessageSent($roomId, $sysMsg));
SaveMessageJob::dispatch($sysMsg);
// 触发微信机器人私聊通知 (等级提升)
try {
$wechatService = app(\App\Services\WechatBot\WechatNotificationService::class);
$wechatService->notifyLevelChange($user, $oldLevel, $newLevel);
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('WechatBot level change notification failed', ['error' => $e->getMessage()]);
}
}
// 5. 向用户私人推送"系统为你自动存点"信息,在其聊天框显示
@@ -0,0 +1,155 @@
<?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'];
// 绑定逻辑:必须是私聊,且内容格式为 BD-xxxxxx
if (! $isChatroom && preg_match('/^BD-\d{6}$/i', $content)) {
$this->info("收到潜在绑定请求: {$content} from {$fromUser}");
$this->handleBindRequest(strtoupper($content), $fromUser, $apiService);
}
}
/**
* 处理账号绑定请求
*/
protected function handleBindRequest(string $code, string $wxid, WechatBotApiService $apiService): void
{
$cacheKey = 'wechat_bind_code:'.$code;
$username = Cache::get($cacheKey);
if (! $username) {
$apiService->sendTextMessage($wxid, '❌ 绑定失败:该验证码无效或已过有效期(5分钟)。请在个人中心重新生成。');
return;
}
$user = User::where('username', $username)->first();
if (! $user) {
$apiService->sendTextMessage($wxid, '❌ 绑定失败:找不到对应的用户账号。');
return;
}
// 判断该微信号是否已经被其他用户绑定(防止碰撞或安全隐患)
$existing = User::where('wxid', $wxid)->where('id', '!=', $user->id)->first();
if ($existing) {
$apiService->sendTextMessage($wxid, "❌ 绑定失败:当前微信号已经被其他账号 [{$existing->username}] 绑定。请先解绑后再试。");
return;
}
$user->wxid = $wxid;
$user->save();
// 验证成功后立即销毁验证码
Cache::forget($cacheKey);
$this->info("用户 [{$username}] 成功绑定微信: {$wxid}");
$successMsg = "🎉 绑定成功!\n"
."您已成功绑定聊天室账号:[{$username}]。\n"
.'现在您可以接收重要系统通知了。';
$apiService->sendTextMessage($wxid, $successMsg);
}
}
@@ -0,0 +1,78 @@
<?php
/**
* 文件功能:测试发送微信机器人消息
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Console\Commands;
use App\Models\SysParam;
use App\Services\WechatBot\WechatBotApiService;
use Illuminate\Console\Command;
class WechatBotTestSend extends Command
{
/**
* @var string
*/
protected $signature = 'wechat-bot:test-send';
/**
* @var string
*/
protected $description = '测试发送一条消息给管理员设定的微信群群 wxid';
/**
* Execute the console command.
*/
public function handle(): int
{
$this->info('开始测试微信机器人发送...');
$param = SysParam::where('alias', 'wechat_bot_config')->first();
if (! $param || empty($param->body)) {
$this->error('错误:未找到 wechat_bot_config 配置,请先在后台保存一次配置。');
return self::FAILURE;
}
$config = json_decode($param->body, true);
$targetWxid = $config['group_notify']['target_wxid'] ?? '';
if (empty($targetWxid)) {
$this->error('错误:请于后台填写【目标微信群 Wxid】。');
return self::FAILURE;
}
if (empty($config['api']['bot_key'] ?? '')) {
$this->error('错误:未配置【机器人 Key (必需)】,API请求将被拒绝(返回该链接不存在)。');
return self::FAILURE;
}
$service = new WechatBotApiService;
$this->info("发送目标: {$targetWxid}");
$this->info('发送 API Base: '.($config['api']['base_url'] ?? ''));
$message = "【系统连通性测试】\n发送时间:".now()->format('Y-m-d H:i:s')."\n如果您看到了这条消息,说明 ChatRoom 通知全站群发接口配置正确!";
$result = $service->sendTextMessage($targetWxid, $message);
if ($result['success']) {
$this->info('✅ 发送成功!');
return self::SUCCESS;
} else {
$this->error('❌ 发送失败:'.($result['error'] ?? '未知错误'));
$this->warn('如果提示『该链接不存在』代表您的基础API URL 或接入 Key 有误。');
return self::FAILURE;
}
}
}