firstOrFail(); $operator = Auth::user(); // 基础公开信息 $data = [ 'username' => $targetUser->username, 'sex' => $targetUser->sex, 'headface' => $targetUser->headface, 'usersf' => $targetUser->usersf, 'user_level' => $targetUser->user_level, 'qianming' => $targetUser->qianming, 'sign' => $targetUser->sign ?? '这个人很懒,什么都没留下。', 'created_at' => $targetUser->created_at->format('Y-m-d'), ]; // 只有等级不低于对方,或者自己看自己时,才能看到详细的财富、经验资产 if ($operator && ($operator->user_level >= $targetUser->user_level || $operator->id === $targetUser->id)) { $data['exp_num'] = $targetUser->exp_num ?? 0; $data['jjb'] = $targetUser->jjb ?? 0; $data['meili'] = $targetUser->meili ?? 0; } // 拥有封禁IP(level_banip)或踢人以上权限的管理,可以查看IP和归属地 $levelBanIp = (int) Sysparam::getValue('level_banip', '15'); if ($operator && $operator->user_level >= $levelBanIp) { $data['last_ip'] = $targetUser->last_ip; $data['login_ip'] = $targetUser->login_ip; // 假设表中存在 login_ip 记录本次IP,若无则使用 last_ip 退化 // 解析归属地 (防崩溃处理:检查服务提供者是否已安装) $ipToLookup = $targetUser->login_ip ?: $targetUser->last_ip; if ($ipToLookup) { if (class_exists('\Stevebauman\Location\Facades\Location')) { try { $position = Location::get($ipToLookup); if ($position) { $region = $this->translateLocation($position->regionName ?? ''); $city = $this->translateLocation($position->cityName ?? ''); $data['location'] = ($position->countryName === 'China' || $position->countryName === 'Local') ? trim($region . ' ' . ($region === $city ? '' : $city)) : $this->translateLocation($position->countryName ?? ''); if (empty($data['location'])) { $data['location'] = '未知区域'; } } else { $data['location'] = '未知区域'; } } catch (\Exception $e) { $data['location'] = '解析失败'; } } else { $data['location'] = '未安装IP库'; } } else { $data['location'] = '暂无记录'; } } return response()->json([ 'status' => 'success', 'data' => $data, ]); } /** * 修改个人资料 (对应 USERSET.ASP) */ public function updateProfile(UpdateProfileRequest $request): JsonResponse { $user = Auth::user(); $data = $request->validated(); // 当用户试图更新邮箱,并且新邮箱不等于当前旧邮箱时启动验证码拦截 if (isset($data['email']) && $data['email'] !== $user->email) { // 首先判断系统开关是否开启,没开启直接禁止修改邮箱 if (\App\Models\SysParam::where('alias', 'smtp_enabled')->value('body') !== '1') { return response()->json(['status' => 'error', 'message' => '系统未开启邮件服务,当前禁止绑定/修改邮箱。'], 403); } $emailCode = $request->input('email_code'); if (empty($emailCode)) { return response()->json(['status' => 'error', 'message' => '新邮箱需要验证码,请先获取并填写验证码。'], 422); } // 获取缓存的验证码 $codeKey = 'email_verify_code_' . $user->id . '_' . $data['email']; $cachedCode = \Illuminate\Support\Facades\Cache::get($codeKey); if (!$cachedCode || $cachedCode != $emailCode) { return response()->json(['status' => 'error', 'message' => '验证码不正确或已过期(有效期5分钟),请重新获取。'], 422); } // 验证成功后,立即核销该验证码防止二次利用 \Illuminate\Support\Facades\Cache::forget($codeKey); } $user->update($data); return response()->json(['status' => 'success', 'message' => '资料更新成功。']); } /** * 修改密码 (对应 chpasswd.asp) */ public function changePassword(ChangePasswordRequest $request): JsonResponse { $user = Auth::user(); $oldPasswordInput = $request->input('old_password'); // 双模式密码校验逻辑(沿用 AuthController 中策略) $isOldPasswordCorrect = false; // 优先验证 Bcrypt if (Hash::check($oldPasswordInput, $user->password)) { $isOldPasswordCorrect = true; } // 降级验证旧版 MD5 elseif (md5($oldPasswordInput) === $user->password) { $isOldPasswordCorrect = true; } if (! $isOldPasswordCorrect) { return response()->json(['status' => 'error', 'message' => '当前密码输入不正确。'], 422); } // 验证通过,覆盖为新的 Bcrypt 加密串 $user->password = Hash::make($request->input('new_password')); $user->save(); return response()->json(['status' => 'success', 'message' => '密码已成功修改。下次请使用新密码登录。']); } /** * 通用权限校验:检查操作者是否有权操作目标用户 * * @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 配置 * 禁言信息存入 Redis,TTL 到期自动解除 */ 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}。"]); } /** * 将英文省市名称简单翻译为中文 */ private function translateLocation(string $name): string { if (empty($name)) return ''; $map = [ 'China' => '中国', 'Beijing' => '北京', 'Shanghai' => '上海', 'Tianjin' => '天津', 'Chongqing' => '重庆', 'Hebei' => '河北', 'Shanxi' => '山西', 'Liaoning' => '辽宁', 'Jilin' => '吉林', 'Heilongjiang' => '黑龙江', 'Jiangsu' => '江苏', 'Zhejiang' => '浙江', 'Anhui' => '安徽', 'Fujian' => '福建', 'Jiangxi' => '江西', 'Shandong' => '山东', 'Henan' => '河南', 'Hubei' => '湖北', 'Hunan' => '湖南', 'Guangdong' => '广东', 'Hainan' => '海南', 'Sichuan' => '四川', 'Guizhou' => '贵州', 'Yunnan' => '云南', 'Shaanxi' => '陕西', 'Gansu' => '甘肃', 'Qinghai' => '青海', 'Taiwan' => '台湾', 'Inner Mongolia' => '内蒙古', 'Guangxi' => '广西', 'Tibet' => '西藏', 'Ningxia' => '宁夏', 'Xinjiang' => '新疆', 'Hong Kong' => '香港', 'Macau' => '澳门', 'Local' => '本地局域网', ]; return $map[$name] ?? $name; } }