2026-03-03 19:29:43 +08:00
|
|
|
|
<?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}!"
|
2026-03-04 15:00:02 +08:00
|
|
|
|
. "获得 💰" . number_format($reward) . " 金币!";
|
2026-03-03 19:29:43 +08:00
|
|
|
|
$color = $box->box_type === 'rare' ? '#c4b5fd' : '#34d399';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$content = "☠️【黑化陷阱】haha!【{$username}】 中了神秘黑化箱的陷阱!"
|
2026-03-04 15:00:02 +08:00
|
|
|
|
. "被扣除 💰" . number_format(abs($reward)) . " 金币!点背~";
|
2026-03-03 19:29:43 +08:00
|
|
|
|
$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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|