711 lines
27 KiB
PHP
711 lines
27 KiB
PHP
<?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\Enums\CurrencySource;
|
||
use App\Events\MessageSent;
|
||
use App\Jobs\SaveMessageJob;
|
||
use App\Models\Message;
|
||
use App\Models\PositionAuthorityLog;
|
||
use App\Models\Sysparam;
|
||
use App\Models\User;
|
||
use App\Services\ChatStateService;
|
||
use App\Services\UserCurrencyService;
|
||
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,
|
||
private readonly UserCurrencyService $currencyService,
|
||
) {}
|
||
|
||
/**
|
||
* 警告用户(=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: $roomId,
|
||
username: $targetUsername,
|
||
muteTime: $duration,
|
||
operator: $admin->username,
|
||
));
|
||
|
||
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)
|
||
*
|
||
* 管理员查看指定用户最近的悄悄话记录。
|
||
* AJAX 请求返回 JSON,浏览器请求返回美观的 HTML 页面。
|
||
*
|
||
* @param string $username 目标用户名
|
||
* @return JsonResponse|\Illuminate\View\View 私信记录
|
||
*/
|
||
public function viewWhispers(Request $request, string $username): JsonResponse|\Illuminate\View\View
|
||
{
|
||
$admin = Auth::user();
|
||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||
|
||
if ($admin->user_level < $superLevel) {
|
||
if ($request->expectsJson()) {
|
||
return response()->json(['status' => 'error', 'message' => '仅站长可查看私信'], 403);
|
||
}
|
||
abort(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']);
|
||
|
||
// AJAX 请求返回 JSON(给名片弹窗用),浏览器请求返回 HTML 页面
|
||
if ($request->expectsJson()) {
|
||
return response()->json([
|
||
'status' => 'success',
|
||
'username' => $username,
|
||
'messages' => $messages,
|
||
]);
|
||
}
|
||
|
||
return view('admin.whispers', [
|
||
'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' => '公告已发送']);
|
||
}
|
||
|
||
/**
|
||
* 管理员全员清屏
|
||
*
|
||
* 清除 Redis 中该房间的聊天记录缓存,并广播清屏事件通知所有用户前端清除消息。
|
||
* 前端只清除普通消息,保留悄悄话。
|
||
*
|
||
* @param Request $request 请求对象,需包含 room_id
|
||
* @return JsonResponse 操作结果
|
||
*/
|
||
public function clearScreen(Request $request): JsonResponse
|
||
{
|
||
$request->validate([
|
||
'room_id' => 'required|integer',
|
||
]);
|
||
|
||
$admin = Auth::user();
|
||
$roomId = $request->input('room_id');
|
||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||
|
||
// 需要站长权限才能全员清屏
|
||
if ($admin->user_level < $superLevel) {
|
||
return response()->json(['status' => 'error', 'message' => '仅站长可执行全员清屏'], 403);
|
||
}
|
||
|
||
// 清除 Redis 中该房间的消息缓存
|
||
$this->chatState->clearMessages($roomId);
|
||
|
||
// 广播清屏事件
|
||
broadcast(new \App\Events\ScreenCleared($roomId, $admin->username));
|
||
|
||
return response()->json(['status' => 'success', 'message' => '已执行全员清屏']);
|
||
}
|
||
|
||
/**
|
||
* 管理员触发全屏特效(烟花/下雨/雷电)
|
||
*
|
||
* 向房间内所有用户广播 EffectBroadcast 事件,前端收到后播放对应 Canvas 动画。
|
||
* 仅 superlevel 等级管理员可触发。
|
||
*
|
||
* @param Request $request 请求对象,需包含 room_id, type
|
||
* @return JsonResponse 操作结果
|
||
*/
|
||
public function effect(Request $request): JsonResponse
|
||
{
|
||
$request->validate([
|
||
'room_id' => 'required|integer',
|
||
'type' => 'required|in:fireworks,rain,lightning,snow',
|
||
]);
|
||
|
||
$admin = Auth::user();
|
||
$roomId = $request->input('room_id');
|
||
$type = $request->input('type');
|
||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||
|
||
// 仅 superlevel 等级可触发特效
|
||
if ($admin->user_level < $superLevel) {
|
||
return response()->json(['status' => 'error', 'message' => '仅站长可触发特效'], 403);
|
||
}
|
||
|
||
// 广播特效事件给房间内所有在线用户
|
||
broadcast(new \App\Events\EffectBroadcast($roomId, $type, $admin->username));
|
||
|
||
return response()->json(['status' => 'success', 'message' => "已触发特效:{$type}"]);
|
||
}
|
||
|
||
/**
|
||
* 职务奖励金币(凭空发放,无需扣操作者余额)
|
||
*
|
||
* 三层限额校验:
|
||
* 1. amount ≤ position.max_reward (单次上限)
|
||
* 2. 今日累计发放 + amount ≤ position.daily_reward_limit (操作人单日累计上限)
|
||
* 3. 今日对同一接收者发放次数 < position.recipient_daily_limit(同一接收者每日次数限)
|
||
*
|
||
* 成功后:
|
||
* - 通过 UserCurrencyService 给接收者增加金币
|
||
* - 写入 PositionAuthorityLog(action_type=reward,记录到履职记录)
|
||
* - 向房间发送悄悄话通知接收者
|
||
*
|
||
* @param Request $request 需包含 username, room_id, amount
|
||
*/
|
||
public function reward(Request $request): JsonResponse
|
||
{
|
||
$request->validate([
|
||
'username' => 'required|string',
|
||
'room_id' => 'required|integer',
|
||
'amount' => 'required|integer|min:1|max:999999999',
|
||
], [
|
||
'amount.max' => '单次发放金币不能超过 999999999',
|
||
'amount.min' => '发放金币至少为 1',
|
||
'amount.integer' => '金币数量必须是整数',
|
||
'amount.required' => '请输入要发放的金币数量',
|
||
]);
|
||
|
||
$admin = Auth::user();
|
||
$roomId = (int) $request->input('room_id');
|
||
$amount = (int) $request->input('amount');
|
||
$targetUsername = $request->input('username');
|
||
|
||
// 不能给自己发放
|
||
if ($admin->username === $targetUsername) {
|
||
return response()->json(['status' => 'error', 'message' => '不能给自己发放奖励'], 422);
|
||
}
|
||
|
||
// 目标用户必须存在
|
||
$target = User::where('username', $targetUsername)->first();
|
||
if (! $target) {
|
||
return response()->json(['status' => 'error', 'message' => '用户不存在'], 404);
|
||
}
|
||
|
||
// id=1 超级管理员:无需职务,无限额限制
|
||
$isSuperAdmin = $admin->id === 1;
|
||
$userPosition = null;
|
||
$position = null;
|
||
|
||
if (! $isSuperAdmin) {
|
||
// ① 必须有在职职务
|
||
$userPosition = $admin->activePosition;
|
||
if (! $userPosition) {
|
||
return response()->json(['status' => 'error', 'message' => '你当前没有在职职务,无权发放奖励'], 403);
|
||
}
|
||
|
||
$position = $userPosition->position;
|
||
|
||
// 职务 max_reward = 0 表示禁止,null 表示不限,正整数表示有上限
|
||
if ($position?->max_reward === 0) {
|
||
return response()->json(['status' => 'error', 'message' => '你的职务未配置奖励权限'], 403);
|
||
}
|
||
|
||
// ② 单次上限校验(max_reward > 0 时才限制,null = 不限)
|
||
if ($position->max_reward && $amount > $position->max_reward) {
|
||
return response()->json([
|
||
'status' => 'error',
|
||
'message' => "单次奖励上限为 {$position->max_reward} 金币,请调整金额",
|
||
], 422);
|
||
}
|
||
|
||
// ③ 操作人单日累计上限校验
|
||
if ($position->daily_reward_limit) {
|
||
$todayTotal = PositionAuthorityLog::where('user_id', $admin->id)
|
||
->where('action_type', 'reward')
|
||
->whereDate('created_at', today())
|
||
->sum('amount');
|
||
|
||
if ($todayTotal + $amount > $position->daily_reward_limit) {
|
||
$remaining = max(0, $position->daily_reward_limit - $todayTotal);
|
||
|
||
return response()->json([
|
||
'status' => 'error',
|
||
'message' => "今日剩余可发放额度为 {$remaining} 金币,超出单日上限({$position->daily_reward_limit})",
|
||
], 422);
|
||
}
|
||
}
|
||
|
||
// ④ 职务级别:接收者每日次数上限
|
||
if ($position->recipient_daily_limit) {
|
||
$recipientCount = PositionAuthorityLog::where('target_user_id', $target->id)
|
||
->where('action_type', 'reward')
|
||
->whereDate('created_at', today())
|
||
->count();
|
||
|
||
if ($recipientCount >= $position->recipient_daily_limit) {
|
||
return response()->json([
|
||
'status' => 'error',
|
||
'message' => "{$targetUsername} 今日已由全体职务人员累计发放 {$recipientCount} 次奖励,已达每日上限({$position->recipient_daily_limit})",
|
||
], 422);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ⑤ 全局系统级别:每位用户单日最多接收奖励次数(所有操作人通用,含超管)
|
||
$globalMax = (int) Sysparam::getValue('reward_recipient_daily_max', '0');
|
||
if ($globalMax > 0) {
|
||
$globalCount = PositionAuthorityLog::where('target_user_id', $target->id)
|
||
->where('action_type', 'reward')
|
||
->whereDate('created_at', today())
|
||
->count();
|
||
|
||
if ($globalCount >= $globalMax) {
|
||
return response()->json([
|
||
'status' => 'error',
|
||
'message' => "{$targetUsername} 今日已累计接收 {$globalCount} 次奖励,已达全局每日上限({$globalMax})",
|
||
], 422);
|
||
}
|
||
}
|
||
|
||
// 发放金币(通过 UserCurrencyService 原子性更新 + 写流水)
|
||
// 组合「部门 · 职务」显示名,超管特殊处理
|
||
if ($isSuperAdmin) {
|
||
$positionName = '超级管理员';
|
||
} elseif ($position) {
|
||
$deptName = $position->department?->name;
|
||
$positionName = $deptName ? "{$deptName} · {$position->name}" : $position->name;
|
||
} else {
|
||
$positionName = '职务';
|
||
}
|
||
|
||
$this->currencyService->change(
|
||
$target,
|
||
'gold',
|
||
$amount,
|
||
CurrencySource::POSITION_REWARD,
|
||
"{$admin->username}({$positionName})职务奖励",
|
||
$roomId,
|
||
);
|
||
|
||
// 写履职记录(PositionAuthorityLog;超管无职务时 user_position_id 留 null)
|
||
PositionAuthorityLog::create([
|
||
'user_id' => $admin->id,
|
||
'user_position_id' => $userPosition?->id,
|
||
'action_type' => 'reward',
|
||
'target_user_id' => $target->id,
|
||
'amount' => $amount,
|
||
'remark' => "发放奖励金币 {$amount} 枚给 {$targetUsername}",
|
||
]);
|
||
|
||
// ① 聊天室公开公告(所有在场用户可见)
|
||
$publicMsg = [
|
||
'id' => $this->chatState->nextMessageId($roomId),
|
||
'room_id' => $roomId,
|
||
'from_user' => '系统公告',
|
||
'to_user' => '',
|
||
'content' => "💰 <b>{$admin->username}</b>({$positionName})向 <b>{$targetUsername}</b> 发放了 <b>{$amount}</b> 枚奖励金币!",
|
||
'is_secret' => false,
|
||
'font_color' => '#d97706',
|
||
'action' => '',
|
||
'sent_at' => now()->toDateTimeString(),
|
||
];
|
||
$this->chatState->pushMessage($roomId, $publicMsg);
|
||
broadcast(new MessageSent($roomId, $publicMsg));
|
||
SaveMessageJob::dispatch($publicMsg);
|
||
|
||
// ② 接收者私信(含 toast_notification 触发右下角小卡片)
|
||
$freshJjb = $target->fresh()->jjb;
|
||
$msg = [
|
||
'id' => $this->chatState->nextMessageId($roomId),
|
||
'room_id' => $roomId,
|
||
'from_user' => '系统',
|
||
'to_user' => $targetUsername,
|
||
'content' => "🎁 <b>{$admin->username}</b>({$positionName})向你发放了 <b>{$amount}</b> 枚金币奖励!当前金币:{$freshJjb} 枚。",
|
||
'is_secret' => true,
|
||
'font_color' => '#d97706',
|
||
'action' => '',
|
||
'sent_at' => now()->toDateTimeString(),
|
||
// 前端 toast-notification 组件识别此字段,弹出右下角通知卡片
|
||
'toast_notification' => [
|
||
'title' => '💰 奖励金币到账',
|
||
'message' => "<b>{$admin->username}</b>({$positionName})向你发放了 <b>{$amount}</b> 枚金币!",
|
||
'icon' => '💰',
|
||
'color' => '#f59e0b',
|
||
'duration' => 8000,
|
||
],
|
||
];
|
||
$this->chatState->pushMessage($roomId, $msg);
|
||
broadcast(new MessageSent($roomId, $msg));
|
||
SaveMessageJob::dispatch($msg);
|
||
|
||
return response()->json([
|
||
'status' => 'success',
|
||
'message' => "已向 {$targetUsername} 发放 {$amount} 金币奖励 🎉",
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 查询当前操作人的奖励额度信息(供发放弹窗展示)
|
||
*
|
||
* 返回字段:
|
||
* - max_once: 单次上限(null = 不限)
|
||
* - daily_limit: 单日发放总额上限(null = 不限)
|
||
* - today_sent: 今日已发放总额
|
||
* - daily_remaining: 今日剩余可发放额度(null = 不限)
|
||
*/
|
||
public function rewardQuota(): \Illuminate\Http\JsonResponse
|
||
{
|
||
$admin = Auth::user();
|
||
$isSuperAdmin = $admin->id === 1;
|
||
|
||
// 最近 10 条本人发放记录(含目标用户名)
|
||
$recent = PositionAuthorityLog::with('targetUser:id,username')
|
||
->where('user_id', $admin->id)
|
||
->where('action_type', 'reward')
|
||
->latest()
|
||
->limit(10)
|
||
->get()
|
||
->map(fn ($log) => [
|
||
'target' => $log->targetUser?->username ?? '未知',
|
||
'amount' => $log->amount,
|
||
'created_at' => $log->created_at?->format('m-d H:i'),
|
||
]);
|
||
|
||
if ($isSuperAdmin) {
|
||
return response()->json([
|
||
'max_once' => null,
|
||
'daily_limit' => null,
|
||
'today_sent' => (int) PositionAuthorityLog::where('user_id', $admin->id)
|
||
->where('action_type', 'reward')
|
||
->whereDate('created_at', today())
|
||
->sum('amount'),
|
||
'daily_remaining' => null,
|
||
'recent_rewards' => $recent,
|
||
]);
|
||
}
|
||
|
||
$position = $admin->activePosition?->position;
|
||
if (! $position) {
|
||
return response()->json([
|
||
'max_once' => 0,
|
||
'daily_limit' => null,
|
||
'today_sent' => 0,
|
||
'daily_remaining' => null,
|
||
'recent_rewards' => $recent,
|
||
]);
|
||
}
|
||
|
||
// 今日已发放总额
|
||
$todaySent = (int) PositionAuthorityLog::where('user_id', $admin->id)
|
||
->where('action_type', 'reward')
|
||
->whereDate('created_at', today())
|
||
->sum('amount');
|
||
|
||
$dailyLimit = $position->daily_reward_limit;
|
||
$remaining = $dailyLimit !== null ? max(0, $dailyLimit - $todaySent) : null;
|
||
|
||
return response()->json([
|
||
'max_once' => $position->max_reward,
|
||
'daily_limit' => $dailyLimit,
|
||
'today_sent' => $todaySent,
|
||
'daily_remaining' => $remaining,
|
||
'recent_rewards' => $recent,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 权限检查:管理员是否可对目标用户执行指定操作
|
||
*
|
||
* 根据 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;
|
||
}
|
||
}
|