🪙 在多数平台/字体上渲染为银灰色,与「金币」语义不符; 💰 各平台均渲染为金黄色,更直观传达金币概念。 涉及文件(43处): - app/Jobs:百家乐、赛马结算广播 - app/Http/Controllers:管理员命令、红包、老虎机、神秘箱子 - app/Listeners - resources/views:聊天室各游戏面板、商店、toolbar、后台页面等
175 lines
5.8 KiB
PHP
175 lines
5.8 KiB
PHP
<?php
|
||
|
||
/**
|
||
* 文件功能:神秘箱子前台控制器
|
||
*
|
||
* 提供神秘箱子相关接口:
|
||
* - /mystery-box/status 查询当前可领取的箱子(给前端轮询)
|
||
* - /mystery-box/claim 用户发送暗号领取箱子
|
||
*
|
||
* 领取流程:
|
||
* 1. 用户在聊天框发送暗号(前端拦截后调用此接口)
|
||
* 2. 验证暗号匹配、箱子未过期、未已领取
|
||
* 3. 随机奖励金额(trap=扣,其余=加)
|
||
* 4. 写货币流水日志
|
||
* 5. 公屏广播结果(中奖/踩雷)
|
||
*
|
||
* @author ChatRoom Laravel
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Enums\CurrencySource;
|
||
use App\Events\MessageSent;
|
||
use App\Jobs\SaveMessageJob;
|
||
use App\Models\GameConfig;
|
||
use App\Models\MysteryBox;
|
||
use App\Models\MysteryBoxClaim;
|
||
use App\Services\ChatStateService;
|
||
use App\Services\UserCurrencyService;
|
||
use Illuminate\Http\JsonResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
class MysteryBoxController extends Controller
|
||
{
|
||
public function __construct(
|
||
private readonly UserCurrencyService $currency,
|
||
private readonly ChatStateService $chatState,
|
||
) {}
|
||
|
||
/**
|
||
* 查询当前可领取的箱子状态(给前端轮询/显示用)。
|
||
*/
|
||
public function status(): JsonResponse
|
||
{
|
||
if (! GameConfig::isEnabled('mystery_box')) {
|
||
return response()->json(['active' => false]);
|
||
}
|
||
|
||
$box = MysteryBox::currentOpenBox();
|
||
|
||
if (! $box) {
|
||
return response()->json(['active' => false]);
|
||
}
|
||
|
||
// 计算剩余时间
|
||
$secondsLeft = $box->expires_at ? max(0, now()->diffInSeconds($box->expires_at, false)) : null;
|
||
|
||
return response()->json([
|
||
'active' => true,
|
||
'box_id' => $box->id,
|
||
'box_type' => $box->box_type,
|
||
'type_name' => $box->typeName(),
|
||
'type_emoji' => $box->typeEmoji(),
|
||
'passcode' => $box->passcode,
|
||
'seconds_left' => $secondsLeft,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 用户用暗号领取箱子。
|
||
*/
|
||
public function claim(Request $request): JsonResponse
|
||
{
|
||
if (! GameConfig::isEnabled('mystery_box')) {
|
||
return response()->json(['ok' => false, 'message' => '神秘箱子功能未开放。']);
|
||
}
|
||
|
||
$passcode = strtoupper(trim((string) $request->input('passcode', '')));
|
||
|
||
if ($passcode === '') {
|
||
return response()->json(['ok' => false, 'message' => '请输入暗号。']);
|
||
}
|
||
|
||
$user = $request->user();
|
||
|
||
return DB::transaction(function () use ($user, $passcode): JsonResponse {
|
||
// 查找匹配暗号的可领取箱子(加锁防并发)
|
||
$box = MysteryBox::query()
|
||
->where('passcode', $passcode)
|
||
->where('status', 'open')
|
||
->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))
|
||
->lockForUpdate()
|
||
->first();
|
||
|
||
if (! $box) {
|
||
return response()->json(['ok' => false, 'message' => '暗号不正确,或箱子已被领走/已过期。']);
|
||
}
|
||
|
||
// ① 随机奖励金额
|
||
$reward = $box->rollReward();
|
||
|
||
// ② 货币变更
|
||
$source = $reward >= 0 ? CurrencySource::MYSTERY_BOX : CurrencySource::MYSTERY_BOX_TRAP;
|
||
$remark = $reward >= 0
|
||
? "神秘箱子【{$box->typeName()}】奖励"
|
||
: "神秘箱子【黑化箱】陷阱扣除";
|
||
|
||
$this->currency->change($user, 'gold', $reward, $source, $remark, $box->room_id);
|
||
|
||
// ③ 写领取记录 + 更新箱子状态
|
||
MysteryBoxClaim::create([
|
||
'mystery_box_id' => $box->id,
|
||
'user_id' => $user->id,
|
||
'reward_amount' => $reward,
|
||
]);
|
||
|
||
$box->update(['status' => 'claimed']);
|
||
|
||
// ④ 公屏广播结果
|
||
$user->refresh();
|
||
$this->broadcastResult($box, $user->username, $reward);
|
||
|
||
return response()->json([
|
||
'ok' => true,
|
||
'reward' => $reward,
|
||
'balance' => $user->jjb ?? 0,
|
||
'message' => $reward >= 0
|
||
? "🎉 恭喜!开箱获得 +{$reward} 金币!"
|
||
: "☠️ 中了黑化陷阱!扣除 " . abs($reward) . ' 金币!',
|
||
]);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 公屏广播开箱结果。
|
||
*
|
||
* @param MysteryBox $box 箱子实例
|
||
* @param string $username 领取者用户名
|
||
* @param int $reward 奖励金额(正/负)
|
||
*/
|
||
private function broadcastResult(MysteryBox $box, string $username, int $reward): void
|
||
{
|
||
$emoji = $box->typeEmoji();
|
||
$typeName = $box->typeName();
|
||
|
||
if ($reward >= 0) {
|
||
$content = "{$emoji}【开箱播报】恭喜 【{$username}】 抢到了神秘{$typeName}!"
|
||
. "获得 💰" . number_format($reward) . " 金币!";
|
||
$color = $box->box_type === 'rare' ? '#c4b5fd' : '#34d399';
|
||
} else {
|
||
$content = "☠️【黑化陷阱】haha!【{$username}】 中了神秘黑化箱的陷阱!"
|
||
. "被扣除 💰" . number_format(abs($reward)) . " 金币!点背~";
|
||
$color = '#f87171';
|
||
}
|
||
|
||
$msg = [
|
||
'id' => $this->chatState->nextMessageId(1),
|
||
'room_id' => 1,
|
||
'from_user' => '系统传音',
|
||
'to_user' => '大家',
|
||
'content' => $content,
|
||
'is_secret' => false,
|
||
'font_color' => $color,
|
||
'action' => '大声宣告',
|
||
'sent_at' => now()->toDateTimeString(),
|
||
];
|
||
|
||
$this->chatState->pushMessage(1, $msg);
|
||
broadcast(new MessageSent(1, $msg));
|
||
SaveMessageJob::dispatch($msg);
|
||
}
|
||
}
|