功能:钓鱼游戏后台管理系统

一、钓鱼全局开关
- 钓鱼纳入 GameConfig(game_key=fishing),游戏管理页可一键开关
- cast() 接口加开关校验,关闭时返回 403 友好提示
- GameConfigSeeder 新增 fishing 配置(含4个参数)

二、钓鱼事件数据库化
- 新建 fishing_events 表(emoji/name/message/exp/jjb/weight/is_active/sort)
- FishingEvent 模型含 rollOne() 加权随机方法
- FishingEventSeeder 填充7条初始事件(经验降低、金币提升)
- FishingController::randomFishResult() 改为读数据库事件

三、钓鱼参数迁移至 GameConfig
- fishing_cost/wait_min/wait_max/cooldown 改为 GameConfig::param() 读取
- 保留 Sysparam fallback 兼容旧数据

四、后台管理页面
- 新建 FishingEventController(CRUD + AJAX toggle)
- 新建 admin/fishing/index.blade.php(事件列表+概率显示+编辑弹窗)
- 侧边栏「游戏管理」下方新增「🎣 钓鱼事件」入口
- 游戏管理视图 gameParamLabels 新增钓鱼参数标签
This commit is contained in:
2026-03-03 16:46:36 +08:00
parent 783afe0677
commit 03ec3a9fbb
11 changed files with 735 additions and 50 deletions

View File

@@ -0,0 +1,108 @@
<?php
/**
* 文件功能:钓鱼事件后台管理控制器
* 提供钓鱼事件的列表展示、创建、编辑、删除、启用/禁用功能
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\FishingEvent;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class FishingEventController extends Controller
{
/**
* 显示所有钓鱼事件列表
*/
public function index(): View
{
$events = FishingEvent::orderBy('sort')->orderBy('id')->get();
$totalWeight = $events->where('is_active', true)->sum('weight');
return view('admin.fishing.index', compact('events', 'totalWeight'));
}
/**
* 创建新钓鱼事件
*/
public function store(Request $request): RedirectResponse
{
$data = $request->validate([
'emoji' => 'required|string|max:10',
'name' => 'required|string|max:100',
'message' => 'required|string|max:255',
'exp' => 'required|integer',
'jjb' => 'required|integer',
'weight' => 'required|integer|min:1|max:9999',
'sort' => 'required|integer|min:0',
'is_active' => 'boolean',
]);
$data['is_active'] = $request->boolean('is_active', true);
FishingEvent::create($data);
return redirect()->route('admin.fishing.index')->with('success', '钓鱼事件已添加!');
}
/**
* 更新钓鱼事件
*
* @param FishingEvent $fishing 路由模型绑定
*/
public function update(Request $request, FishingEvent $fishing): RedirectResponse
{
$data = $request->validate([
'emoji' => 'required|string|max:10',
'name' => 'required|string|max:100',
'message' => 'required|string|max:255',
'exp' => 'required|integer',
'jjb' => 'required|integer',
'weight' => 'required|integer|min:1|max:9999',
'sort' => 'required|integer|min:0',
'is_active' => 'boolean',
]);
$data['is_active'] = $request->boolean('is_active');
$fishing->update($data);
return redirect()->route('admin.fishing.index')->with('success', "事件「{$fishing->name}」已更新!");
}
/**
* 切换事件启用/禁用状态AJAX
*
* @param FishingEvent $fishing 路由模型绑定
*/
public function toggle(FishingEvent $fishing): JsonResponse
{
$fishing->update(['is_active' => ! $fishing->is_active]);
return response()->json([
'ok' => true,
'is_active' => $fishing->is_active,
'message' => $fishing->is_active ? "{$fishing->name}」已启用" : "{$fishing->name}」已禁用",
]);
}
/**
* 删除钓鱼事件
*
* @param FishingEvent $fishing 路由模型绑定
*/
public function destroy(FishingEvent $fishing): RedirectResponse
{
$name = $fishing->name;
$fishing->delete();
return redirect()->route('admin.fishing.index')->with('success', "事件「{$name}」已删除!");
}
}

View File

@@ -18,6 +18,8 @@ namespace App\Http\Controllers;
use App\Enums\CurrencySource;
use App\Events\MessageSent;
use App\Models\FishingEvent;
use App\Models\GameConfig;
use App\Models\Sysparam;
use App\Services\ChatStateService;
use App\Services\ShopService;
@@ -56,6 +58,11 @@ class FishingController extends Controller
return response()->json(['status' => 'error', 'message' => '请先登录'], 401);
}
// 检查钓鱼全局开关
if (! GameConfig::isEnabled('fishing')) {
return response()->json(['status' => 'error', 'message' => '钓鱼功能暂未开放。'], 403);
}
// 1. 检查冷却时间Redis TTL
$cooldownKey = "fishing:cd:{$user->id}";
if (Redis::exists($cooldownKey)) {
@@ -69,7 +76,7 @@ class FishingController extends Controller
}
// 2. 检查金币是否足够
$cost = (int) Sysparam::getValue('fishing_cost', '5');
$cost = (int) (GameConfig::param('fishing', 'fishing_cost') ?? Sysparam::getValue('fishing_cost', '5'));
if (($user->jjb ?? 0) < $cost) {
return response()->json([
'status' => 'error',
@@ -87,8 +94,8 @@ class FishingController extends Controller
$user->refresh();
// 4. 生成一次性 token存入 RedisTTL = 等待时间 + 收竿窗口 + 缓冲)
$waitMin = (int) Sysparam::getValue('fishing_wait_min', '8');
$waitMax = (int) Sysparam::getValue('fishing_wait_max', '15');
$waitMin = (int) (GameConfig::param('fishing', 'fishing_wait_min') ?? Sysparam::getValue('fishing_wait_min', '8'));
$waitMax = (int) (GameConfig::param('fishing', 'fishing_wait_max') ?? Sysparam::getValue('fishing_wait_max', '15'));
$waitTime = rand($waitMin, $waitMax);
$token = Str::random(32);
$tokenKey = "fishing:token:{$user->id}";
@@ -169,7 +176,7 @@ class FishingController extends Controller
Redis::del($tokenKey);
// 2. 设置冷却时间
$cooldown = (int) Sysparam::getValue('fishing_cooldown', '300');
$cooldown = (int) (GameConfig::param('fishing', 'fishing_cooldown') ?? Sysparam::getValue('fishing_cooldown', '300'));
Redis::setex("fishing:cd:{$user->id}", $cooldown, time());
// 3. 随机决定钓鱼结果
@@ -229,57 +236,31 @@ class FishingController extends Controller
}
/**
* 随机钓鱼结果(复刻原版概率分布
* 随机钓鱼结果(从数据库 fishing_events 加权随机抽取
*
* 若数据库中无激活事件,回退到兜底结果。
*
* @return array{emoji: string, message: string, exp: int, jjb: int}
*/
private function randomFishResult(): array
{
$roll = rand(1, 100);
$event = FishingEvent::rollOne();
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,
],
};
// 数据库无事件时的兜底
if (! $event) {
return [
'emoji' => '🐟',
'message' => '钓到一条小鱼,获得金币10',
'exp' => 0,
'jjb' => 10,
];
}
return [
'emoji' => $event->emoji,
'message' => $event->message,
'exp' => $event->exp,
'jjb' => $event->jjb,
];
}
}