功能:字体颜色持久化、等级体系升级至99级、钓鱼小游戏、补充系统参数
- 字体颜色:s_color 改为 varchar,发消息时保存颜色,进入聊天室自动恢复 - 等级体系:maxlevel 15→99,superlevel 16→100,99级经验阶梯(幂次曲线) - 管理权限等级按比例调整:禁言50、踢人60、设公告60、封号80、封IP90 - 钓鱼小游戏:FishingController(抛竿扣金币+收竿随机结果+广播) - 补充6个缺失的 sysparam 参数 + 4个钓鱼参数 - 用户列表点击用户名后自动聚焦输入框 - Pint 格式化
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:自动事件管理控制器
|
||||
* 管理员可在后台增删改随机事件(好运/坏运/经验/金币奖惩等)
|
||||
* 复刻原版 ASP 聊天室的 autoact 管理功能
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Autoact;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AutoactController extends Controller
|
||||
{
|
||||
/**
|
||||
* 显示所有自动事件列表
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$events = Autoact::orderByDesc('id')->get();
|
||||
|
||||
return view('admin.autoact.index', compact('events'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存新事件
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'text_body' => 'required|string|max:500',
|
||||
'event_type' => 'required|in:good,bad,neutral',
|
||||
'exp_change' => 'required|integer',
|
||||
'jjb_change' => 'required|integer',
|
||||
]);
|
||||
|
||||
$data['enabled'] = true;
|
||||
|
||||
Autoact::create($data);
|
||||
|
||||
return redirect()->route('admin.autoact.index')->with('success', '事件添加成功!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新事件
|
||||
*/
|
||||
public function update(Request $request, int $id): RedirectResponse
|
||||
{
|
||||
$event = Autoact::findOrFail($id);
|
||||
|
||||
$data = $request->validate([
|
||||
'text_body' => 'required|string|max:500',
|
||||
'event_type' => 'required|in:good,bad,neutral',
|
||||
'exp_change' => 'required|integer',
|
||||
'jjb_change' => 'required|integer',
|
||||
]);
|
||||
|
||||
$event->update($data);
|
||||
|
||||
return redirect()->route('admin.autoact.index')->with('success', '事件修改成功!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换事件启用/禁用状态
|
||||
*/
|
||||
public function toggle(int $id): JsonResponse
|
||||
{
|
||||
$event = Autoact::findOrFail($id);
|
||||
$event->enabled = ! $event->enabled;
|
||||
$event->save();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'enabled' => $event->enabled,
|
||||
'message' => $event->enabled ? '已启用' : '已禁用',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
public function destroy(int $id): RedirectResponse
|
||||
{
|
||||
Autoact::findOrFail($id)->delete();
|
||||
|
||||
return redirect()->route('admin.autoact.index')->with('success', '事件已删除!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:后台房间管理控制器
|
||||
* 管理员可查看、编辑房间信息(名称、介绍、公告等)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Room;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class RoomManagerController extends Controller
|
||||
{
|
||||
/**
|
||||
* 显示所有房间列表
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$rooms = Room::orderBy('id')->get();
|
||||
|
||||
return view('admin.rooms.index', compact('rooms'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新房间信息
|
||||
*/
|
||||
public function update(Request $request, int $id): RedirectResponse
|
||||
{
|
||||
$room = Room::findOrFail($id);
|
||||
|
||||
$data = $request->validate([
|
||||
'room_name' => 'required|string|max:100',
|
||||
'room_des' => 'nullable|string|max:500',
|
||||
'announcement' => 'nullable|string|max:500',
|
||||
'room_owner' => 'nullable|string|max:50',
|
||||
'permit_level' => 'required|integer|min:0|max:15',
|
||||
'door_open' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$room->update($data);
|
||||
|
||||
return redirect()->route('admin.rooms.index')->with('success', "房间 [{$room->room_name}] 信息已更新!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除房间(非系统房间)
|
||||
*/
|
||||
public function destroy(int $id): RedirectResponse
|
||||
{
|
||||
$room = Room::findOrFail($id);
|
||||
|
||||
if ($room->room_keep) {
|
||||
return redirect()->route('admin.rooms.index')->with('error', '系统房间不允许删除!');
|
||||
}
|
||||
|
||||
$room->delete();
|
||||
|
||||
return redirect()->route('admin.rooms.index')->with('success', "房间 [{$room->room_name}] 已删除!");
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:后台 SQL 探针
|
||||
* (替代原版 SQL.ASP,严格限制为只读模式)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SqlController extends Controller
|
||||
{
|
||||
/**
|
||||
* 显示 SQL 执行沙盒界面
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return view('admin.sql.index', ['results' => null, 'query' => '', 'columns' => []]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 极度受限地执行 SQL (仅限 SELECT)
|
||||
*/
|
||||
public function execute(Request $request): View
|
||||
{
|
||||
$request->validate([
|
||||
'query' => 'required|string|min:6',
|
||||
]);
|
||||
|
||||
$sql = trim($request->input('query'));
|
||||
|
||||
// 安全拦截:绝不允许含有 update/delete/insert/truncate/drop 等破坏性指令
|
||||
// 我们只允许查询,所以要求必须以 SELECT 起手,或者 EXPLAIN/SHOW
|
||||
if (! preg_match('/^(SELECT|EXPLAIN|SHOW|DESCRIBE)\s/i', $sql)) {
|
||||
return view('admin.sql.index', [
|
||||
'results' => null,
|
||||
'columns' => [],
|
||||
'query' => $sql,
|
||||
'error' => '安全保护触发:本探针只允许执行 SELECT / SHOW 等只读查询!',
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$results = DB::select($sql);
|
||||
|
||||
// 提取表头
|
||||
$columns = [];
|
||||
if (! empty($results)) {
|
||||
$firstRow = (array) $results[0];
|
||||
$columns = array_keys($firstRow);
|
||||
}
|
||||
|
||||
return view('admin.sql.index', [
|
||||
'results' => $results,
|
||||
'columns' => $columns,
|
||||
'query' => $sql,
|
||||
'error' => null,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return view('admin.sql.index', [
|
||||
'results' => null,
|
||||
'columns' => [],
|
||||
'query' => $sql,
|
||||
'error' => 'SQL 执行发生异常: '.$e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,9 @@ class SystemController extends Controller
|
||||
|
||||
// 写入 Cache 保证极速读取
|
||||
$this->chatState->setSysParam($alias, $body);
|
||||
|
||||
// 同时清除 Sysparam 模型的内部缓存
|
||||
SysParam::clearCache($alias);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.system.edit')->with('success', '系统参数已成功更新并生效!');
|
||||
|
||||
@@ -52,17 +52,23 @@ class UserManagerController extends Controller
|
||||
return response()->json(['status' => 'error', 'message' => '权限不足:您无法修改同级或高级管理人员资料。'], 403);
|
||||
}
|
||||
|
||||
// 管理员级别 = 最高等级 + 1,后台编辑最高可设到管理员级别
|
||||
$adminLevel = (int) \App\Models\Sysparam::getValue('maxlevel', '15') + 1;
|
||||
|
||||
$validated = $request->validate([
|
||||
'sex' => 'sometimes|in:男,女,保密',
|
||||
'user_level' => 'sometimes|integer|min:0',
|
||||
'sex' => 'sometimes|integer|in:0,1,2',
|
||||
'user_level' => "sometimes|integer|min:0|max:{$adminLevel}",
|
||||
'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',
|
||||
'sign' => 'sometimes|string|max:255',
|
||||
'password' => 'nullable|string|min:6',
|
||||
]);
|
||||
|
||||
// 如果传了且没超权,直接赋予
|
||||
if (isset($validated['user_level'])) {
|
||||
// 不能把自己或别人提权到超过自己的等级
|
||||
// 不能把别人提权到超过自己的等级
|
||||
if ($validated['user_level'] > $currentUser->user_level && $currentUser->id !== $targetUser->id) {
|
||||
return response()->json(['status' => 'error', 'message' => '您不能将别人提升至超过您的等级!'], 403);
|
||||
}
|
||||
@@ -72,12 +78,21 @@ class UserManagerController extends Controller
|
||||
if (isset($validated['sex'])) {
|
||||
$targetUser->sex = $validated['sex'];
|
||||
}
|
||||
if (isset($validated['exp_num'])) {
|
||||
$targetUser->exp_num = $validated['exp_num'];
|
||||
}
|
||||
if (isset($validated['jjb'])) {
|
||||
$targetUser->jjb = $validated['jjb'];
|
||||
}
|
||||
if (isset($validated['meili'])) {
|
||||
$targetUser->meili = $validated['meili'];
|
||||
}
|
||||
if (array_key_exists('qianming', $validated)) {
|
||||
$targetUser->qianming = $validated['qianming'];
|
||||
}
|
||||
if (isset($validated['headface'])) {
|
||||
$targetUser->headface = $validated['headface'];
|
||||
}
|
||||
if (isset($validated['sign'])) {
|
||||
$targetUser->sign = $validated['sign'];
|
||||
}
|
||||
|
||||
if (! empty($validated['password'])) {
|
||||
$targetUser->password = Hash::make($validated['password']);
|
||||
|
||||
@@ -70,7 +70,7 @@ class AuthController extends Controller
|
||||
'last_ip' => $ip,
|
||||
'user_level' => 1, // 默认普通用户等级
|
||||
'sex' => 0, // 默认性别: 0保密 1男 2女
|
||||
// 如果原表里还有其他必填字段,在这里初始化默认值
|
||||
'usersf' => '1.GIF', // 默认头像
|
||||
]);
|
||||
|
||||
$this->performLogin($newUser, $ip);
|
||||
|
||||
@@ -16,12 +16,15 @@ use App\Events\UserJoined;
|
||||
use App\Events\UserLeft;
|
||||
use App\Http\Requests\SendMessageRequest;
|
||||
use App\Jobs\SaveMessageJob;
|
||||
use App\Models\Autoact;
|
||||
use App\Models\Room;
|
||||
use App\Models\Sysparam;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\MessageFilterService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ChatController extends Controller
|
||||
@@ -42,6 +45,9 @@ class ChatController extends Controller
|
||||
$room = Room::findOrFail($id);
|
||||
$user = Auth::user();
|
||||
|
||||
// 房间人气 +1(每次访问递增,复刻原版人气计数)
|
||||
$room->increment('visit_num');
|
||||
|
||||
// 1. 将当前用户加入到 Redis 房间在线列表
|
||||
$this->chatState->userJoin($id, $user->username, [
|
||||
'level' => $user->user_level,
|
||||
@@ -76,6 +82,18 @@ class ChatController extends Controller
|
||||
$data = $request->validated();
|
||||
$user = Auth::user();
|
||||
|
||||
// 0. 检查用户是否被禁言(Redis TTL 自动过期)
|
||||
$muteKey = "mute:{$id}:{$user->username}";
|
||||
if (Redis::exists($muteKey)) {
|
||||
$ttl = Redis::ttl($muteKey);
|
||||
$minutes = ceil($ttl / 60);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => "您正在禁言中,还需等待约 {$minutes} 分钟。",
|
||||
], 403);
|
||||
}
|
||||
|
||||
// 1. 过滤净化消息体
|
||||
$pureContent = $this->filter->filter($data['content'] ?? '');
|
||||
if (empty($pureContent)) {
|
||||
@@ -104,6 +122,13 @@ class ChatController extends Controller
|
||||
// 5. 丢进异步列队,慢慢持久化到 MySQL,保护数据库连接池
|
||||
SaveMessageJob::dispatch($messageData);
|
||||
|
||||
// 6. 如果用户更换了字体颜色,顺便保存到 s_color 字段,下次进入时恢复
|
||||
$chosenColor = $data['font_color'] ?? '';
|
||||
if ($chosenColor && $chosenColor !== ($user->s_color ?? '')) {
|
||||
$user->s_color = $chosenColor;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
@@ -120,19 +145,22 @@ class ChatController extends Controller
|
||||
return response()->json(['status' => 'error'], 401);
|
||||
}
|
||||
|
||||
// 1. 每次心跳 +1 点经验
|
||||
$user->exp_num += 1;
|
||||
|
||||
// 2. 检查等级计算:设定简单粗暴的平滑算式:需要经验=等级*等级*10
|
||||
// 例如:0级->0点;1级->10点;2级->40点;3级->90点;10级->1000点
|
||||
$currentLevel = $user->user_level;
|
||||
$requiredExpForNextLevel = ($currentLevel) * ($currentLevel) * 10;
|
||||
// 1. 每次心跳增加经验(可在 sysparam 后台配置)
|
||||
$expGain = (int) Sysparam::getValue('exp_per_heartbeat', '1');
|
||||
$user->exp_num += $expGain;
|
||||
|
||||
// 2. 使用 sysparam 表中可配置的等级-经验阈值计算等级
|
||||
// 管理员(superlevel 及以上)不参与自动升降级,等级由后台手动设置
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
$oldLevel = $user->user_level;
|
||||
$leveledUp = false;
|
||||
|
||||
if ($user->exp_num >= $requiredExpForNextLevel) {
|
||||
$user->user_level += 1;
|
||||
$leveledUp = true;
|
||||
if ($oldLevel < $superLevel) {
|
||||
$newLevel = Sysparam::calculateLevel($user->exp_num);
|
||||
if ($newLevel !== $oldLevel && $newLevel < $superLevel) {
|
||||
$user->user_level = $newLevel;
|
||||
$leveledUp = ($newLevel > $oldLevel);
|
||||
}
|
||||
}
|
||||
|
||||
$user->save(); // 存点入库
|
||||
@@ -167,12 +195,61 @@ class ChatController extends Controller
|
||||
SaveMessageJob::dispatch($sysMsg);
|
||||
}
|
||||
|
||||
// 5. 随机事件触发(复刻原版 autoact 系统,概率可在后台配置)
|
||||
$autoEvent = null;
|
||||
$eventChance = (int) Sysparam::getValue('auto_event_chance', '10');
|
||||
if ($eventChance > 0 && rand(1, 100) <= $eventChance) {
|
||||
$autoEvent = Autoact::randomEvent();
|
||||
if ($autoEvent) {
|
||||
// 应用经验/金币变化(不低于 0)
|
||||
if ($autoEvent->exp_change !== 0) {
|
||||
$user->exp_num = max(0, $user->exp_num + $autoEvent->exp_change);
|
||||
}
|
||||
if ($autoEvent->jjb_change !== 0) {
|
||||
$user->jjb = max(0, ($user->jjb ?? 0) + $autoEvent->jjb_change);
|
||||
}
|
||||
$user->save();
|
||||
|
||||
// 重新计算等级(经验可能因事件而变化,但管理员不参与自动升降级)
|
||||
if ($user->user_level < $superLevel) {
|
||||
$recalcLevel = Sysparam::calculateLevel($user->exp_num);
|
||||
if ($recalcLevel !== $user->user_level && $recalcLevel < $superLevel) {
|
||||
$user->user_level = $recalcLevel;
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
// 广播随机事件消息到聊天室
|
||||
$eventMsg = [
|
||||
'id' => $this->chatState->nextMessageId($id),
|
||||
'room_id' => $id,
|
||||
'from_user' => '星海小博士',
|
||||
'to_user' => '大家',
|
||||
'content' => $autoEvent->renderText($user->username),
|
||||
'is_secret' => false,
|
||||
'font_color' => match ($autoEvent->event_type) {
|
||||
'good' => '#16a34a', // 绿色(好运)
|
||||
'bad' => '#dc2626', // 红色(坏运)
|
||||
default => '#7c3aed', // 紫色(中性)
|
||||
},
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($id, $eventMsg);
|
||||
broadcast(new MessageSent($id, $eventMsg));
|
||||
SaveMessageJob::dispatch($eventMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'exp_num' => $user->exp_num,
|
||||
'user_level' => $user->user_level,
|
||||
'leveled_up' => $leveledUp,
|
||||
'is_max_level' => $user->user_level >= $superLevel,
|
||||
'auto_event' => $autoEvent ? $autoEvent->renderText($user->username) : null,
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -197,4 +274,105 @@ class ChatController extends Controller
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用头像列表(返回 JSON)
|
||||
* 扫描 /public/images/headface/ 目录,返回所有可用头像文件名
|
||||
*/
|
||||
public function headfaceList(): JsonResponse
|
||||
{
|
||||
$dir = public_path('images/headface');
|
||||
$files = [];
|
||||
|
||||
if (is_dir($dir)) {
|
||||
$all = scandir($dir);
|
||||
foreach ($all as $file) {
|
||||
// 只包含图片文件
|
||||
if (preg_match('/\.(gif|jpg|jpeg|png|bmp)$/i', $file)) {
|
||||
$files[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自然排序(1, 2, 3... 10, 11...)
|
||||
natsort($files);
|
||||
|
||||
return response()->json(['headfaces' => array_values($files)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改头像(原版 fw.asp 功能)
|
||||
* 用户选择一个头像文件名,更新到 usersf 字段
|
||||
*/
|
||||
public function changeAvatar(Request $request): JsonResponse
|
||||
{
|
||||
$user = auth()->user();
|
||||
$headface = $request->input('headface', '');
|
||||
|
||||
if (empty($headface)) {
|
||||
return response()->json(['status' => 'error', 'message' => '请选择一个头像'], 422);
|
||||
}
|
||||
|
||||
// 验证文件确实存在
|
||||
if (! file_exists(public_path('images/headface/'.$headface))) {
|
||||
return response()->json(['status' => 'error', 'message' => '头像文件不存在'], 422);
|
||||
}
|
||||
|
||||
// 更新用户头像
|
||||
$user->usersf = $headface;
|
||||
$user->save();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => '头像修改成功!',
|
||||
'headface' => $headface,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置房间公告/祝福语(滚动显示在聊天室顶部)
|
||||
* 需要房间主人或等级达到 level_announcement 配置值
|
||||
*
|
||||
* @param int $id 房间ID
|
||||
*/
|
||||
public function setAnnouncement(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$user = Auth::user();
|
||||
$room = Room::findOrFail($id);
|
||||
|
||||
// 权限检查:房间主人 或 等级 >= level_announcement
|
||||
$requiredLevel = (int) Sysparam::getValue('level_announcement', '10');
|
||||
if ($user->username !== $room->master && $user->user_level < $requiredLevel) {
|
||||
return response()->json(['status' => 'error', 'message' => '权限不足,无法修改公告'], 403);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'announcement' => 'required|string|max:500',
|
||||
]);
|
||||
|
||||
$room->announcement = $request->input('announcement');
|
||||
$room->save();
|
||||
|
||||
// 广播公告更新到所有在线用户
|
||||
$sysMsg = [
|
||||
'id' => $this->chatState->nextMessageId($id),
|
||||
'room_id' => $id,
|
||||
'from_user' => '系统公告',
|
||||
'to_user' => '大家',
|
||||
'content' => "📢 {$user->username} 更新了房间公告:{$room->announcement}",
|
||||
'is_secret' => false,
|
||||
'font_color' => '#cc0000',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($id, $sysMsg);
|
||||
broadcast(new MessageSent($id, $sysMsg));
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => '公告已更新!',
|
||||
'announcement' => $room->announcement,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:钓鱼小游戏控制器
|
||||
* 复刻原版 ASP 聊天室 diaoyu/ 目录下的钓鱼功能
|
||||
* 简化掉鱼竿道具系统,用 Redis 控制冷却,随机奖惩经验/金币
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\MessageSent;
|
||||
use App\Models\Sysparam;
|
||||
use App\Services\ChatStateService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class FishingController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 抛竿 — 检查冷却和金币,扣除金币,返回随机等待时间
|
||||
*
|
||||
* @param int $id 房间ID
|
||||
*/
|
||||
public function cast(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (! $user) {
|
||||
return response()->json(['status' => 'error', 'message' => '请先登录'], 401);
|
||||
}
|
||||
|
||||
// 1. 检查冷却时间(Redis TTL)
|
||||
$cooldownKey = "fishing:cd:{$user->id}";
|
||||
if (Redis::exists($cooldownKey)) {
|
||||
$ttl = Redis::ttl($cooldownKey);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => "钓鱼冷却中,还需等待 {$ttl} 秒。",
|
||||
'cooldown' => $ttl,
|
||||
], 429);
|
||||
}
|
||||
|
||||
// 2. 检查金币是否足够
|
||||
$cost = (int) Sysparam::getValue('fishing_cost', '5');
|
||||
if (($user->jjb ?? 0) < $cost) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => "金币不足!钓鱼需要 {$cost} 金币,您当前只有 {$user->jjb} 金币。",
|
||||
], 422);
|
||||
}
|
||||
|
||||
// 3. 扣除金币
|
||||
$user->jjb = max(0, ($user->jjb ?? 0) - $cost);
|
||||
$user->save();
|
||||
|
||||
// 4. 设置"正在钓鱼"标记(防止重复抛竿,30秒后自动过期)
|
||||
Redis::setex("fishing:active:{$user->id}", 30, time());
|
||||
|
||||
// 5. 计算随机等待时间
|
||||
$waitMin = (int) Sysparam::getValue('fishing_wait_min', '8');
|
||||
$waitMax = (int) Sysparam::getValue('fishing_wait_max', '15');
|
||||
$waitTime = rand($waitMin, $waitMax);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => "已花费 {$cost} 金币,鱼竿已抛出!等待鱼儿上钩...",
|
||||
'wait_time' => $waitTime,
|
||||
'cost' => $cost,
|
||||
'jjb' => $user->jjb,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收竿 — 随机计算钓鱼结果,更新经验/金币,广播到聊天室
|
||||
*
|
||||
* @param int $id 房间ID
|
||||
*/
|
||||
public function reel(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (! $user) {
|
||||
return response()->json(['status' => 'error', 'message' => '请先登录'], 401);
|
||||
}
|
||||
|
||||
// 1. 检查是否有"正在钓鱼"标记
|
||||
$activeKey = "fishing:active:{$user->id}";
|
||||
if (! Redis::exists($activeKey)) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => '您还没有抛竿,或者鱼已经跑了!',
|
||||
], 422);
|
||||
}
|
||||
|
||||
// 清除钓鱼标记
|
||||
Redis::del($activeKey);
|
||||
|
||||
// 2. 设置冷却时间
|
||||
$cooldown = (int) Sysparam::getValue('fishing_cooldown', '300');
|
||||
Redis::setex("fishing:cd:{$user->id}", $cooldown, time());
|
||||
|
||||
// 3. 随机决定钓鱼结果
|
||||
$result = $this->randomFishResult();
|
||||
|
||||
// 4. 更新用户经验和金币(不低于 0)
|
||||
if ($result['exp'] !== 0) {
|
||||
$user->exp_num = max(0, ($user->exp_num ?? 0) + $result['exp']);
|
||||
}
|
||||
if ($result['jjb'] !== 0) {
|
||||
$user->jjb = max(0, ($user->jjb ?? 0) + $result['jjb']);
|
||||
}
|
||||
$user->save();
|
||||
|
||||
// 5. 广播钓鱼结果到聊天室
|
||||
$sysMsg = [
|
||||
'id' => $this->chatState->nextMessageId($id),
|
||||
'room_id' => $id,
|
||||
'from_user' => '钓鱼播报',
|
||||
'to_user' => '大家',
|
||||
'content' => "{$result['emoji']} {$user->username}{$result['message']}",
|
||||
'is_secret' => false,
|
||||
'font_color' => $result['exp'] >= 0 ? '#16a34a' : '#dc2626',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($id, $sysMsg);
|
||||
broadcast(new MessageSent($id, $sysMsg));
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'result' => $result,
|
||||
'exp_num' => $user->exp_num,
|
||||
'jjb' => $user->jjb,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机钓鱼结果(复刻原版概率分布)
|
||||
*
|
||||
* @return array{emoji: string, message: string, exp: int, jjb: int}
|
||||
*/
|
||||
private function randomFishResult(): array
|
||||
{
|
||||
$roll = rand(1, 100);
|
||||
|
||||
// 概率分布(总计 100%)
|
||||
// 1-15: 大鲨鱼 (+100exp, +20金)
|
||||
// 16-30: 娃娃鱼 (+0exp, +30金)
|
||||
// 31-50: 大草鱼 (+50exp)
|
||||
// 51-70: 小鲤鱼 (+50exp, +10金)
|
||||
// 71-85: 落水 (-50exp)
|
||||
// 86-95: 被打 (-20exp, -3金)
|
||||
// 96-100:大丰收 (+150exp, +50金)
|
||||
|
||||
return match (true) {
|
||||
$roll <= 15 => [
|
||||
'emoji' => '🦈',
|
||||
'message' => '钓到一条大鲨鱼!增加经验100、金币20',
|
||||
'exp' => 100,
|
||||
'jjb' => 20,
|
||||
],
|
||||
$roll <= 30 => [
|
||||
'emoji' => '🐟',
|
||||
'message' => '钓到一条娃娃鱼,到集市卖得30个金币',
|
||||
'exp' => 0,
|
||||
'jjb' => 30,
|
||||
],
|
||||
$roll <= 50 => [
|
||||
'emoji' => '🐠',
|
||||
'message' => '钓到一只大草鱼,吃下增加经验50',
|
||||
'exp' => 50,
|
||||
'jjb' => 0,
|
||||
],
|
||||
$roll <= 70 => [
|
||||
'emoji' => '🐡',
|
||||
'message' => '钓到一条小鲤鱼,增加经验50、金币10',
|
||||
'exp' => 50,
|
||||
'jjb' => 10,
|
||||
],
|
||||
$roll <= 85 => [
|
||||
'emoji' => '💧',
|
||||
'message' => '鱼没钓到,摔到河里经验减少50',
|
||||
'exp' => -50,
|
||||
'jjb' => 0,
|
||||
],
|
||||
$roll <= 95 => [
|
||||
'emoji' => '👊',
|
||||
'message' => '偷钓鱼塘被主人发现,一阵殴打!经验减少20、金币减少3',
|
||||
'exp' => -20,
|
||||
'jjb' => -3,
|
||||
],
|
||||
default => [
|
||||
'emoji' => '🎉',
|
||||
'message' => '运气爆棚!钓到大鲨鱼、大草鱼、小鲤鱼各一条!经验+150,金币+50!',
|
||||
'exp' => 150,
|
||||
'jjb' => 50,
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class LeaderboardController extends Controller
|
||||
|
||||
// 1. 境界榜 (以 user_level 为尊)
|
||||
$topLevels = Cache::remember('leaderboard:top_levels', $ttl, function () {
|
||||
return User::select('id', 'username', 'headface', 'user_level', 'sex')
|
||||
return User::select('id', 'username', 'usersf', 'user_level', 'sex')
|
||||
->where('user_level', '>', 0)
|
||||
->orderByDesc('user_level')
|
||||
->orderBy('id')
|
||||
@@ -38,7 +38,7 @@ class LeaderboardController extends Controller
|
||||
|
||||
// 2. 修为榜 (以 exp_num 为尊)
|
||||
$topExp = Cache::remember('leaderboard:top_exp', $ttl, function () {
|
||||
return User::select('id', 'username', 'headface', 'exp_num', 'sex', 'user_level')
|
||||
return User::select('id', 'username', 'usersf', 'exp_num', 'sex', 'user_level')
|
||||
->where('exp_num', '>', 0)
|
||||
->orderByDesc('exp_num')
|
||||
->orderBy('id')
|
||||
@@ -48,7 +48,7 @@ class LeaderboardController extends Controller
|
||||
|
||||
// 3. 财富榜 (以 jjb-交友币 为尊)
|
||||
$topWealth = Cache::remember('leaderboard:top_wealth', $ttl, function () {
|
||||
return User::select('id', 'username', 'headface', 'jjb', 'sex', 'user_level')
|
||||
return User::select('id', 'username', 'usersf', 'jjb', 'sex', 'user_level')
|
||||
->where('jjb', '>', 0)
|
||||
->orderByDesc('jjb')
|
||||
->orderBy('id')
|
||||
@@ -58,7 +58,7 @@ class LeaderboardController extends Controller
|
||||
|
||||
// 4. 魅力榜 (以 meili 为尊)
|
||||
$topCharm = Cache::remember('leaderboard:top_charm', $ttl, function () {
|
||||
return User::select('id', 'username', 'headface', 'meili', 'sex', 'user_level')
|
||||
return User::select('id', 'username', 'usersf', 'meili', 'sex', 'user_level')
|
||||
->where('meili', '>', 0)
|
||||
->orderByDesc('meili')
|
||||
->orderBy('id')
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
/**
|
||||
* 文件功能:用户中心与管理控制器
|
||||
* 接管原版 USERinfo.ASP, USERSET.ASP, chpasswd.asp, KILLUSER.ASP
|
||||
* 接管原版 USERinfo.ASP, USERSET.ASP, chpasswd.asp, KILLUSER.ASP, LOCKIP.ASP
|
||||
*
|
||||
* 权限等级通过 sysparam 表动态配置:
|
||||
* level_kick - 踢人所需等级
|
||||
* level_mute - 禁言所需等级
|
||||
* level_ban - 封号所需等级
|
||||
* level_banip - 封IP所需等级
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @version 1.1.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
@@ -16,11 +22,13 @@ use App\Events\UserMuted;
|
||||
use App\Http\Requests\ChangePasswordRequest;
|
||||
use App\Http\Requests\UpdateProfileRequest;
|
||||
use App\Models\Room;
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
@@ -36,7 +44,11 @@ class UserController extends Controller
|
||||
'username' => $user->username,
|
||||
'sex' => $user->sex,
|
||||
'headface' => $user->headface,
|
||||
'usersf' => $user->usersf,
|
||||
'user_level' => $user->user_level,
|
||||
'exp_num' => $user->exp_num ?? 0,
|
||||
'jjb' => $user->jjb ?? 0,
|
||||
'qianming' => $user->qianming,
|
||||
'sign' => $user->sign ?? '这个人很懒,什么都没留下。',
|
||||
'created_at' => $user->created_at->format('Y-m-d'),
|
||||
]);
|
||||
@@ -85,7 +97,41 @@ class UserController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员/房主操作:踢出房间 (对应 KILLUSER.ASP)
|
||||
* 通用权限校验:检查操作者是否有权操作目标用户
|
||||
*
|
||||
* @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
|
||||
{
|
||||
@@ -96,54 +142,113 @@ class UserController extends Controller
|
||||
return response()->json(['status' => 'error', 'message' => '缺少房间参数。'], 422);
|
||||
}
|
||||
|
||||
$room = Room::findOrFail($roomId);
|
||||
|
||||
// 鉴权:操作者要是房间房主或者系统超管
|
||||
if ($room->master !== $operator->username && $operator->user_level < 15) {
|
||||
return response()->json(['status' => 'error', 'message' => '权限不足,无法执行踢出操作。'], 403);
|
||||
$result = $this->checkPermission($operator, $username, $roomId, 'level_kick', '踢出');
|
||||
if ($result instanceof JsonResponse) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$targetUser = User::where('username', $username)->first();
|
||||
if (! $targetUser) {
|
||||
return response()->json(['status' => 'error', 'message' => '目标用户不存在。'], 404);
|
||||
}
|
||||
// 广播踢出事件
|
||||
broadcast(new UserKicked($roomId, $result['target']->username, "管理员 [{$operator->username}] 将 [{$result['target']->username}] 踢出了聊天室。"));
|
||||
|
||||
// 防误伤高管
|
||||
if ($targetUser->user_level >= 15 && $operator->user_level < 15) {
|
||||
return response()->json(['status' => 'error', 'message' => '权限不足,无法踢出同级或高级管理人员。'], 403);
|
||||
}
|
||||
|
||||
// 核心动作:向频道内所有人发送包含“某某踢出某某”的事件
|
||||
broadcast(new UserKicked($roomId, $targetUser->username, "管理员 [{$operator->username}] 将 [{$targetUser->username}] 踢出了聊天室。"));
|
||||
|
||||
return response()->json(['status' => 'success', 'message' => "已成功将 {$targetUser->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 = $request->input('duration', 5); // 默认封停分钟数
|
||||
$duration = (int) $request->input('duration', 5);
|
||||
|
||||
if (! $roomId) {
|
||||
return response()->json(['status' => 'error', 'message' => '缺少房间参数。'], 422);
|
||||
}
|
||||
|
||||
$room = Room::findOrFail($roomId);
|
||||
|
||||
// 此处只做简单鉴权演示,和踢人一致
|
||||
if ($room->master !== $operator->username && $operator->user_level < 15) {
|
||||
return response()->json(['status' => 'error', 'message' => '权限不足,无法执行禁言操作。'], 403);
|
||||
$result = $this->checkPermission($operator, $username, $roomId, 'level_mute', '禁言');
|
||||
if ($result instanceof JsonResponse) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 后续可以在 Redis 中写入一个 `mute:{$username}` 并附带 `TTL` 以在后台拦截
|
||||
// 写入 Redis 禁言标记,TTL = 禁言分钟数 * 60
|
||||
Redis::setex("mute:{$roomId}:{$username}", $duration * 60, json_encode([
|
||||
'operator' => $operator->username,
|
||||
'reason' => '管理员禁言',
|
||||
'until' => now()->addMinutes($duration)->toDateTimeString(),
|
||||
]));
|
||||
|
||||
// 立刻向房间发送 Muted 事件
|
||||
// 广播禁言事件
|
||||
broadcast(new UserMuted($roomId, $username, $duration));
|
||||
|
||||
return response()->json(['status' => 'success', 'message' => "已对 {$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}。"]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user