feat: 添加 user_update hook 事件并重构用户生命周期 hook

- 新增 user_update 事件,当用户基本信息变更时触发
  - 扩展 dispatchUserHook payload 包含完整用户信息(tel、profession、birthday、address、introduction、departments)
  - 将 user_onboard/user_offboard/user_update hook 触发逻辑集中到 UserObserver
  - 区分 profile_update(用户自己修改)和 admin_update(管理员修改)事件类型
  - 修复 User::reg() 中 Manticore 索引同步遗漏问题
  - 排除机器人账号的 hook 触发
This commit is contained in:
kuaifan 2026-01-08 11:31:16 +00:00
parent 7f9c42d3d8
commit 1ac3a4cc96
4 changed files with 104 additions and 24 deletions

View File

@ -37,7 +37,6 @@ use App\Models\UserRecentItem;
use App\Models\UserTag;
use App\Models\UserTagRecognition;
use App\Models\UserAppSort;
use App\Module\Apps;
use Illuminate\Support\Facades\DB;
use App\Models\UserEmailVerification;
use App\Module\AgoraIO\AgoraTokenGenerator;
@ -1104,8 +1103,6 @@ class UsersController extends AbstractController
$upArray = [];
$upLdap = [];
$transferUser = null;
$hookAction = '';
$hookEvent = '';
switch ($type) {
case 'setadmin':
$msg = '设置成功';
@ -1187,16 +1184,12 @@ class UsersController extends AbstractController
return Base::retError('交接人已离职,请选择另一个交接人');
}
}
$hookAction = 'user_offboard';
$hookEvent = 'offboard';
break;
case 'cleardisable':
$msg = '操作成功';
$upArray['identity'] = array_diff($userInfo->identity, ['disable']);
$upArray['disable_at'] = null;
$hookAction = 'user_onboard';
$hookEvent = 'restore';
break;
case 'delete':
@ -1315,9 +1308,6 @@ class UsersController extends AbstractController
}
});
}
if ($hookAction) {
Apps::dispatchUserHook($userInfo, $hookAction, $hookEvent);
}
//
return Base::retSuccess($msg, $userInfo);
}

View File

@ -7,7 +7,9 @@ use App\Module\Base;
use App\Module\Doo;
use App\Module\Apps;
use App\Module\Table\OnlineData;
use App\Observers\AbstractObserver;
use App\Services\RequestContext;
use App\Tasks\ManticoreSyncTask;
use Cache;
use Carbon\Carbon;
@ -335,9 +337,6 @@ class User extends AbstractModel
//
return $this->delete();
});
if ($ret) {
Apps::dispatchUserHook($this, 'user_offboard', 'delete');
}
return $ret;
}
@ -413,7 +412,12 @@ class User extends AbstractModel
}
}
$createdUser = $user->find($user->userid);
Apps::dispatchUserHook($createdUser, 'user_onboard', 'onboard');
if (!$createdUser->bot) {
// Manticore 索引同步
AbstractObserver::taskDeliver(new ManticoreSyncTask('user_sync', $createdUser->toArray()));
// 触发 user_onboard hook
Apps::dispatchUserHook($createdUser, 'user_onboard', 'onboard');
}
return $createdUser;
}

View File

