收口聊天室安全边界并优化特效生命周期
This commit is contained in:
@@ -22,11 +22,13 @@ use App\Events\MessageSent;
|
||||
use App\Jobs\SaveMessageJob;
|
||||
use App\Models\Message;
|
||||
use App\Models\PositionAuthorityLog;
|
||||
use App\Models\Room;
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\PositionPermissionService;
|
||||
use App\Services\UserCurrencyService;
|
||||
use App\Support\ChatContentSanitizer;
|
||||
use App\Support\PositionPermissionRegistry;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -66,8 +68,14 @@ class AdminCommandController extends Controller
|
||||
|
||||
$admin = Auth::user();
|
||||
$targetUsername = $request->input('username');
|
||||
$roomId = $request->input('room_id');
|
||||
$reason = $request->input('reason', '请注意言行');
|
||||
$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);
|
||||
|
||||
// 权限检查:必须拥有职务权限,且不能处理职务高于自己的用户。
|
||||
@@ -76,13 +84,18 @@ class AdminCommandController extends Controller
|
||||
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);
|
||||
}
|
||||
|
||||
// 广播警告消息
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => '大家',
|
||||
'content' => "⚠️ {$operatorDisplay} 警告 <b>{$targetUsername}</b>:{$reason}",
|
||||
'content' => "⚠️ {$operatorDisplay} 警告 <b>{$safeTargetUsername}</b>:{$reason}",
|
||||
'is_secret' => false,
|
||||
'font_color' => '#dc2626',
|
||||
'action' => '',
|
||||
@@ -124,8 +137,14 @@ class AdminCommandController extends Controller
|
||||
|
||||
$admin = Auth::user();
|
||||
$targetUsername = $request->input('username');
|
||||
$roomId = $request->input('room_id');
|
||||
$reason = $request->input('reason', '违反聊天室规则');
|
||||
$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);
|
||||
|
||||
// 权限检查:必须拥有职务权限,且不能处理职务高于自己的用户。
|
||||
@@ -134,6 +153,11 @@ class AdminCommandController extends Controller
|
||||
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);
|
||||
}
|
||||
|
||||
// 在强制踢出前补发目标私聊提示,尽量让对方先看到 toast。
|
||||
$this->pushTargetToastMessage(
|
||||
roomId: (int) $roomId,
|
||||
@@ -154,7 +178,7 @@ class AdminCommandController extends Controller
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => '大家',
|
||||
'content' => "🚫 {$operatorDisplay} 已将 <b>{$targetUsername}</b> 踢出聊天室。原因:{$reason}",
|
||||
'content' => "🚫 {$operatorDisplay} 已将 <b>{$safeTargetUsername}</b> 踢出聊天室。原因:{$reason}",
|
||||
'is_secret' => false,
|
||||
'font_color' => '#dc2626',
|
||||
'action' => '',
|
||||
@@ -188,8 +212,14 @@ class AdminCommandController extends Controller
|
||||
|
||||
$admin = Auth::user();
|
||||
$targetUsername = $request->input('username');
|
||||
$roomId = $request->input('room_id');
|
||||
$roomId = (int) $request->input('room_id');
|
||||
$roomAuthorization = $this->authorizeManagedRoom($roomId, $admin);
|
||||
if (! $roomAuthorization['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
|
||||
}
|
||||
|
||||
$duration = $request->input('duration');
|
||||
$safeTargetUsername = ChatContentSanitizer::htmlText($targetUsername);
|
||||
$operatorDisplay = $this->buildOperatorDisplayHtml($admin);
|
||||
$operatorLabel = $this->buildOperatorIdentityLabel($admin).' '.$admin->username;
|
||||
|
||||
@@ -199,6 +229,11 @@ class AdminCommandController extends Controller
|
||||
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);
|
||||
}
|
||||
|
||||
// 设置 Redis 禁言标记,TTL 自动过期
|
||||
$muteKey = "mute:{$roomId}:{$targetUsername}";
|
||||
Redis::setex($muteKey, $duration * 60, now()->toDateTimeString());
|
||||
@@ -209,7 +244,7 @@ class AdminCommandController extends Controller
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => '大家',
|
||||
'content' => "🔇 {$operatorDisplay} 已将 <b>{$targetUsername}</b> 禁言 {$duration} 分钟。",
|
||||
'content' => "🔇 {$operatorDisplay} 已将 <b>{$safeTargetUsername}</b> 禁言 {$duration} 分钟。",
|
||||
'is_secret' => false,
|
||||
'font_color' => '#dc2626',
|
||||
'action' => '',
|
||||
@@ -260,8 +295,14 @@ class AdminCommandController extends Controller
|
||||
|
||||
$admin = Auth::user();
|
||||
$targetUsername = $request->input('username');
|
||||
$roomId = $request->input('room_id');
|
||||
$reason = $request->input('reason', '违反聊天室规则');
|
||||
$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);
|
||||
|
||||
// 权限检查:必须拥有职务权限,且不能处理职务高于自己的用户。
|
||||
@@ -270,6 +311,11 @@ class AdminCommandController extends Controller
|
||||
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);
|
||||
}
|
||||
|
||||
// 冻结用户账号(将等级设为 -1 表示冻结)
|
||||
$target = $authorization['target'];
|
||||
$target->user_level = -1;
|
||||
@@ -298,7 +344,7 @@ class AdminCommandController extends Controller
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => '大家',
|
||||
'content' => "🧊 {$operatorDisplay} 已冻结 <b>{$targetUsername}</b> 的账号。原因:{$reason}",
|
||||
'content' => "🧊 {$operatorDisplay} 已冻结 <b>{$safeTargetUsername}</b> 的账号。原因:{$reason}",
|
||||
'is_secret' => false,
|
||||
'font_color' => '#dc2626',
|
||||
'action' => '',
|
||||
@@ -381,17 +427,23 @@ class AdminCommandController extends Controller
|
||||
return response()->json(['status' => 'error', 'message' => '当前职务无权发布公屏讲话'], 403);
|
||||
}
|
||||
|
||||
$roomId = $request->input('room_id');
|
||||
$content = $request->input('content');
|
||||
$roomId = (int) $request->input('room_id');
|
||||
$roomAuthorization = $this->authorizeManagedRoom($roomId, $admin);
|
||||
if (! $roomAuthorization['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
|
||||
}
|
||||
|
||||
$content = ChatContentSanitizer::htmlText($request->input('content'));
|
||||
|
||||
// 按当前在职职务拼装发布者身份,避免继续显示为固定“站长公告”
|
||||
$publisherLabel = $this->buildAnnouncementPublisherLabel($admin);
|
||||
$publisherLabel = ChatContentSanitizer::htmlText($this->buildAnnouncementPublisherLabel($admin));
|
||||
$publisherUsername = ChatContentSanitizer::htmlText($admin->username);
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统公告',
|
||||
'to_user' => '大家',
|
||||
'content' => "📢 <b>{$publisherLabel}</b> <b>{$admin->username}</b> 发布公告:{$content}",
|
||||
'content' => "📢 <b>{$publisherLabel}</b> <b>{$publisherUsername}</b> 发布公告:{$content}",
|
||||
'is_secret' => false,
|
||||
'font_color' => '#b91c1c',
|
||||
'action' => '',
|
||||
@@ -447,6 +499,44 @@ class AdminCommandController extends Controller
|
||||
return "<b>{$identityLabel}</b> <b>{$username}</b>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验操作者是否可在指定房间执行聊天室管理命令。
|
||||
*
|
||||
* @return array{ok: bool, message: string, room?: Room}
|
||||
*/
|
||||
private function authorizeManagedRoom(int $roomId, User $operator): array
|
||||
{
|
||||
$room = Room::query()->find($roomId);
|
||||
if (! $room) {
|
||||
return ['ok' => false, 'message' => '房间不存在'];
|
||||
}
|
||||
|
||||
if (! $room->canUserEnter($operator)) {
|
||||
return ['ok' => false, 'message' => '无权进入该房间,不能执行管理命令'];
|
||||
}
|
||||
|
||||
// 管理命令只能作用于操作者当前所在房间,防止手工 POST 跨房间操作。
|
||||
if (! $this->chatState->isUserInRoom($roomId, $operator->username)) {
|
||||
return ['ok' => false, 'message' => '请先进入该房间后再执行管理命令'];
|
||||
}
|
||||
|
||||
return ['ok' => true, 'message' => '校验通过', 'room' => $room];
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验目标用户是否仍在线于当前房间。
|
||||
*
|
||||
* @return array{ok: bool, message: string}
|
||||
*/
|
||||
private function authorizeTargetOnlineInRoom(int $roomId, string $targetUsername): array
|
||||
{
|
||||
if (! $this->chatState->isUserInRoom($roomId, $targetUsername)) {
|
||||
return ['ok' => false, 'message' => '目标用户不在当前房间,无法执行该操作'];
|
||||
}
|
||||
|
||||
return ['ok' => true, 'message' => '校验通过'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验聊天室用户管理动作是否可执行。
|
||||
*
|
||||
@@ -548,12 +638,17 @@ class AdminCommandController extends Controller
|
||||
]);
|
||||
|
||||
$admin = Auth::user();
|
||||
$roomId = $request->input('room_id');
|
||||
$roomId = (int) $request->input('room_id');
|
||||
// 改为按职务权限控制聊天室顶部“清屏”按钮。
|
||||
if (! $this->positionPermissionService->hasPermission($admin, PositionPermissionRegistry::ROOM_CLEAR_SCREEN)) {
|
||||
return response()->json(['status' => 'error', 'message' => '当前职务无权执行全员清屏'], 403);
|
||||
}
|
||||
|
||||
$roomAuthorization = $this->authorizeManagedRoom($roomId, $admin);
|
||||
if (! $roomAuthorization['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
|
||||
}
|
||||
|
||||
// 清除 Redis 中该房间的消息缓存
|
||||
$this->chatState->clearMessages($roomId);
|
||||
|
||||
@@ -582,7 +677,12 @@ class AdminCommandController extends Controller
|
||||
}
|
||||
|
||||
$roomId = (int) $request->input('room_id');
|
||||
$reason = trim((string) $request->input('reason', ''));
|
||||
$roomAuthorization = $this->authorizeManagedRoom($roomId, $admin);
|
||||
if (! $roomAuthorization['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
|
||||
}
|
||||
|
||||
$reason = ChatContentSanitizer::htmlText($request->input('reason', ''));
|
||||
|
||||
// 立即广播页面刷新指令,确保在线用户尽快拿到最新前端状态。
|
||||
broadcast(new BrowserRefreshRequested(
|
||||
@@ -614,12 +714,17 @@ class AdminCommandController extends Controller
|
||||
]);
|
||||
|
||||
$admin = Auth::user();
|
||||
$roomId = $request->input('room_id');
|
||||
$roomId = (int) $request->input('room_id');
|
||||
$type = $request->input('type');
|
||||
if (! $this->positionPermissionService->hasPermission($admin, PositionPermissionRegistry::ROOM_FULLSCREEN_EFFECT)) {
|
||||
return response()->json(['status' => 'error', 'message' => '当前职务无权触发特效'], 403);
|
||||
}
|
||||
|
||||
$roomAuthorization = $this->authorizeManagedRoom($roomId, $admin);
|
||||
if (! $roomAuthorization['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
|
||||
}
|
||||
|
||||
// 广播特效事件给房间内所有在线用户
|
||||
broadcast(new EffectBroadcast($roomId, $type, $admin->username));
|
||||
|
||||
@@ -658,6 +763,10 @@ class AdminCommandController extends Controller
|
||||
$roomId = (int) $request->input('room_id');
|
||||
$amount = (int) $request->input('amount');
|
||||
$targetUsername = $request->input('username');
|
||||
$roomAuthorization = $this->authorizeManagedRoom($roomId, $admin);
|
||||
if (! $roomAuthorization['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $roomAuthorization['message']], 403);
|
||||
}
|
||||
|
||||
// 不能给自己发放
|
||||
if ($admin->username === $targetUsername) {
|
||||
@@ -670,12 +779,21 @@ class AdminCommandController extends Controller
|
||||
return response()->json(['status' => 'error', 'message' => '用户不存在'], 404);
|
||||
}
|
||||
|
||||
$targetAuthorization = $this->authorizeTargetOnlineInRoom($roomId, $targetUsername);
|
||||
if (! $targetAuthorization['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $targetAuthorization['message']], 403);
|
||||
}
|
||||
|
||||
// id=1 超级管理员:无需职务,无限额限制
|
||||
$isSuperAdmin = $admin->id === 1;
|
||||
$userPosition = null;
|
||||
$position = null;
|
||||
|
||||
if (! $isSuperAdmin) {
|
||||
if (! $this->positionPermissionService->hasPermission($admin, PositionPermissionRegistry::ROOM_REWARD)) {
|
||||
return response()->json(['status' => 'error', 'message' => '当前职务无权发放奖励'], 403);
|
||||
}
|
||||
|
||||
// ① 必须有在职职务
|
||||
$userPosition = $admin->activePosition;
|
||||
if (! $userPosition) {
|
||||
|
||||
Reference in New Issue
Block a user