mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-11-08 20: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\tools\log\ToolsLogStats;
|
||||||
use app\common\command\Test;
|
use app\common\command\Test;
|
||||||
use app\common\event\AdminLoginSuccess\LogEvent;
|
use app\common\event\AdminLoginSuccess\LogEvent;
|
||||||
|
use app\common\event\AdminLoginSuccess\NoticeEvent;
|
||||||
use app\common\event\AdminLoginType\DemoEvent;
|
use app\common\event\AdminLoginType\DemoEvent;
|
||||||
use app\common\event\AdminMenuTab\DemoEvent as AdminMenuTabDemoEvent;
|
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\ExceptionHandle;
|
||||||
use app\common\provider\Request;
|
use app\common\provider\Request;
|
||||||
use app\common\provider\View;
|
use app\common\provider\View;
|
||||||
@@ -68,6 +70,7 @@ class UlthonAdminService extends Service
|
|||||||
'LogWrite' => [],
|
'LogWrite' => [],
|
||||||
'AdminLoginSuccess' => [
|
'AdminLoginSuccess' => [
|
||||||
LogEvent::class,
|
LogEvent::class,
|
||||||
|
NoticeEvent::class,
|
||||||
],
|
],
|
||||||
'AdminLoginType' => [
|
'AdminLoginType' => [
|
||||||
DemoEvent::class,
|
DemoEvent::class,
|
||||||
@@ -75,6 +78,9 @@ class UlthonAdminService extends Service
|
|||||||
'AdminMenuTab' => [
|
'AdminMenuTab' => [
|
||||||
AdminMenuTabDemoEvent::class,
|
AdminMenuTabDemoEvent::class,
|
||||||
],
|
],
|
||||||
|
'NoticeSended' => [
|
||||||
|
NoticeSendedLogEvent::class,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->app->event->listenEvents($event_listen);
|
$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