Files
chatroom/app/Http/Controllers/AdminCommandController.php
lkddi ba1c94e0f7 优化:管理操作按等级独立显示,查看私信仅站长可用
- 新增 level_warn(警告等级) 和 level_freeze(冻结等级) 系统参数
- AdminCommandController 各操作使用独立等级检查
- 弹窗各按钮按 sysparam 等级条件独立显示/隐藏
- 查看私信(🔍)仅 superlevel 站长可见
- 后台系统参数可配置各操作所需等级
2026-02-26 22:38:33 +08:00

359 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能:管理员聊天室实时命令控制器
*
* 提供管理员在聊天室内对用户执行的管理操作:
* 警告(=J)、踢出(=T)、禁言(=B)、冻结(=Y)、查看私信(=S)、站长公屏讲话。
*
* 对应原 ASP 文件DOUSER.ASP / KILLUSER.ASP / LOCKIP.ASP / NEWSAY.ASP
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers;
use App\Events\MessageSent;
use App\Jobs\SaveMessageJob;
use App\Models\Message;
use App\Models\Sysparam;
use App\Models\User;
use App\Services\ChatStateService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;
class AdminCommandController extends Controller
{
/**
* 构造函数:注入聊天状态服务
*/
public function __construct(
private readonly ChatStateService $chatState,
) {}
/**
* 警告用户(=J 理由)
*
* 向目标用户发送系统警告消息,所有人可见。
*
* @param Request $request 请求对象,需包含 username, room_id, reason
* @return JsonResponse 操作结果
*/
public function warn(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 = $request->input('room_id');
$reason = $request->input('reason', '请注意言行');
// 权限检查(等级由 level_warn 配置)
if (! $this->canExecute($admin, $targetUsername, 'level_warn', '5')) {
return response()->json(['status' => 'error', 'message' => '权限不足'], 403);
}
// 广播警告消息
$msg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => "⚠️ 管理员 <b>{$admin->username}</b> 警告 <b>{$targetUsername}</b>{$reason}",
'is_secret' => false,
'font_color' => '#dc2626',
'action' => '',
'sent_at' => now()->toDateTimeString(),
];
$this->chatState->pushMessage($roomId, $msg);
broadcast(new MessageSent($roomId, $msg));
SaveMessageJob::dispatch($msg);
return response()->json(['status' => 'success', 'message' => "已警告 {$targetUsername}"]);
}
/**
* 踢出用户(=T 理由)
*
* 将目标用户从聊天室踢出,清除其 Redis 在线状态。
*
* @param Request $request 请求对象,需包含 username, room_id, reason
* @return JsonResponse 操作结果
*/
public function kick(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 = $request->input('room_id');
$reason = $request->input('reason', '违反聊天室规则');
// 权限检查(等级由 level_kick 配置)
if (! $this->canExecute($admin, $targetUsername, 'level_kick', '10')) {
return response()->json(['status' => 'error', 'message' => '权限不足'], 403);
}
// 从 Redis 在线列表移除
$this->chatState->userLeave($roomId, $targetUsername);
// 广播踢出消息(通知前端强制该用户跳转)
$msg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => "🚫 管理员 <b>{$admin->username}</b> 已将 <b>{$targetUsername}</b> 踢出聊天室。原因:{$reason}",
'is_secret' => false,
'font_color' => '#dc2626',
'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, $reason));
return response()->json(['status' => 'success', 'message' => "已踢出 {$targetUsername}"]);
}
/**
* 禁言用户(=B 分钟数)
*
* 使用 Redis TTL 自动过期机制,禁止用户发言指定分钟数。
*
* @param Request $request 请求对象,需包含 username, room_id, duration
* @return JsonResponse 操作结果
*/
public function mute(Request $request): JsonResponse
{
$request->validate([
'username' => 'required|string',
'room_id' => 'required|integer',
'duration' => 'required|integer|min:1|max:1440',
]);
$admin = Auth::user();
$targetUsername = $request->input('username');
$roomId = $request->input('room_id');
$duration = $request->input('duration');
// 权限检查(等级由 level_mute 配置)
if (! $this->canExecute($admin, $targetUsername, 'level_mute', '8')) {
return response()->json(['status' => 'error', 'message' => '权限不足'], 403);
}
// 设置 Redis 禁言标记TTL 自动过期
$muteKey = "mute:{$roomId}:{$targetUsername}";
Redis::setex($muteKey, $duration * 60, now()->toDateTimeString());
// 广播禁言消息
$msg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => "🔇 管理员 <b>{$admin->username}</b> 已将 <b>{$targetUsername}</b> 禁言 {$duration} 分钟。",
'is_secret' => false,
'font_color' => '#dc2626',
'action' => '',
'sent_at' => now()->toDateTimeString(),
];
$this->chatState->pushMessage($roomId, $msg);
broadcast(new MessageSent($roomId, $msg));
SaveMessageJob::dispatch($msg);
// 广播禁言事件(前端禁用输入框)
broadcast(new \App\Events\UserMuted($roomId, $targetUsername, $duration, "管理员 {$admin->username} 已将您禁言 {$duration} 分钟"));
return response()->json(['status' => 'success', 'message' => "已禁言 {$targetUsername} {$duration} 分钟"]);
}
/**
* 冻结用户账号(=Y 理由)
*
* 将用户账号状态设为冻结,禁止登录。
*
* @param Request $request 请求对象,需包含 username, reason
* @return JsonResponse 操作结果
*/
public function freeze(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 = $request->input('room_id');
$reason = $request->input('reason', '违反聊天室规则');
// 权限检查(等级由 level_freeze 配置)
if (! $this->canExecute($admin, $targetUsername, 'level_freeze', '14')) {
return response()->json(['status' => 'error', 'message' => '权限不足'], 403);
}
// 冻结用户账号(将等级设为 -1 表示冻结)
$target = User::where('username', $targetUsername)->first();
if (! $target) {
return response()->json(['status' => 'error', 'message' => '用户不存在'], 404);
}
$target->user_level = -1;
$target->save();
// 从所有房间移除
$rooms = $this->chatState->getUserRooms($targetUsername);
foreach ($rooms as $rid) {
$this->chatState->userLeave($rid, $targetUsername);
}
// 广播冻结消息
$msg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => "🧊 管理员 <b>{$admin->username}</b> 已冻结 <b>{$targetUsername}</b> 的账号。原因:{$reason}",
'is_secret' => false,
'font_color' => '#dc2626',
'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, "账号已被冻结:{$reason}"));
return response()->json(['status' => 'success', 'message' => "已冻结 {$targetUsername} 的账号"]);
}
/**
* 查看用户私信(=S
*
* 管理员查看指定用户最近的悄悄话记录。
*
* @param string $username 目标用户名
* @return JsonResponse 私信记录列表
*/
public function viewWhispers(string $username): JsonResponse
{
$admin = Auth::user();
$superLevel = (int) Sysparam::getValue('superlevel', '100');
if ($admin->user_level < $superLevel) {
return response()->json(['status' => 'error', 'message' => '仅站长可查看私信'], 403);
}
// 查询最近 50 条悄悄话(发送或接收)
$messages = Message::where('is_secret', true)
->where(function ($q) use ($username) {
$q->where('from_user', $username)
->orWhere('to_user', $username);
})
->orderByDesc('id')
->limit(50)
->get(['id', 'from_user', 'to_user', 'content', 'sent_at']);
return response()->json([
'status' => 'success',
'username' => $username,
'messages' => $messages,
]);
}
/**
* 站长公屏讲话
*
* 站长发送全聊天室公告,以特殊样式显示。
*
* @param Request $request 请求对象,需包含 content, room_id
* @return JsonResponse 操作结果
*/
public function announce(Request $request): JsonResponse
{
$request->validate([
'content' => 'required|string|max:500',
'room_id' => 'required|integer',
]);
$admin = Auth::user();
$superLevel = (int) Sysparam::getValue('superlevel', '100');
if ($admin->user_level < $superLevel) {
return response()->json(['status' => 'error', 'message' => '仅站长可发布公屏讲话'], 403);
}
$roomId = $request->input('room_id');
$content = $request->input('content');
// 广播站长公告
$msg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统公告',
'to_user' => '大家',
'content' => "📢 站长 <b>{$admin->username}</b> 讲话:{$content}",
'is_secret' => false,
'font_color' => '#b91c1c',
'action' => '',
'sent_at' => now()->toDateTimeString(),
];
$this->chatState->pushMessage($roomId, $msg);
broadcast(new MessageSent($roomId, $msg));
SaveMessageJob::dispatch($msg);
return response()->json(['status' => 'success', 'message' => '公告已发送']);
}
/**
* 权限检查:管理员是否可对目标用户执行指定操作
*
* 根据 sysparam 中配置的等级门槛判断权限。
*
* @param User $admin 管理员用户
* @param string $targetUsername 目标用户名
* @param string $levelKey sysparam 中的等级键名(如 level_kick、level_warn
* @param string $defaultLevel 默认等级值
* @return bool 是否有权限
*/
private function canExecute(User $admin, string $targetUsername, string $levelKey, string $defaultLevel = '5'): bool
{
// 必须达到该操作所需的最低等级
$requiredLevel = (int) Sysparam::getValue($levelKey, $defaultLevel);
if ($admin->user_level < $requiredLevel) {
return false;
}
// 不能操作自己
if ($admin->username === $targetUsername) {
return false;
}
// 目标用户等级必须低于操作者
$target = User::where('username', $targetUsername)->first();
if ($target && $target->user_level >= $admin->user_level) {
return false;
}
return true;
}
}