mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-11-10 21:10:50 +08:00
feat(notice): NoticeSended 事件与登录提醒内置监听
This commit is contained in:
9
app/common/event/AdminLoginSuccess/NoticeEvent.php
Normal file
9
app/common/event/AdminLoginSuccess/NoticeEvent.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\event\AdminLoginSuccess;
|
||||
|
||||
use base\common\event\AdminLoginSuccess\NoticeEventBase;
|
||||
|
||||
class NoticeEvent extends NoticeEventBase
|
||||
{
|
||||
}
|
||||
9
app/common/event/NoticeSended/LogEvent.php
Normal file
9
app/common/event/NoticeSended/LogEvent.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\event\NoticeSended;
|
||||
|
||||
use base\common\event\NoticeSended\LogEventBase;
|
||||
|
||||
class LogEvent extends LogEventBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\event\AdminLoginSuccess;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\common\service\NoticeService;
|
||||
use think\facade\Log;
|
||||
|
||||
class NoticeEventBase
|
||||
{
|
||||
public function handle(SystemAdmin $admin)
|
||||
{
|
||||
try {
|
||||
// 登录提醒场景默认关闭:无配置行(null 强转 0)不发,DB value 为字符串须强转 int
|
||||
if ((int) sysconfig('notify', 'notify_login_scene_enable') === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 不传 channels=按场景注册渠道展开(admin_login=[inmail,email]),再经各管理员订阅矩阵过滤
|
||||
NoticeService::send([$admin->id], '登录提醒', "账号 {$admin->username} 于 ".date('Y-m-d H:i').' 登录成功', ['scene' => 'admin_login']);
|
||||
} catch (\Throwable $e) {
|
||||
// 通知失败不得影响登录主流程
|
||||
Log::error('admin login notice failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
20
extend/base/common/event/NoticeSended/LogEventBase.php
Normal file
20
extend/base/common/event/NoticeSended/LogEventBase.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\event\NoticeSended;
|
||||
|
||||
use think\facade\Log;
|
||||
|
||||
class LogEventBase
|
||||
{
|
||||
public function handle(array $payload)
|
||||
{
|
||||
// 事件监听处理:payload = [$notice(模型), $targets(投递行数组)](NoticeServiceBase::send trigger 形态)
|
||||
[$notice, $targets] = $payload;
|
||||
|
||||
Log::info('notice sended', [
|
||||
'notice_id' => (int) $notice->id,
|
||||
'target_count' => count($targets),
|
||||
'channels' => array_values(array_unique(array_column($targets, 'channel'))),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -45,8 +45,10 @@ use app\common\command\tools\log\ToolsLogShow;
|
||||
use app\common\command\tools\log\ToolsLogStats;
|
||||
use app\common\command\Test;
|
||||
use app\common\event\AdminLoginSuccess\LogEvent;
|
||||
use app\common\event\AdminLoginSuccess\NoticeEvent;
|
||||
use app\common\event\AdminLoginType\DemoEvent;
|
||||
use app\common\event\AdminMenuTab\DemoEvent as AdminMenuTabDemoEvent;
|
||||
use app\common\event\NoticeSended\LogEvent as NoticeSendedLogEvent;
|
||||
use app\common\provider\ExceptionHandle;
|
||||
use app\common\provider\Request;
|
||||
use app\common\provider\View;
|
||||
@@ -68,6 +70,7 @@ class UlthonAdminService extends Service
|
||||
'LogWrite' => [],
|
||||
'AdminLoginSuccess' => [
|
||||
LogEvent::class,
|
||||
NoticeEvent::class,
|
||||
],
|
||||
'AdminLoginType' => [
|
||||
DemoEvent::class,
|
||||
@@ -75,6 +78,9 @@ class UlthonAdminService extends Service
|
||||
'AdminMenuTab' => [
|
||||
AdminMenuTabDemoEvent::class,
|
||||
],
|
||||
'NoticeSended' => [
|
||||
NoticeSendedLogEvent::class,
|
||||
],
|
||||
];
|
||||
|
||||
$this->app->event->listenEvents($event_listen);
|
||||
|
||||
163
tests/Feature/NoticeEventTest.php
Normal file
163
tests/Feature/NoticeEventTest.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests\Feature;
|
||||
|
||||
use app\admin\model\SystemAdmin;
|
||||
use app\common\event\AdminLoginSuccess\NoticeEvent;
|
||||
use app\common\service\NoticeService;
|
||||
use app\common\test\TestCase;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Db;
|
||||
use think\facade\Event;
|
||||
|
||||
/**
|
||||
* 事件层集成测试.
|
||||
*
|
||||
* 覆盖:
|
||||
* 1. send 触发 NoticeSended 事件,payload = [$notice(模型), $targets(投递行数组)]
|
||||
* 2. 登录提醒开关开启时 NoticeEvent 落 admin_login 通知(inmail 行直接成功终态)
|
||||
* 3. 开关默认关闭(无配置行)时不发
|
||||
*
|
||||
* fixture 均为事务内自造数据,tearDown 自动回滚;sysconfig 缓存显式清理
|
||||
* (Cache 不受事务回滚影响,非 '0' 值会缓存 3600 秒)。
|
||||
*/
|
||||
class NoticeEventTest extends TestCase
|
||||
{
|
||||
// =========================================================================
|
||||
// 1. NoticeSended 事件触发
|
||||
// =========================================================================
|
||||
|
||||
public function test_send_triggers_notice_sended_event_with_notice_and_targets(): void
|
||||
{
|
||||
$adminId = $this->createAdmin();
|
||||
|
||||
// 临时注入收集器监听器(容器内与框架注册的 NoticeSended LogEvent 并存,互不影响)
|
||||
$captured = [];
|
||||
Event::listen('NoticeSended', function ($payload) use (&$captured) {
|
||||
$captured[] = $payload;
|
||||
});
|
||||
|
||||
$noticeId = NoticeService::send([$adminId], '事件测试', '内容');
|
||||
|
||||
$this->assertCount(1, $captured, 'send 应触发一次 NoticeSended 事件');
|
||||
[$notice, $targets] = $captured[0];
|
||||
$this->assertSame($noticeId, (int) $notice->id, 'payload[0] 应为本次创建的 notice 模型');
|
||||
$this->assertIsArray($targets, 'payload[1] 应为投递行数组');
|
||||
$this->assertCount(1, $targets);
|
||||
$this->assertSame($adminId, (int) $targets[0]['admin_id']);
|
||||
$this->assertSame('inmail', $targets[0]['channel']);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 2. 登录提醒开关开启:NoticeEvent 落 admin_login 通知
|
||||
// =========================================================================
|
||||
|
||||
public function test_login_notice_sent_when_scene_switch_on(): void
|
||||
{
|
||||
$adminId = $this->createAdmin(['username' => 'lgn_' . uniqid()]);
|
||||
$admin = SystemAdmin::find($adminId);
|
||||
$this->assertNotNull($admin, 'SystemAdmin 模型应能取到监听器入参形态');
|
||||
|
||||
$this->setLoginSceneSwitch('1');
|
||||
|
||||
(new NoticeEvent())->handle($admin);
|
||||
|
||||
$row = Db::name('system_notice')
|
||||
->where('scene', 'admin_login')
|
||||
->where('send_admin_id', 0)
|
||||
->find();
|
||||
$this->assertNotNull($row, '开关开启时应产生 admin_login 通知主体');
|
||||
$this->assertSame('登录提醒', $row['title']);
|
||||
// 文案含登录时间(Y-m-d H:i),跨分钟竞态下按正则断言而非逐字相等
|
||||
$pattern = '/^账号 ' . preg_quote((string) $admin->username, '/') . ' 于 \d{4}-\d{2}-\d{2} \d{2}:\d{2} 登录成功$/u';
|
||||
$this->assertMatchesRegularExpression($pattern, (string) $row['content']);
|
||||
|
||||
// inmail 行直接成功终态(无 email 地址的管理员 email 渠道被跳过)
|
||||
$this->assertDatabaseHas('system_notice_target', [
|
||||
'notice_id' => $row['id'],
|
||||
'admin_id' => $adminId,
|
||||
'channel' => 'inmail',
|
||||
'status' => 2,
|
||||
]);
|
||||
$this->assertSame(
|
||||
1,
|
||||
Db::name('system_notice_target')->where('notice_id', $row['id'])->count(),
|
||||
'无 email 管理员应仅展开 inmail 一行'
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 3. 登录提醒开关默认关闭
|
||||
// =========================================================================
|
||||
|
||||
public function test_login_notice_skipped_when_scene_switch_absent(): void
|
||||
{
|
||||
$adminId = $this->createAdmin(['username' => 'lgn_' . uniqid()]);
|
||||
$admin = SystemAdmin::find($adminId);
|
||||
|
||||
// 无配置行 = 默认关;清理可能被前序用例缓存的非 '0' 值
|
||||
$this->clearLoginSceneSwitch();
|
||||
|
||||
(new NoticeEvent())->handle($admin);
|
||||
|
||||
$this->assertSame(
|
||||
0,
|
||||
Db::name('system_notice')->where('scene', 'admin_login')->where('send_admin_id', 0)->count(),
|
||||
'开关缺省(无配置行)时不应产生 admin_login 通知'
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// fixture 工厂(事务内运行,tearDown 自动回滚)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 造启用管理员.
|
||||
*/
|
||||
private function createAdmin(array $overrides = []): int
|
||||
{
|
||||
$data = array_merge([
|
||||
'username' => 'evt_' . uniqid(),
|
||||
'password' => 'not-a-real-password',
|
||||
'nickname' => '事件测试用户',
|
||||
'email' => '',
|
||||
'status' => 1,
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
'delete_time' => 0,
|
||||
], $overrides);
|
||||
|
||||
return (int) Db::name('system_admin')->insertGetId($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写 notify 组登录提醒开关配置行并清缓存(缓存不受事务回滚影响).
|
||||
*/
|
||||
private function setLoginSceneSwitch(string $value): void
|
||||
{
|
||||
Db::name('system_config')->insert([
|
||||
'name' => 'notify_login_scene_enable',
|
||||
'group' => 'notify',
|
||||
'value' => $value,
|
||||
'remark' => 'NoticeEventTest fixture',
|
||||
'sort' => 100,
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
Cache::tag('sysconfig')->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 notify 组登录提醒开关配置行并清缓存.
|
||||
*/
|
||||
private function clearLoginSceneSwitch(): void
|
||||
{
|
||||
Db::name('system_config')
|
||||
->where('group', 'notify')
|
||||
->where('name', 'notify_login_scene_enable')
|
||||
->delete();
|
||||
Cache::tag('sysconfig')->clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user