补齐座驾购买金币流水
This commit is contained in:
@@ -35,6 +35,9 @@ enum CurrencySource: string
|
||||
/** 商城购买消耗(扣除金币) */
|
||||
case SHOP_BUY = 'shop_buy';
|
||||
|
||||
/** 购买聊天室座驾消耗(扣除金币) */
|
||||
case RIDE_BUY = 'ride_buy';
|
||||
|
||||
/** 管理员手动调整(后台直接修改经验/金币/魅力) */
|
||||
case ADMIN_ADJUST = 'admin_adjust';
|
||||
|
||||
@@ -174,6 +177,7 @@ enum CurrencySource: string
|
||||
self::RECV_GIFT => '收到礼物',
|
||||
self::NEWBIE_BONUS => '新人礼包',
|
||||
self::SHOP_BUY => '商城购买',
|
||||
self::RIDE_BUY => '座驾购买',
|
||||
self::ADMIN_ADJUST => '管理员调整',
|
||||
self::POSITION_REWARD => '职务奖励',
|
||||
self::SIGN_IN => '每日签到',
|
||||
|
||||
@@ -61,7 +61,7 @@ class RideController extends Controller
|
||||
}
|
||||
|
||||
$item = Ride::query()->findOrFail((int) $request->integer('item_id'));
|
||||
$result = $this->rideService->buy($user, $item);
|
||||
$result = $this->rideService->buy($user, $item, $roomId);
|
||||
|
||||
if (! $result['ok']) {
|
||||
return response()->json(['status' => 'error', 'message' => $result['message']], 400);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Models\Ride;
|
||||
use App\Models\User;
|
||||
use App\Models\UserRidePurchase;
|
||||
@@ -22,6 +23,13 @@ use Illuminate\Support\Facades\DB;
|
||||
*/
|
||||
class RideService
|
||||
{
|
||||
/**
|
||||
* 构造座驾服务依赖。
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly UserCurrencyService $currencyService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取全部上架座驾商品。
|
||||
*
|
||||
@@ -116,7 +124,7 @@ class RideService
|
||||
*
|
||||
* @return array{ok:bool, message:string, current_ride?:array<string, mixed>}
|
||||
*/
|
||||
public function buy(User $user, Ride $item): array
|
||||
public function buy(User $user, Ride $item, ?int $roomId = null): array
|
||||
{
|
||||
if (! $item->is_active) {
|
||||
return ['ok' => false, 'message' => '该座驾暂未上架。'];
|
||||
@@ -131,7 +139,7 @@ class RideService
|
||||
return ['ok' => false, 'message' => "金币不足,购买【{$item->name}】需要 {$item->price} 金币,当前仅有 {$user->jjb} 金币。"];
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($user, $item, $days): void {
|
||||
$purchased = DB::transaction(function () use ($user, $item, $days, $roomId): bool {
|
||||
$now = Carbon::now();
|
||||
|
||||
// 先清理已过期的 active 座驾,避免旧状态影响替换判断。
|
||||
@@ -149,8 +157,17 @@ class RideService
|
||||
->orderByDesc('expires_at')
|
||||
->first();
|
||||
|
||||
// 座驾购买必须先扣金币,后续续期或替换都在同一个事务内完成。
|
||||
$user->decrement('jjb', $item->price);
|
||||
$balanceAfter = $this->currencyService->deductGoldIfEnough(
|
||||
$user,
|
||||
(int) $item->price,
|
||||
CurrencySource::RIDE_BUY,
|
||||
"购买聊天室座驾:{$item->name}",
|
||||
$roomId,
|
||||
);
|
||||
|
||||
if ($balanceAfter === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($activeRide && (int) $activeRide->ride_id === (int) $item->id) {
|
||||
$baseTime = $activeRide->expires_at && $activeRide->expires_at->greaterThan($now)
|
||||
@@ -167,7 +184,7 @@ class RideService
|
||||
'expires_at' => $baseTime->copy()->addDays($days),
|
||||
]);
|
||||
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($activeRide) {
|
||||
@@ -182,8 +199,14 @@ class RideService
|
||||
'price_paid' => $item->price,
|
||||
'expires_at' => $now->copy()->addDays($days),
|
||||
]);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (! $purchased) {
|
||||
return ['ok' => false, 'message' => "金币不足,购买【{$item->name}】需要 {$item->price} 金币,当前仅有 {$user->fresh()->jjb} 金币。"];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => "购买成功!{$item->icon} {$item->name} 已激活({$days}天有效)。",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Models\Ride;
|
||||
use App\Models\Room;
|
||||
use App\Models\User;
|
||||
@@ -80,6 +81,10 @@ class RideControllerTest extends TestCase
|
||||
'user_id' => $user->id,
|
||||
'ride_id' => $ride->id,
|
||||
]);
|
||||
$this->assertDatabaseMissing('user_currency_logs', [
|
||||
'user_id' => $user->id,
|
||||
'source' => CurrencySource::RIDE_BUY->value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +113,15 @@ class RideControllerTest extends TestCase
|
||||
'status' => 'active',
|
||||
'price_paid' => 18888,
|
||||
]);
|
||||
$this->assertDatabaseHas('user_currency_logs', [
|
||||
'user_id' => $user->id,
|
||||
'currency' => 'gold',
|
||||
'amount' => -18888,
|
||||
'balance_after' => 11112,
|
||||
'source' => CurrencySource::RIDE_BUY->value,
|
||||
'remark' => '购买聊天室座驾:测试座驾',
|
||||
'room_id' => $room->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
-4
@@ -16,10 +16,17 @@ abstract class TestCase extends BaseTestCase
|
||||
*/
|
||||
protected function flushChatRoomRedisState(): void
|
||||
{
|
||||
$keys = Redis::keys('room:*');
|
||||
$prefix = config('database.redis.options.prefix', '');
|
||||
$cursor = '0';
|
||||
|
||||
if ($keys !== []) {
|
||||
Redis::del(...$keys);
|
||||
}
|
||||
do {
|
||||
[$cursor, $keys] = Redis::scan($cursor, ['match' => $prefix.'room:*', 'count' => 200]);
|
||||
|
||||
foreach ($keys ?? [] as $fullKey) {
|
||||
// Laravel Redis Facade 写入时会自动追加前缀,删除时要还原成业务短 key。
|
||||
$shortKey = $prefix ? substr((string) $fullKey, strlen($prefix)) : (string) $fullKey;
|
||||
Redis::del($shortKey);
|
||||
}
|
||||
} while ($cursor !== '0');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user