新增:管理员命令系统(警告/踢出/禁言/冻结/查看私信/站长公屏)
- 新建 AdminCommandController 处理6个管理操作命令
- 注册管理员命令路由 /command/*
- 更新 UserKicked 事件增加原因字段
- 更新 UserMuted 事件支持自定义提示消息
- 重构用户名片弹窗管理面板:警告/踢出/禁言/冻结按钮
- 站长专属:查看私信记录、📢公屏讲话按钮
- 被踢出时显示踢出原因
This commit is contained in:
355
app/Http/Controllers/AdminCommandController.php
Normal file
355
app/Http/Controllers/AdminCommandController.php
Normal file
@@ -0,0 +1,355 @@
|
||||
<?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', '请注意言行');
|
||||
|
||||
// 权限检查
|
||||
if (! $this->canManage($admin, $targetUsername)) {
|
||||
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', '违反聊天室规则');
|
||||
|
||||
if (! $this->canManage($admin, $targetUsername)) {
|
||||
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');
|
||||
|
||||
if (! $this->canManage($admin, $targetUsername)) {
|
||||
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', '违反聊天室规则');
|
||||
|
||||
if (! $this->canManage($admin, $targetUsername)) {
|
||||
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' => '公告已发送']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限检查:管理员是否可管理目标用户
|
||||
*
|
||||
* 管理员等级必须高于目标用户等级,且不能操作自己。
|
||||
*
|
||||
* @param User $admin 管理员用户
|
||||
* @param string $targetUsername 目标用户名
|
||||
* @return bool 是否有权限
|
||||
*/
|
||||
private function canManage(User $admin, string $targetUsername): bool
|
||||
{
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
|
||||
// 必须是管理员(达到踢人等级)
|
||||
$kickLevel = (int) Sysparam::getValue('level_kick', '5');
|
||||
if ($admin->user_level < $kickLevel) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user