@ -4,6 +4,7 @@ namespace App\Module;
use App\Exceptions\ApiException;
use App\Models\User;
use App\Models\UserDepartment;
use App\Services\RequestContext;
use Symfony\Component\Yaml\Yaml;
use App\Module\Base;
@ -62,9 +63,14 @@ class Apps
}
/**
* Dispatch user lifecycle hook to appstore (onboard/offboard/delete/restore).
* Dispatch user lifecycle hook to appstore (user_onboard/user_offboard/user_update).
*
* @param User $user 用户对象
* @param string $action Hook 动作: user_onboard, user_offboard, user_update
* @param string $eventType 事件类型: onboard, restore, offboarded, delete, profile_update, admin_update
* @param array $changedFields 变更字段列表(仅 user_update 时有值)
*/
public static function dispatchUserHook(User $user, string $action, string $eventType = ''): void
public static function dispatchUserHook(User $user, string $action, string $eventType = '', array $changedFields = []): void
{
$appKey = env('APP_KEY', '');
if (empty($appKey)) {
@ -72,18 +78,40 @@ class Apps
return;
}
// 获取用户部门信息
$departments = [];
if (!empty($user->department)) {
$deptIds = is_array($user->department)
? $user->department
: array_filter(explode(',', $user->department));
if (!empty($deptIds)) {
$deptList = UserDepartment::whereIn('id', $deptIds)->get(['id', 'name']);
foreach ($deptList as $dept) {
$departments[] = [
'id' => (string) $dept->id,
'name' => (string) $dept->name,
];
}
}
}
$url = sprintf('http://appstore/api/v1/internal/hooks/%s', $action);
$payload = [
'user' => [
'id' => (string) $user->userid,
'email' => (string) $user->email,
'name' => (string) $user->nickname,
'role' => in_array('admin', $user->identity ?? []) ? 'admin' : 'normal',
'role' => $user->isAdmin() ? 'admin' : 'normal',
'tel' => (string) ($user->tel ?? ''),
'profession' => (string) ($user->profession ?? ''),
'birthday' => $user->birthday ? (string) $user->birthday : '',
'address' => (string) ($user->address ?? ''),
'introduction' => (string) ($user->introduction ?? ''),
'departments' => $departments,
],
'event_type' => $eventType,
'changed_fields' => $changedFields,
];
if ($eventType !== '') {
$payload['event_type'] = $eventType;
}
$headers = [
'Content-Type' => 'application/json',

View File

@ -3,10 +3,26 @@
namespace App\Observers;
use App\Models\User;
use App\Module\Apps;
use App\Tasks\ManticoreSyncTask;
class UserObserver extends AbstractObserver
{
/**
* 搜索相关字段Manticore 同步)
*/
private static array $searchableFields = [
'nickname', 'email', 'profession', 'introduction', 'disable_at'
];
/**
* 需要监控并触发 user_update hook 的字段
*/
private static array $hookMonitoredFields = [
'email', 'tel', 'nickname', 'profession',
'birthday', 'address', 'introduction', 'department'
];
/**
* Handle the User "created" event.
*
@ -30,15 +46,14 @@ class UserObserver extends AbstractObserver
*/
public function updated(User $user)
{
// 机器人账号不同步
// 机器人账号不处理
if ($user->bot) {
return;
}
// 检查是否有搜索相关字段变化
$searchableFields = ['nickname', 'email', 'profession', 'introduction', 'disable_at'];
// 检查是否有搜索相关字段变化Manticore 同步)
$isDirty = false;
foreach ($searchableFields as $field) {
foreach (self::$searchableFields as $field) {
if ($user->isDirty($field)) {
$isDirty = true;
break;
@ -53,6 +68,43 @@ class UserObserver extends AbstractObserver
self::taskDeliver(new ManticoreSyncTask('user_sync', $user->toArray()));
}
}
// 检测 onboard/offboard 场景disable_at 变化)
if ($user->isDirty('disable_at')) {
$originalDisableAt = $user->getOriginal('disable_at');
$currentDisableAt = $user->disable_at;
if ($originalDisableAt && !$currentDisableAt) {
// disable_at 从有值变为 null → 取消离职 (restore)
Apps::dispatchUserHook($user, 'user_onboard', 'restore');
} elseif (!$originalDisableAt && $currentDisableAt) {
// disable_at 从 null 变为有值 → 离职 (offboarded)
Apps::dispatchUserHook($user, 'user_offboard', 'offboarded');
}
return;
}
// 排除仅 identity 变化的场景
if ($user->isDirty('identity')) {
return;
}
// 检测监控字段变更,触发 user_update hook
$changedFields = [];
foreach (self::$hookMonitoredFields as $field) {
if ($user->isDirty($field)) {
$changedFields[] = $field;
}
}
if (!empty($changedFields)) {
// 判断是用户自己修改还是管理员修改
$currentUser = User::authInfo();
$eventType = ($currentUser && $currentUser->userid === $user->userid)
? 'profile_update'
: 'admin_update';
Apps::dispatchUserHook($user, 'user_update', $eventType, $changedFields);
}
}
/**
@ -63,7 +115,13 @@ class UserObserver extends AbstractObserver
*/
public function deleted(User $user)
{
// Manticore 索引删除
self::taskDeliver(new ManticoreSyncTask('user_delete', ['userid' => $user->userid]));
// 触发 user_offboard (delete) hook
if (!$user->bot) {
Apps::dispatchUserHook($user, 'user_offboard', 'delete');
}
}
}