feat: 微信端私聊机器人支持绑定微信号的密码自助重置功能,并提供反射自动化测试

This commit is contained in:
pllx
2026-07-01 10:53:24 +08:00
parent fcc88ecb0c
commit 879dcb0b59
2 changed files with 153 additions and 0 deletions
@@ -107,6 +107,44 @@ class ConsumeWechatMessages extends Command
$this->info("收到潜在绑定请求: {$content} from {$fromUser}");
$this->handleBindRequest(strtoupper($content), $fromUser, $apiService);
}
// 微信密码重置逻辑:必须是私聊
if (! $isChatroom && str_contains($content, '重置密码')) {
$this->info("收到微信密码重置请求 from {$fromUser}");
$this->handlePasswordResetRequest($fromUser, $apiService);
}
}
/**
* 处理微信端的自助密码重置请求
*/
protected function handlePasswordResetRequest(string $wxid, WechatBotApiService $apiService): void
{
// 检索绑定了该微信号的聊天室用户
$user = User::where('wxid', $wxid)->first();
if (! $user) {
$apiService->sendTextMessage(
$wxid,
"❌ 密码重置失败:您当前的微信号尚未绑定任何聊天室账号。\n\n"
.'请先登录聊天室大厅,在个人设置中获取 6 位绑定码,然后再在微信中向我发送“BD-绑定码”(例如: BD-123456)进行绑定。'
);
return;
}
// 随机生成 8 位新随机密码并更新
$newPassword = \Illuminate\Support\Str::random(8);
$user->password = \Illuminate\Support\Facades\Hash::make($newPassword);
$user->save();
$successMsg = "🎉 密码重置成功!\n"
."本小助手已为您绑定的聊天室账号 [{$user->username}] 重新生成了密码:\n\n"
."{$newPassword}\n\n"
.'温馨提示:请使用此随机密码登录聊天室,并尽快在个人偏好设置中将其修改为您的常用密码。';
$apiService->sendTextMessage($wxid, $successMsg);
$this->info("微信用户 [{$user->username}] 已通过微信机器人成功重置了密码");
}
/**
+115
View File
@@ -0,0 +1,115 @@
<?php
/**
* 文件功能:微信机器人 Kafka 消息消费及密码重置测试
*
* 覆盖在微信上对助手机器人发送重置密码的拦截逻辑,
* 验证对未绑定账号及已绑定账号的回复行为和密码更新结果。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace Tests\Feature;
use App\Models\User;
use App\Services\WechatBot\KafkaConsumerService;
use App\Services\WechatBot\WechatBotApiService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Mockery\MockInterface;
use Tests\TestCase;
/**
* 验证 ConsumeWechatMessages 控制台消费命令的解析与动作路由行为。
*/
class ConsumeWechatMessagesTest extends TestCase
{
use RefreshDatabase;
/**
* 当微信用户未绑定任何聊天室账户时发送“重置密码”,回复未绑定指引微信。
*/
public function test_wechat_password_reset_fails_when_not_bound(): void
{
$apiServiceMock = $this->mock(WechatBotApiService::class, function (MockInterface $mock) {
$mock->shouldReceive('sendTextMessage')
->once()
->with('wxid_test_user_123', \Mockery::on(function ($arg) {
return str_contains($arg, '密码重置失败') && str_contains($arg, '尚未绑定');
}))
->andReturn([]);
});
$msg = [
'msg_type' => 1,
'content' => '重置密码',
'from_user' => 'wxid_test_user_123',
'is_chatroom' => false,
];
$command = new \App\Console\Commands\ConsumeWechatMessages(
$this->createMock(KafkaConsumerService::class)
);
$command->setLaravel($this->app);
// 绑定 NullOutput 避免 $this->info 报 null 指针错误
$output = new \Symfony\Component\Console\Output\NullOutput;
$reflectorOutput = new \ReflectionProperty($command, 'output');
$reflectorOutput->setValue($command, new \Illuminate\Console\OutputStyle(
new \Symfony\Component\Console\Input\ArrayInput([]),
$output
));
$reflector = new \ReflectionMethod($command, 'processMessage');
$reflector->invoke($command, $msg, $apiServiceMock);
}
/**
* 当已绑定微信的用户在微信私聊中发送“重置密码”时,自动随机生成 8 位密码并完成更新,通过微信私信回发。
*/
public function test_wechat_password_reset_succeeds_when_bound(): void
{
$user = User::factory()->create([
'username' => 'test_bind_user',
'wxid' => 'wxid_test_user_123',
'password' => Hash::make('old_password_123'),
]);
$apiServiceMock = $this->mock(WechatBotApiService::class, function (MockInterface $mock) use ($user) {
$mock->shouldReceive('sendTextMessage')
->once()
->with('wxid_test_user_123', \Mockery::on(function ($arg) use ($user) {
return str_contains($arg, '密码重置成功') && str_contains($arg, $user->username);
}))
->andReturn([]);
});
$msg = [
'msg_type' => 1,
'content' => ' 想要重置密码呢 ',
'from_user' => 'wxid_test_user_123',
'is_chatroom' => false,
];
$command = new \App\Console\Commands\ConsumeWechatMessages(
$this->createMock(KafkaConsumerService::class)
);
$command->setLaravel($this->app);
// 绑定 NullOutput
$output = new \Symfony\Component\Console\Output\NullOutput;
$reflectorOutput = new \ReflectionProperty($command, 'output');
$reflectorOutput->setValue($command, new \Illuminate\Console\OutputStyle(
new \Symfony\Component\Console\Input\ArrayInput([]),
$output
));
$reflector = new \ReflectionMethod($command, 'processMessage');
$reflector->invoke($command, $msg, $apiServiceMock);
$user->refresh();
$this->assertFalse(Hash::check('old_password_123', $user->password));
}
}