Files
chatroom/app/Http/Controllers/Admin/UserManagerController.php
lkddi 6fa42b90d5 功能:站长礼包系统(金币/经验双类型)+ 后台用户编辑权限收紧(仅 id=1 超管)
新增功能:
- 礼包系统:superlevel 站长可发 888 数量 10 份礼包,支持金币/经验双类型
- 发包前三按钮选择(金币礼包 / 经验礼包 / 取消),使用 chatBanner 弹窗
- 聊天室系统公告含「立即抢包」按钮,金币红色/经验紫色配色区分
- WebSocket 实时推送红包弹窗卡片至所有在线用户
- Redis LPOP 原子分发 + 数据库 unique 约束防重领,并发安全
- 弹窗打开自动拉取服务端最新状态(剩余数量/已领/过期实时刷新)
- 新增 GET /red-packet/{id}/status 状态查询接口
- 新增 CurrencySource::RED_PACKET_RECV / RED_PACKET_RECV_EXP 枚举
安全加固:
- 后台用户编辑/强杀按钮仅 id=1 超管可见(前端隐藏 + 后端 403 双重拦截)
2026-03-01 22:20:54 +08:00

212 lines
8.0 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
/**
* 文件功能:后台用户大盘管理控制器
* (替代原版 gl/ 下的各种管理面)
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers\Admin;
use App\Enums\CurrencySource;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\View\View;
class UserManagerController extends Controller
{
/**
* 注入统一积分服务和聊天室状态服务
*/
public function __construct(
private readonly UserCurrencyService $currencyService,
private readonly ChatStateService $chatState,
) {}
/**
* 显示用户列表及搜索(支持按等级/经验/金币/魅力/在线状态排序)
*/
public function index(Request $request): View
{
$query = User::query();
if ($request->filled('username')) {
$query->where('username', 'like', '%'.$request->input('username').'%');
}
// 从 Redis 获取所有在线用户名(跨所有房间去重)
$onlineUsernames = collect();
foreach ($this->chatState->getAllActiveRoomIds() as $roomId) {
$onlineUsernames = $onlineUsernames->merge(array_keys($this->chatState->getRoomUsers($roomId)));
}
$onlineUsernames = $onlineUsernames->unique()->values();
// 排序:允许的字段白名单,防止 SQL 注入
$sortable = ['user_level', 'exp_num', 'jjb', 'meili', 'id', 'online'];
$sortBy = in_array($request->input('sort_by'), $sortable) ? $request->input('sort_by') : 'id';
$sortDir = $request->input('sort_dir') === 'asc' ? 'asc' : 'desc';
if ($sortBy === 'online') {
// 用虚拟列排序:在线用户标记为 1离线为 0desc = 在线优先
if ($onlineUsernames->isNotEmpty()) {
$placeholders = implode(',', array_fill(0, $onlineUsernames->count(), '?'));
$query->orderByRaw(
"CASE WHEN username IN ({$placeholders}) THEN 1 ELSE 0 END {$sortDir}",
$onlineUsernames->toArray(),
);
}
$query->orderBy('id', 'desc'); // 二级排序
} else {
$query->orderBy($sortBy, $sortDir);
}
$users = $query
->with(['activePosition.position.department', 'vipLevel'])
->paginate(20)
->withQueryString();
// VIP 等级选项列表(供编辑弹窗使用)
$vipLevels = \App\Models\VipLevel::orderBy('sort_order')->get();
return view('admin.users.index', compact('users', 'vipLevels', 'sortBy', 'sortDir', 'onlineUsernames'));
}
/**
* 修改用户资料、等级或密码 (AJAX 或表单)
*
* @param User $user 路由模型自动注入
*/
public function update(Request $request, User $user): JsonResponse|RedirectResponse
{
$targetUser = $user;
$currentUser = Auth::user();
// 超级管理员专属:仅 id=1 的账号可编辑用户信息
if ($currentUser->id !== 1) {
if ($request->wantsJson()) {
return response()->json(['status' => 'error', 'message' => '仅超级管理员id=1可编辑用户信息。'], 403);
}
abort(403, '仅超级管理员id=1可编辑用户信息。');
}
// 越权防护:不能修改 等级大于或等于自己 的目标(除非修改自己)
if ($targetUser->id !== $currentUser->id && $targetUser->user_level >= $currentUser->user_level) {
return response()->json(['status' => 'error', 'message' => '权限不足:您无法修改同级或高级管理人员资料。'], 403);
}
$validated = $request->validate([
'sex' => 'sometimes|integer|in:0,1,2',
'exp_num' => 'sometimes|integer|min:0',
'jjb' => 'sometimes|integer|min:0',
'meili' => 'sometimes|integer|min:0',
'qianming' => 'sometimes|nullable|string|max:255',
'headface' => 'sometimes|string|max:50',
'password' => 'nullable|string|min:6',
'vip_level_id' => 'sometimes|nullable|integer|exists:vip_levels,id',
'hy_time' => 'sometimes|nullable|date',
]);
if (isset($validated['sex'])) {
$targetUser->sex = $validated['sex'];
}
if (isset($validated['exp_num'])) {
// 计算差值并通过统一服务记录流水(管理员手动调整)
$expDiff = $validated['exp_num'] - ($targetUser->exp_num ?? 0);
if ($expDiff !== 0) {
$this->currencyService->change(
$targetUser, 'exp', $expDiff, CurrencySource::ADMIN_ADJUST,
"管理员 {$currentUser->username} 手动调整经验",
);
$targetUser->refresh();
}
}
if (isset($validated['jjb'])) {
$jjbDiff = $validated['jjb'] - ($targetUser->jjb ?? 0);
if ($jjbDiff !== 0) {
$this->currencyService->change(
$targetUser, 'gold', $jjbDiff, CurrencySource::ADMIN_ADJUST,
"管理员 {$currentUser->username} 手动调整金币",
);
$targetUser->refresh();
}
}
if (isset($validated['meili'])) {
$meiliDiff = $validated['meili'] - ($targetUser->meili ?? 0);
if ($meiliDiff !== 0) {
$this->currencyService->change(
$targetUser, 'charm', $meiliDiff, CurrencySource::ADMIN_ADJUST,
"管理员 {$currentUser->username} 手动调整魅力",
);
$targetUser->refresh();
}
}
if (array_key_exists('qianming', $validated)) {
$targetUser->qianming = $validated['qianming'];
}
if (isset($validated['headface'])) {
$targetUser->headface = $validated['headface'];
}
// VIP 会员等级设置
if (array_key_exists('vip_level_id', $validated)) {
$targetUser->vip_level_id = $validated['vip_level_id'] ?: null;
}
if (array_key_exists('hy_time', $validated)) {
$targetUser->hy_time = $validated['hy_time'] ?: null;
}
if (! empty($validated['password'])) {
$targetUser->password = Hash::make($validated['password']);
}
$targetUser->save();
if ($request->wantsJson()) {
return response()->json(['status' => 'success', 'message' => '用户资料已强行更新完毕!']);
}
return back()->with('success', '用户资料已更新!');
}
/**
* 物理删除杀封用户
*
* @param User $user 路由模型自动注入
*/
public function destroy(Request $request, User $user): RedirectResponse
{
$targetUser = $user;
$currentUser = Auth::user();
// 超级管理员专属:仅 id=1 的账号可删除用户
if ($currentUser->id !== 1) {
abort(403, '仅超级管理员id=1可删除用户。');
}
// 越权防护:不允许删除同级或更高等级的账号
if ($targetUser->id !== $currentUser->id && $targetUser->user_level >= $currentUser->user_level) {
abort(403, '权限不足:无法删除同级或高级账号!');
}
// 管理员保护:达到踢人等级(level_kick)的用户视为管理员,不可被强杀
$levelKick = (int) \App\Models\Sysparam::getValue('level_kick', '10');
if ($targetUser->user_level >= $levelKick) {
abort(403, '该用户为管理员,不允许强杀!请先在用户编辑中降低其等级。');
}
$targetUser->delete();
return back()->with('success', '目标已被物理删除。');
}
}