Files
chatroom/app/Http/Controllers/FishingController.php
lkddi 0c5e218aa8 功能:新增用户积分流水系统
- 新建 user_currency_logs 流水表 (Migration)
- App\Enums\CurrencySource 来源枚举(可扩展)
- App\Models\UserCurrencyLog 流水模型
- App\Services\UserCurrencyService 统一积分变更服务
- FishingController:抛竿/收竿接入流水记录
- AutoSaveExp:自动存点接入流水记录
- Admin/UserManagerController:管理员调整接入流水记录
- LeaderboardController:新增今日三榜(经验/金币/魅力)+ 个人流水日志页
- Admin/CurrencyStatsController:后台活动统计页
- views:新增个人日志页、后台统计页;排行榜新增今日榜数据传递
- routes:新增个人日志路由 /my/currency-logs、后台路由 /admin/currency-stats
2026-02-28 12:49:26 +08:00

233 lines
7.8 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
/**
* 文件功能:钓鱼小游戏控制器
* 复刻原版 ASP 聊天室 diaoyu/ 目录下的钓鱼功能
* 简化掉鱼竿道具系统,用 Redis 控制冷却,随机奖惩经验/金币
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers;
use App\Events\MessageSent;
use App\Enums\CurrencySource;
use App\Models\Sysparam;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
use App\Services\VipService;
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,
private readonly VipService $vipService,
private readonly UserCurrencyService $currencyService,
) {}
/**
* 抛竿 — 检查冷却和金币,扣除金币,返回随机等待时间
*
* @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. 扣除金币(通过统一积分服务记录流水)
$this->currencyService->change(
$user,
'gold',
-$cost,
CurrencySource::FISHING_COST,
"钓鱼抛竿消耗 {$cost} 金币",
$id,
);
$user->refresh(); // 刷新本地模型service 已原子更新)
// 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. 通过统一积分服务更新经验和金币,写入流水
$expMul = $this->vipService->getExpMultiplier($user);
$jjbMul = $this->vipService->getJjbMultiplier($user);
if ($result['exp'] !== 0) {
$finalExp = $result['exp'] > 0 ? (int) round($result['exp'] * $expMul) : $result['exp'];
$this->currencyService->change(
$user, 'exp', $finalExp, CurrencySource::FISHING_GAIN,
"钓鱼收竿:{$result['message']}", $id,
);
}
if ($result['jjb'] !== 0) {
$finalJjb = $result['jjb'] > 0 ? (int) round($result['jjb'] * $jjbMul) : $result['jjb'];
$this->currencyService->change(
$user, 'gold', $finalJjb, CurrencySource::FISHING_GAIN,
"钓鱼收竿:{$result['message']}", $id,
);
}
$user->refresh(); // 刷新获取最新余额
// 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,
],
};
}
}