聊天室管理权限统一为职务权限

This commit is contained in:
2026-04-26 20:55:11 +08:00
parent b07f4e971a
commit 0402097b59
21 changed files with 590 additions and 395 deletions
+115 -27
View File
@@ -4,7 +4,7 @@
* 文件功能:管理员聊天室实时命令控制器
*
* 提供管理员在聊天室内对用户执行的管理操作:
* 警告(=J)、踢出(=T)、禁言(=B)冻结(=Y)、查看私信(=S)、职务公屏讲话。
* 警告(=J)、踢出(=T)、禁言(=B)封号、封IP、查看私信(=S)、职务公屏讲话。
*
* 对应原 ASP 文件:DOUSER.ASP / KILLUSER.ASP / LOCKIP.ASP / NEWSAY.ASP
*
@@ -278,14 +278,14 @@ class AdminCommandController extends Controller
}
/**
* 冻结用户账号=Y 理由)
* 封禁用户账号
*
* 用户账号状态设为冻结,禁止登录
* 目标账号设为封禁状态,并将其从当前在线房间中移出
*
* @param Request $request 请求对象,需包含 username, reason
* @param Request $request 请求对象,需包含 username, room_id, reason
* @return JsonResponse 操作结果
*/
public function freeze(Request $request): JsonResponse
public function ban(Request $request): JsonResponse
{
$request->validate([
'username' => 'required|string',
@@ -301,12 +301,12 @@ class AdminCommandController extends Controller
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
}
$reason = ChatContentSanitizer::htmlText($request->input('reason', '违反聊天室规则'));
$reason = ChatContentSanitizer::htmlText($request->input('reason', '严重违规'));
$safeTargetUsername = ChatContentSanitizer::htmlText($targetUsername);
$operatorDisplay = $this->buildOperatorDisplayHtml($admin);
// 权限检查:必须拥有职务权限,且不能处理职务高于自己的用户。
$authorization = $this->authorizeModerationAction($admin, $targetUsername, PositionPermissionRegistry::USER_FREEZE, '冻结');
$authorization = $this->authorizeModerationAction($admin, $targetUsername, PositionPermissionRegistry::USER_BAN, '封禁');
if (! $authorization['ok']) {
return response()->json(['status' => 'error', 'message' => $authorization['message']], 403);
}
@@ -316,37 +316,30 @@ class AdminCommandController extends Controller
return response()->json(['status' => 'error', 'message' => $targetAuthorization['message']], 403);
}
// 冻结用户账号(将等级设为 -1 表示冻结)
$target = $authorization['target'];
$target->user_level = -1;
$target->save();
// 先给被冻结用户补发私聊提示,再将其移出各房间并强制下线。
$this->pushTargetToastMessage(
roomId: (int) $roomId,
roomId: $roomId,
targetUsername: $targetUsername,
content: "🧊 {$operatorDisplay}冻结你的账号。原因:{$reason}",
title: '🧊 账号已冻结',
toastMessage: "{$operatorDisplay}冻结你的账号。<br>原因:{$reason}",
color: '#3b82f6',
icon: '🧊',
content: " {$operatorDisplay}封禁你的账号。原因:{$reason}",
title: ' 账号已封禁',
toastMessage: "{$operatorDisplay}封禁你的账号。<br>原因:{$reason}",
color: '#991b1b',
icon: '',
);
// 从所有房间移除
$rooms = $this->chatState->getUserRooms($targetUsername);
foreach ($rooms as $rid) {
$this->chatState->userLeave($rid, $targetUsername);
}
$this->removeUserFromAllRooms($targetUsername);
// 广播冻结消息
$msg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => "🧊 {$operatorDisplay}冻结 <b>{$safeTargetUsername}</b> 的账号。原因:{$reason}",
'content' => " {$operatorDisplay}封禁 <b>{$safeTargetUsername}</b> 的账号。原因:{$reason}",
'is_secret' => false,
'font_color' => '#dc2626',
'font_color' => '#991b1b',
'action' => '',
'sent_at' => now()->toDateTimeString(),
];
@@ -354,10 +347,93 @@ class AdminCommandController extends Controller
broadcast(new MessageSent($roomId, $msg));
SaveMessageJob::dispatch($msg);
// 广播踢出事件
broadcast(new \App\Events\UserKicked($roomId, $targetUsername, "账号已被冻结:{$reason}"));
broadcast(new \App\Events\UserKicked($roomId, $targetUsername, "账号已被封禁:{$reason}"));
return response()->json(['status' => 'success', 'message' => "冻结 {$targetUsername} 的账号"]);
return response()->json(['status' => 'success', 'message' => "封禁 {$targetUsername} 的账号"]);
}
/**
* 封禁用户 IP。
*
* 将目标账号设为封禁状态并把最近登录 IP 写入黑名单。
*
* @param Request $request 请求对象,需包含 username, room_id, reason
* @return JsonResponse 操作结果
*/
public function banIp(Request $request): JsonResponse
{
$request->validate([
'username' => 'required|string',
'room_id' => 'required|integer',
'reason' => 'nullable|string|max:200',
]);
$admin = Auth::user();
$targetUsername = $request->input('username');
$roomId = (int) $request->input('room_id');
$roomAuthorization = $this->authorizeManagedRoom($roomId, $admin);
if (! $roomAuthorization['ok']) {
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
}
$reason = ChatContentSanitizer::htmlText($request->input('reason', '严重违规'));
$safeTargetUsername = ChatContentSanitizer::htmlText($targetUsername);
$operatorDisplay = $this->buildOperatorDisplayHtml($admin);
// 权限检查:必须拥有职务权限,且不能处理职务高于自己的用户。
$authorization = $this->authorizeModerationAction($admin, $targetUsername, PositionPermissionRegistry::USER_BANIP, '封禁 IP ');
if (! $authorization['ok']) {
return response()->json(['status' => 'error', 'message' => $authorization['message']], 403);
}
$targetAuthorization = $this->authorizeTargetOnlineInRoom($roomId, $targetUsername);
if (! $targetAuthorization['ok']) {
return response()->json(['status' => 'error', 'message' => $targetAuthorization['message']], 403);
}
$target = $authorization['target'];
$targetIp = (string) ($target->last_ip ?? '');
if ($targetIp !== '') {
Redis::sadd('banned_ips', $targetIp);
}
$target->user_level = -1;
$target->save();
$this->pushTargetToastMessage(
roomId: $roomId,
targetUsername: $targetUsername,
content: "🌐 {$operatorDisplay} 已封禁你的 IP 并冻结账号。原因:{$reason}",
title: '🌐 IP 已封禁',
toastMessage: "{$operatorDisplay} 已封禁你的 IP 并冻结账号。<br>原因:{$reason}",
color: '#7c2d12',
icon: '🌐',
);
$this->removeUserFromAllRooms($targetUsername);
$ipSuffix = $targetIp !== '' ? "IP{$targetIp}" : '(未记录有效 IP';
$msg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => "🌐 {$operatorDisplay} 已封禁 <b>{$safeTargetUsername}</b> 的 IP 并冻结账号。原因:{$reason} {$ipSuffix}",
'is_secret' => false,
'font_color' => '#9a3412',
'action' => '',
'sent_at' => now()->toDateTimeString(),
];
$this->chatState->pushMessage($roomId, $msg);
broadcast(new MessageSent($roomId, $msg));
SaveMessageJob::dispatch($msg);
broadcast(new \App\Events\UserKicked($roomId, $targetUsername, "IP 已被封禁:{$reason}"));
return response()->json([
'status' => 'success',
'message' => $targetIp !== '' ? "已封禁 {$targetUsername} 的 IP 与账号" : "已封禁 {$targetUsername} 的账号,但未记录可封禁 IP",
]);
}
/**
@@ -555,7 +631,7 @@ class AdminCommandController extends Controller
string $permissionCode,
string $actionLabel,
): array {
if (! $this->positionPermissionService->hasPermission($admin, $permissionCode)) {
if ($admin->id !== 1 && ! $this->positionPermissionService->hasPermission($admin, $permissionCode)) {
return ['ok' => false, 'message' => "当前职务无权{$actionLabel}用户"];
}
@@ -583,6 +659,18 @@ class AdminCommandController extends Controller
return ['ok' => true, 'message' => '校验通过', 'target' => $target];
}
/**
* 将目标用户从当前在线的全部房间中移除。
*/
private function removeUserFromAllRooms(string $targetUsername): void
{
$rooms = $this->chatState->getUserRooms($targetUsername);
foreach ($rooms as $rid) {
$this->chatState->userLeave((int) $rid, $targetUsername);
}
}
/**
* 判断操作者是否可以按职务位阶处理目标用户。
*