2026-02-27 15:57:12 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件功能:商店控制器
|
2026-02-27 16:19:21 +08:00
|
|
|
|
* 提供商品列表查询、商品购买(含赠送特效广播)、改名卡使用 三个接口
|
2026-02-27 15:57:12 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
2026-02-27 16:19:21 +08:00
|
|
|
|
use App\Events\EffectBroadcast;
|
|
|
|
|
|
use App\Events\MessageSent;
|
2026-02-27 15:57:12 +08:00
|
|
|
|
use App\Models\ShopItem;
|
|
|
|
|
|
use App\Services\ShopService;
|
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
|
|
|
|
|
|
|
|
class ShopController extends Controller
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 注入商店服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function __construct(
|
|
|
|
|
|
private readonly ShopService $shopService,
|
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取商店商品列表及当前用户状态
|
|
|
|
|
|
*
|
|
|
|
|
|
* 返回字段:items(商品列表)、user_jjb(当前金币)、
|
|
|
|
|
|
* active_week_effect(当前周卡)、has_rename_card(是否持有改名卡)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function items(): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
$items = ShopItem::active()->map(fn ($item) => [
|
|
|
|
|
|
'id' => $item->id,
|
|
|
|
|
|
'name' => $item->name,
|
|
|
|
|
|
'slug' => $item->slug,
|
|
|
|
|
|
'description' => $item->description,
|
|
|
|
|
|
'icon' => $item->icon,
|
|
|
|
|
|
'price' => $item->price,
|
|
|
|
|
|
'type' => $item->type,
|
|
|
|
|
|
'duration_days' => $item->duration_days,
|
2026-03-01 16:19:45 +08:00
|
|
|
|
'duration_minutes' => $item->duration_minutes,
|
2026-03-01 15:42:25 +08:00
|
|
|
|
'intimacy_bonus' => $item->intimacy_bonus,
|
|
|
|
|
|
'charm_bonus' => $item->charm_bonus,
|
2026-02-27 15:57:12 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
2026-03-01 15:42:25 +08:00
|
|
|
|
// 统计背包中各戒指持有数量
|
|
|
|
|
|
$ringCounts = \App\Models\UserPurchase::query()
|
|
|
|
|
|
->where('user_id', $user->id)
|
|
|
|
|
|
->where('status', 'active')
|
|
|
|
|
|
->whereHas('item', fn ($q) => $q->where('type', 'ring'))
|
|
|
|
|
|
->selectRaw('shop_item_id, count(*) as qty')
|
|
|
|
|
|
->groupBy('shop_item_id')
|
|
|
|
|
|
->pluck('qty', 'shop_item_id')
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
2026-02-27 15:57:12 +08:00
|
|
|
|
return response()->json([
|
|
|
|
|
|
'items' => $items,
|
|
|
|
|
|
'user_jjb' => $user->jjb ?? 0,
|
|
|
|
|
|
'active_week_effect' => $this->shopService->getActiveWeekEffect($user),
|
|
|
|
|
|
'has_rename_card' => $this->shopService->hasRenameCard($user),
|
2026-03-01 16:19:45 +08:00
|
|
|
|
'ring_counts' => $ringCounts,
|
|
|
|
|
|
'auto_fishing_minutes_left' => $this->shopService->getActiveAutoFishingMinutesLeft($user),
|
2026-02-27 15:57:12 +08:00
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 购买商品
|
|
|
|
|
|
*
|
2026-02-27 16:19:21 +08:00
|
|
|
|
* 单次特效卡额外支持:
|
|
|
|
|
|
* - recipient 接收者用户名(传 "all" 或留空则全员可见)
|
|
|
|
|
|
* - message 公屏赠言(可选)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param Request $request 含 item_id, recipient?, message?
|
2026-02-27 15:57:12 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public function buy(Request $request): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$request->validate(['item_id' => 'required|integer|exists:shop_items,id']);
|
|
|
|
|
|
|
|
|
|
|
|
$item = ShopItem::find($request->item_id);
|
|
|
|
|
|
if (! $item->is_active) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => '该商品已下架。'], 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$result = $this->shopService->buyItem(Auth::user(), $item);
|
|
|
|
|
|
|
|
|
|
|
|
if (! $result['ok']) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => $result['message']], 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$response = ['status' => 'success', 'message' => $result['message']];
|
|
|
|
|
|
|
2026-02-27 16:19:21 +08:00
|
|
|
|
// ── 单次特效卡:广播给指定用户或全员 ────────────────────────
|
2026-02-27 15:57:12 +08:00
|
|
|
|
if (isset($result['play_effect'])) {
|
2026-02-27 16:19:21 +08:00
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
$roomId = (int) $request->room_id;
|
|
|
|
|
|
$recipient = trim($request->input('recipient', '')); // 空字符串 = 全员
|
|
|
|
|
|
$message = trim($request->input('message', ''));
|
|
|
|
|
|
|
|
|
|
|
|
// recipient 为空或 "all" 表示全员
|
|
|
|
|
|
$targetUsername = ($recipient === '' || $recipient === 'all') ? null : $recipient;
|
|
|
|
|
|
|
|
|
|
|
|
// 广播特效事件(全员频道)
|
|
|
|
|
|
broadcast(new EffectBroadcast(
|
|
|
|
|
|
roomId: $roomId,
|
|
|
|
|
|
type: $result['play_effect'],
|
|
|
|
|
|
operator: $user->username,
|
|
|
|
|
|
targetUsername: $targetUsername,
|
|
|
|
|
|
giftMessage: $message ?: null,
|
|
|
|
|
|
))->toOthers();
|
|
|
|
|
|
|
|
|
|
|
|
// 同时前端也需要播放(自己也要看到)
|
2026-02-27 15:57:12 +08:00
|
|
|
|
$response['play_effect'] = $result['play_effect'];
|
2026-02-27 16:19:21 +08:00
|
|
|
|
$response['target_username'] = $targetUsername;
|
|
|
|
|
|
$response['gift_message'] = $message ?: null;
|
|
|
|
|
|
|
|
|
|
|
|
// 公屏系统消息
|
|
|
|
|
|
if ($roomId > 0) {
|
|
|
|
|
|
$icons = [
|
|
|
|
|
|
'fireworks' => '🎆',
|
|
|
|
|
|
'rain' => '🌧',
|
|
|
|
|
|
'lightning' => '⚡',
|
|
|
|
|
|
'snow' => '❄️',
|
|
|
|
|
|
];
|
2026-03-01 17:04:11 +08:00
|
|
|
|
// 赠礼消息文案(改成"为XX触发了一场特效")
|
2026-02-27 16:19:21 +08:00
|
|
|
|
$icon = $icons[$result['play_effect']] ?? '✨';
|
|
|
|
|
|
$toStr = $targetUsername ? "【{$targetUsername}】" : '全体聊友';
|
2026-02-27 16:30:41 +08:00
|
|
|
|
$remarkPart = $message ? " 「{$message}」" : '';
|
2026-02-27 17:00:26 +08:00
|
|
|
|
$sysContent = "{$icon} {$user->username} 为 {$toStr} 燃放了一场【{$item->name}】特效!{$remarkPart}";
|
2026-02-27 16:19:21 +08:00
|
|
|
|
|
2026-02-27 16:30:41 +08:00
|
|
|
|
// 广播系统消息到公屏(字段名与前端 appendMessage() 保持一致)
|
2026-02-27 16:26:16 +08:00
|
|
|
|
$sysMsgEvent = new MessageSent(
|
2026-02-27 16:19:21 +08:00
|
|
|
|
roomId: $roomId,
|
|
|
|
|
|
message: [
|
|
|
|
|
|
'id' => 0,
|
|
|
|
|
|
'room_id' => $roomId,
|
2026-02-27 16:30:41 +08:00
|
|
|
|
'from_user' => '系统传音', // 触发金色左边框样式(已有处理分支)
|
|
|
|
|
|
'to_user' => '大家',
|
2026-02-27 16:19:21 +08:00
|
|
|
|
'content' => $sysContent,
|
2026-02-27 16:30:41 +08:00
|
|
|
|
'font_color' => '#cc6600',
|
|
|
|
|
|
'sent_at' => now()->format('H:i:s'),
|
|
|
|
|
|
'is_secret' => false,
|
|
|
|
|
|
'action' => null,
|
2026-02-27 16:19:21 +08:00
|
|
|
|
]
|
2026-02-27 16:26:16 +08:00
|
|
|
|
);
|
|
|
|
|
|
broadcast($sysMsgEvent);
|
2026-02-27 16:19:21 +08:00
|
|
|
|
}
|
2026-03-01 17:04:11 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// ── 其他类型:广播购买通知到公屏 ────────────────────────────
|
|
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
$roomId = (int) $request->room_id;
|
|
|
|
|
|
|
|
|
|
|
|
if ($roomId > 0) {
|
|
|
|
|
|
// auto_fishing 有效期文案(提前算好,避免在 match 内写复杂三元表达式)
|
|
|
|
|
|
$fishDuration = '';
|
|
|
|
|
|
if ($item->type === 'auto_fishing') {
|
|
|
|
|
|
$mins = (int) ($item->duration_minutes ?? 0);
|
|
|
|
|
|
$fishDuration = $mins >= 60 ? floor($mins / 60).'小时' : $mins.'分钟';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据商品类型生成不同通知文案
|
|
|
|
|
|
$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} 的自动钓鱼模式!",
|
|
|
|
|
|
default => "🛒 【{$user->username}】购买了「{$item->name}」。",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
broadcast(new MessageSent(
|
|
|
|
|
|
roomId: $roomId,
|
|
|
|
|
|
message: [
|
|
|
|
|
|
'id' => 0,
|
|
|
|
|
|
'room_id' => $roomId,
|
|
|
|
|
|
'from_user' => '系统传音',
|
|
|
|
|
|
'to_user' => '大家',
|
|
|
|
|
|
'content' => $sysContent,
|
|
|
|
|
|
'font_color' => '#7c3aed',
|
|
|
|
|
|
'sent_at' => now()->format('H:i:s'),
|
|
|
|
|
|
'is_secret' => false,
|
|
|
|
|
|
'action' => null,
|
|
|
|
|
|
]
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
2026-02-27 15:57:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回最新金币余额
|
|
|
|
|
|
$response['jjb'] = Auth::user()->fresh()->jjb;
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json($response);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 使用改名卡修改昵称
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param Request $request 含 new_name
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function rename(Request $request): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$request->validate([
|
|
|
|
|
|
'new_name' => 'required|string|min:1|max:10',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$result = $this->shopService->useRenameCard(Auth::user(), $request->new_name);
|
|
|
|
|
|
|
|
|
|
|
|
if (! $result['ok']) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => $result['message']], 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json(['status' => 'success', 'message' => $result['message']]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|