收口聊天室安全边界并优化特效生命周期
This commit is contained in:
@@ -9,9 +9,12 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\EffectBroadcast;
|
||||
use App\Events\MessageSent;
|
||||
use App\Models\Room;
|
||||
use App\Models\ShopItem;
|
||||
use App\Models\UserPurchase;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\ShopService;
|
||||
use App\Support\ChatContentSanitizer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -23,6 +26,7 @@ class ShopController extends Controller
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ShopService $shopService,
|
||||
private readonly ChatStateService $chatState,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -85,16 +89,26 @@ class ShopController extends Controller
|
||||
{
|
||||
$request->validate([
|
||||
'item_id' => 'required|integer|exists:shop_items,id',
|
||||
'room_id' => 'required|integer|exists:rooms,id',
|
||||
'recipient' => 'nullable|string|max:50',
|
||||
'message' => 'nullable|string|max:120',
|
||||
'quantity' => 'nullable|integer|min:1|max:99',
|
||||
]);
|
||||
|
||||
$user = Auth::user();
|
||||
$roomId = (int) $request->input('room_id');
|
||||
$room = Room::query()->findOrFail($roomId);
|
||||
if (! $room->canUserEnter($user) || ! $this->chatState->isUserInRoom($roomId, $user->username)) {
|
||||
return response()->json(['status' => 'error', 'message' => '请先进入当前房间后再购买聊天室道具。'], 403);
|
||||
}
|
||||
|
||||
$item = ShopItem::find($request->item_id);
|
||||
if (! $item->is_active) {
|
||||
return response()->json(['status' => 'error', 'message' => '该商品已下架。'], 400);
|
||||
}
|
||||
|
||||
$quantity = (int) $request->input('quantity', 1);
|
||||
$result = $this->shopService->buyItem(Auth::user(), $item, $quantity);
|
||||
$result = $this->shopService->buyItem($user, $item, $quantity);
|
||||
|
||||
if (! $result['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $result['message']], 400);
|
||||
@@ -109,15 +123,14 @@ class ShopController extends Controller
|
||||
|
||||
// ── 单次特效卡:广播给指定用户或全员 ────────────────────────
|
||||
if (isset($result['play_effect'])) {
|
||||
$user = Auth::user();
|
||||
$roomId = (int) $request->room_id;
|
||||
$recipient = trim($request->input('recipient', '')); // 空字符串 = 全员
|
||||
$message = trim($request->input('message', ''));
|
||||
$message = ChatContentSanitizer::htmlText($request->input('message', ''));
|
||||
|
||||
// recipient 为空或 "all" 表示全员
|
||||
$targetUsername = ($recipient === '' || $recipient === 'all') ? null : $recipient;
|
||||
$safeTargetUsername = $targetUsername ? ChatContentSanitizer::htmlText($targetUsername) : null;
|
||||
|
||||
// 广播特效事件(全员频道)
|
||||
// 广播特效事件时保留原始用户名标识,前端需要用它和当前登录名做精确比较。
|
||||
broadcast(new EffectBroadcast(
|
||||
roomId: $roomId,
|
||||
type: $result['play_effect'],
|
||||
@@ -147,9 +160,11 @@ class ShopController extends Controller
|
||||
];
|
||||
// 赠礼消息文案(改成"为XX触发了一场特效")
|
||||
$icon = $icons[$result['play_effect']] ?? '✨';
|
||||
$toStr = $targetUsername ? "【{$targetUsername}】" : '全体聊友';
|
||||
$safeBuyer = ChatContentSanitizer::htmlText($user->username);
|
||||
$safeItemName = ChatContentSanitizer::htmlText($item->name);
|
||||
$toStr = $safeTargetUsername ? "【{$safeTargetUsername}】" : '全体聊友';
|
||||
$remarkPart = $message ? " 「{$message}」" : '';
|
||||
$sysContent = "{$icon} {$user->username} 为 {$toStr} 燃放了一场【{$item->name}】特效!{$remarkPart}";
|
||||
$sysContent = "{$icon} {$safeBuyer} 为 {$toStr} 燃放了一场【{$safeItemName}】特效!{$remarkPart}";
|
||||
|
||||
// 广播系统消息到公屏(字段名与前端 appendMessage() 保持一致)
|
||||
$sysMsgEvent = new MessageSent(
|
||||
@@ -170,9 +185,6 @@ class ShopController extends Controller
|
||||
}
|
||||
} else {
|
||||
// ── 其他类型:广播购买通知到公屏 ────────────────────────────
|
||||
$user = Auth::user();
|
||||
$roomId = (int) $request->room_id;
|
||||
|
||||
if ($roomId > 0) {
|
||||
// auto_fishing 有效期文案(提前算好,避免在 match 内写复杂三元表达式)
|
||||
$fishDuration = '';
|
||||
@@ -185,13 +197,15 @@ class ShopController extends Controller
|
||||
$broadcastFromUser = $item->type === 'auto_fishing' ? '钓鱼播报' : '系统传音';
|
||||
|
||||
// 根据商品类型生成不同通知文案
|
||||
$safeBuyer = ChatContentSanitizer::htmlText($user->username);
|
||||
$safeItemName = ChatContentSanitizer::htmlText($item->name);
|
||||
$sysContent = match ($item->type) {
|
||||
'duration' => "📅 【{$user->username}】购买了全屏特效周卡「{$item->name}」,登录时将自动触发!",
|
||||
'one_time' => "🎫 【{$user->username}】购买了「{$item->name}」道具!",
|
||||
'ring' => "💍 【{$user->username}】在商店购买了一枚「{$item->name}」,不知道打算送给谁呢?",
|
||||
'auto_fishing' => "🎣 【{$user->username}】购买了「{$item->name}」,开启了 {$fishDuration} 的自动钓鱼模式!",
|
||||
ShopItem::TYPE_SIGN_REPAIR => "🗓️ 【{$user->username}】购买了 {$quantity} 张「{$item->name}」,准备把漏掉的签到补回来!",
|
||||
default => "🛒 【{$user->username}】购买了「{$item->name}」。",
|
||||
'duration' => "📅 【{$safeBuyer}】购买了全屏特效周卡「{$safeItemName}」,登录时将自动触发!",
|
||||
'one_time' => "🎫 【{$safeBuyer}】购买了「{$safeItemName}」道具!",
|
||||
'ring' => "💍 【{$safeBuyer}】在商店购买了一枚「{$safeItemName}」,不知道打算送给谁呢?",
|
||||
'auto_fishing' => "🎣 【{$safeBuyer}】购买了「{$safeItemName}」,开启了 {$fishDuration} 的自动钓鱼模式!",
|
||||
ShopItem::TYPE_SIGN_REPAIR => "🗓️ 【{$safeBuyer}】购买了 {$quantity} 张「{$safeItemName}」,准备把漏掉的签到补回来!",
|
||||
default => "🛒 【{$safeBuyer}】购买了「{$safeItemName}」。",
|
||||
};
|
||||
|
||||
broadcast(new MessageSent(
|
||||
@@ -212,7 +226,7 @@ class ShopController extends Controller
|
||||
}
|
||||
|
||||
// 返回最新金币余额
|
||||
$response['jjb'] = Auth::user()->fresh()->jjb;
|
||||
$response['jjb'] = $user->fresh()->jjb;
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user