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

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
+11 -172
View File
@@ -2,13 +2,8 @@
/**
* 文件功能:用户中心与管理控制器
* 接管原版 USERinfo.ASP, USERSET.ASP, chpasswd.asp, KILLUSER.ASP, LOCKIP.ASP
*
* 权限等级通过 sysparam 表动态配置:
* level_kick - 踢人所需等级
* level_mute - 禁言所需等级
* level_ban - 封号所需等级
* level_banip - 封IP所需等级
* 接管原版 USERinfo.ASP, USERSET.ASP chpasswd.asp。
* 聊天室管理动作已统一迁移到 AdminCommandController 的职务权限链路。
*
* @author ChatRoom Laravel
*
@@ -18,25 +13,23 @@
namespace App\Http\Controllers;
use App\Enums\CurrencySource;
use App\Events\UserKicked;
use App\Events\UserMuted;
use App\Events\UserStatusUpdated;
use App\Http\Requests\ChangePasswordRequest;
use App\Http\Requests\RevealProfileInfoRequest;
use App\Http\Requests\UpdateChatPreferencesRequest;
use App\Http\Requests\UpdateDailyStatusRequest;
use App\Http\Requests\UpdateProfileRequest;
use App\Models\Room;
use App\Models\Sysparam;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\ChatUserPresenceService;
use App\Services\PositionPermissionService;
use App\Services\UserCurrencyService;
use App\Support\PositionPermissionRegistry;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Redis;
/**
* 类功能:处理用户资料、聊天室偏好、当日状态与基础管理动作。
@@ -65,6 +58,7 @@ class UserController extends Controller
private readonly ChatStateService $chatState,
private readonly ChatUserPresenceService $chatUserPresenceService,
private readonly UserCurrencyService $currencyService,
private readonly PositionPermissionService $positionPermissionService,
) {}
/**
@@ -166,9 +160,10 @@ class UserController extends Controller
] : null,
];
// 拥有封禁IP(level_banip)或踢人以上权限的管理,可以查看IP和归属地
$levelBanIp = (int) Sysparam::getValue('level_banip', '15');
if ($operator && $operator->user_level >= $levelBanIp) {
// 管理员网络信息仅对站长或拥有「封IP」职务权限的操作者展示。
$canViewNetworkInfo = $operator
&& ($operator->id === 1 || $this->positionPermissionService->hasPermission($operator, PositionPermissionRegistry::USER_BANIP));
if ($canViewNetworkInfo) {
$data['first_ip'] = $targetUser->first_ip;
// last_ip 目前定义为『上次登录IP』(取数据库 previous_ip
$data['last_ip'] = $targetUser->previous_ip;
@@ -415,7 +410,7 @@ class UserController extends Controller
/**
* 生成微信绑定代码
*/
public function generateWechatCode(\Illuminate\Http\Request $request): JsonResponse
public function generateWechatCode(Request $request): JsonResponse
{
$user = \Illuminate\Support\Facades\Auth::user();
if (! $user) {
@@ -435,7 +430,7 @@ class UserController extends Controller
/**
* 取消绑定微信
*/
public function unbindWechat(\Illuminate\Http\Request $request): JsonResponse
public function unbindWechat(Request $request): JsonResponse
{
$user = \Illuminate\Support\Facades\Auth::user();
if (! $user) {
@@ -451,162 +446,6 @@ class UserController extends Controller
]);
}
/**
* 通用权限校验:检查操作者是否有权操作目标用户
*
* @param object $operator 操作者
* @param string $targetUsername 目标用户名
* @param int $roomId 房间ID
* @param string $levelKey sysparam中的等级键名(如 level_kick
* @param string $actionName 操作名称(用于错误提示)
* @return array{room: Room, target: User}|JsonResponse
*/
private function checkPermission(object $operator, string $targetUsername, int $roomId, string $levelKey, string $actionName): array|JsonResponse
{
$room = Room::findOrFail($roomId);
$requiredLevel = (int) Sysparam::getValue($levelKey, '15');
// 鉴权:操作者要是房间房主或达到所需等级
if ($room->master !== $operator->username && $operator->user_level < $requiredLevel) {
return response()->json(['status' => 'error', 'message' => "权限不足(需要{$requiredLevel}级),无法执行{$actionName}操作。"], 403);
}
$targetUser = User::where('username', $targetUsername)->first();
if (! $targetUser) {
return response()->json(['status' => 'error', 'message' => '目标用户不存在。'], 404);
}
// 防误伤:不能操作等级 >= 自己的人
if ($targetUser->user_level >= $operator->user_level) {
return response()->json(['status' => 'error', 'message' => "权限不足,无法对同级或高级用户执行{$actionName}"], 403);
}
return ['room' => $room, 'target' => $targetUser];
}
/**
* 踢出房间 (对应 KILLUSER.ASP)
* 所需等级由 sysparam level_kick 配置
*/
public function kick(Request $request, string $username): JsonResponse
{
$operator = Auth::user();
$roomId = $request->input('room_id');
if (! $roomId) {
return response()->json(['status' => 'error', 'message' => '缺少房间参数。'], 422);
}
$result = $this->checkPermission($operator, $username, $roomId, 'level_kick', '踢出');
if ($result instanceof JsonResponse) {
return $result;
}
// 广播踢出事件
broadcast(new UserKicked($roomId, $result['target']->username, "管理员 [{$operator->username}] 将 [{$result['target']->username}] 踢出了聊天室。"));
return response()->json(['status' => 'success', 'message' => "已成功将 {$result['target']->username} 踢出房间。"]);
}
/**
* 禁言 (对应原版限制功能)
* 所需等级由 sysparam level_mute 配置
* 禁言信息存入 RedisTTL 到期自动解除
*/
public function mute(Request $request, string $username): JsonResponse
{
$operator = Auth::user();
$roomId = $request->input('room_id');
$duration = (int) $request->input('duration', 5);
if (! $roomId) {
return response()->json(['status' => 'error', 'message' => '缺少房间参数。'], 422);
}
$result = $this->checkPermission($operator, $username, $roomId, 'level_mute', '禁言');
if ($result instanceof JsonResponse) {
return $result;
}
// 写入 Redis 禁言标记,TTL = 禁言分钟数 * 60
Redis::setex("mute:{$roomId}:{$username}", $duration * 60, json_encode([
'operator' => $operator->username,
'reason' => '管理员禁言',
'until' => now()->addMinutes($duration)->toDateTimeString(),
]));
// 广播禁言事件
broadcast(new UserMuted($roomId, $username, $duration));
return response()->json(['status' => 'success', 'message' => "已对 {$username} 实施禁言 {$duration} 分钟。"]);
}
/**
* 封号(禁止登录)
* 所需等级由 sysparam level_ban 配置
* 将用户等级设为 -1 表示封禁
*/
public function ban(Request $request, string $username): JsonResponse
{
$operator = Auth::user();
$roomId = $request->input('room_id');
if (! $roomId) {
return response()->json(['status' => 'error', 'message' => '缺少房间参数。'], 422);
}
$result = $this->checkPermission($operator, $username, $roomId, 'level_ban', '封号');
if ($result instanceof JsonResponse) {
return $result;
}
// 封号:设置等级为 -1
$result['target']->user_level = -1;
$result['target']->save();
// 踢出聊天室
broadcast(new UserKicked($roomId, $username, "管理员 [{$operator->username}] 已封禁用户 [{$username}] 的账号。"));
return response()->json(['status' => 'success', 'message' => "用户 {$username} 已被封号。"]);
}
/**
* 封IP(记录IP到黑名单并踢出)
* 所需等级由 sysparam level_banip 配置
*/
public function banIp(Request $request, string $username): JsonResponse
{
$operator = Auth::user();
$roomId = $request->input('room_id');
if (! $roomId) {
return response()->json(['status' => 'error', 'message' => '缺少房间参数。'], 422);
}
$result = $this->checkPermission($operator, $username, $roomId, 'level_banip', '封IP');
if ($result instanceof JsonResponse) {
return $result;
}
$targetIp = $result['target']->last_ip;
if ($targetIp) {
// 将IP加入 Redis 黑名单(永久)
Redis::sadd('banned_ips', $targetIp);
}
// 同时封号
$result['target']->user_level = -1;
$result['target']->save();
// 踢出聊天室
broadcast(new UserKicked($roomId, $username, "管理员 [{$operator->username}] 已封禁用户 [{$username}] 的IP地址。"));
$ipInfo = $targetIp ? "IP: {$targetIp}" : '(未记录IP';
return response()->json(['status' => 'success', 'message' => "用户 {$username} 已被封号并封IP{$ipInfo}"]);
}
/**
* 判断操作者是否可以免费查看目标用户经验、金币与魅力。
*/