优化vip

This commit is contained in:
2026-04-12 23:25:38 +08:00
parent 353aaaf6ce
commit dca43a2d0d
9 changed files with 346 additions and 100 deletions
+71 -5
View File
@@ -7,6 +7,8 @@
namespace App\Services;
use App\Events\EffectBroadcast;
use App\Events\MessageSent;
use App\Models\User;
use App\Models\VipLevel;
use App\Models\VipPaymentOrder;
@@ -14,6 +16,10 @@ use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use RuntimeException;
/**
* VIP 支付业务服务
* 负责支付订单创建、支付回调幂等处理,以及会员开通成功后的聊天室喜报广播。
*/
class VipPaymentService
{
/**
@@ -25,6 +31,8 @@ class VipPaymentService
public function __construct(
private readonly VipPaymentCenterClient $paymentCenterClient,
private readonly VipService $vipService,
private readonly ChatStateService $chatState,
private readonly RoomBroadcastService $roomBroadcastService,
) {}
/**
@@ -64,7 +72,7 @@ class VipPaymentService
'vip_level_id' => $vipLevel->id,
'status' => 'created',
'amount' => $price,
'subject' => ($isUpgrade ? '【升级】' : '购买') . ' VIP 会员 - ' . $vipLevel->name,
'subject' => ($isUpgrade ? '【升级】' : '购买').' VIP 会员 - '.$vipLevel->name,
'provider' => 'alipay',
'vip_name' => $vipLevel->name,
'vip_duration_days' => (int) $vipLevel->duration_days,
@@ -173,7 +181,9 @@ class VipPaymentService
*/
public function markOrderAsPaid(VipPaymentOrder $vipPaymentOrder, array $payload, string $source = 'async'): VipPaymentOrder
{
return DB::transaction(function () use ($vipPaymentOrder, $payload, $source) {
$shouldBroadcastVipCelebration = false;
$paidOrder = DB::transaction(function () use ($vipPaymentOrder, $payload, $source, &$shouldBroadcastVipCelebration) {
$lockedOrder = VipPaymentOrder::query()
->with(['user', 'vipLevel'])
->lockForUpdate()
@@ -208,19 +218,28 @@ class VipPaymentService
if (! $lockedOrder->isVipOpened()) {
// 只在首次成功支付时开通会员,防止重复回调导致会员时长重复叠加。
$user = User::query()->lockForUpdate()->findOrFail($lockedOrder->user_id);
// 从 meta 中提取是否升级
// 从订单扩展信息中识别是否升级购买,保证会员时长与等级处理一致。
$isUpgrade = (bool) ($lockedOrder->meta['is_upgrade'] ?? false);
$this->vipService->grantVip($user, $lockedOrder->vip_level_id, (int) $lockedOrder->vip_duration_days, $isUpgrade);
// 仅首次开通时触发聊天室喜报,重复回调只更新订单状态不重复刷屏。
$lockedOrder->update([
'opened_vip_at' => now(),
]);
$shouldBroadcastVipCelebration = true;
}
return $lockedOrder->fresh(['user', 'vipLevel']);
});
if ($shouldBroadcastVipCelebration) {
$this->broadcastVipPurchaseCelebration($paidOrder);
}
return $paidOrder;
}
/**
@@ -230,4 +249,51 @@ class VipPaymentService
{
return 'VPO'.date('YmdHis').random_int(1000, 9999);
}
/**
* 向用户当前在线的聊天室广播 VIP 购买成功喜报与烟花特效。
*
* @param VipPaymentOrder $vipPaymentOrder 已完成开通的支付订单
*/
private function broadcastVipPurchaseCelebration(VipPaymentOrder $vipPaymentOrder): void
{
$user = User::query()->with('vipLevel')->find($vipPaymentOrder->user_id);
if (! $user) {
return;
}
$purchasePayload = $this->roomBroadcastService->buildVipPurchasePayload($user);
if (empty($purchasePayload)) {
return;
}
$roomIds = $this->chatState->getUserRooms($user->username);
foreach ($roomIds as $roomId) {
// 先把喜报写入房间历史,确保当前在线用户和后续短时间内进房的人都能看到。
$celebrationMessage = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '会员播报',
'to_user' => '大家',
'content' => sprintf(
'<span style="color: %s; font-weight: bold;">%s</span>',
$purchasePayload['presence_color'] ?: '#f59e0b',
$purchasePayload['presence_text']
),
'is_secret' => false,
'font_color' => $purchasePayload['presence_color'] ?: '#f59e0b',
'action' => 'vip_presence',
'sent_at' => now()->toDateTimeString(),
];
$celebrationMessage = array_merge($celebrationMessage, $purchasePayload);
$this->chatState->pushMessage($roomId, $celebrationMessage);
broadcast(new MessageSent($roomId, $celebrationMessage));
// 购买成功固定播放烟花,和会员登录时的豪华表现保持一致。
broadcast(new EffectBroadcast($roomId, 'fireworks', $user->username));
}
}
}