Files
chatroom/app/Services/ShopService.php

310 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能:商店业务逻辑服务层
* 处理商品购买、周卡激活/覆盖、改名卡使用等全部核心业务
*/
namespace App\Services;
use App\Models\ShopItem;
use App\Models\User;
use App\Models\UsernameBlacklist;
use App\Models\UserPurchase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class ShopService
{
/**
* 购买商品入口:扣金币、按类型分发处理
*
* @return array{ok:bool, message:string, play_effect?:string}
*/
public function buyItem(User $user, ShopItem $item): array
{
// 校验金币是否足够
if ($user->jjb < $item->price) {
return ['ok' => false, 'message' => "金币不足,购买 [{$item->name}] 需要 {$item->price} 金币,当前仅有 {$user->jjb} 金币。"];
}
return match ($item->type) {
'instant' => $this->buyInstant($user, $item),
'duration' => $this->buyWeekCard($user, $item),
'one_time' => $this->buyRenameCard($user, $item),
'ring' => $this->buyRing($user, $item),
'auto_fishing' => $this->buyAutoFishingCard($user, $item),
default => ['ok' => false, 'message' => '未知商品类型'],
};
}
/**
* 购买单次特效卡:立即扣金币,记录已用,返回需要播放的特效 key
*
* @return array{ok:bool, message:string, play_effect?:string}
*/
public function buyInstant(User $user, ShopItem $item): array
{
DB::transaction(function () use ($user, $item) {
// 扣除金币
$user->decrement('jjb', $item->price);
// 写入已使用记录(用于统计)
UserPurchase::create([
'user_id' => $user->id,
'shop_item_id' => $item->id,
'status' => 'used',
'price_paid' => $item->price,
'used_at' => Carbon::now(),
]);
});
return [
'ok' => true,
'message' => "购买成功!{$item->icon} {$item->name} 正在为您播放...",
'play_effect' => $item->effectKey(), // 返回特效 key 让前端立即播放
];
}
/**
* 购买周卡取消旧周卡金币不退激活新周卡有效期7天
*
* @return array{ok:bool, message:string}
*/
public function buyWeekCard(User $user, ShopItem $item): array
{
DB::transaction(function () use ($user, $item) {
// 将所有已激活的周卡标记为 cancelled金币不退
UserPurchase::where('user_id', $user->id)
->where('status', 'active')
->whereHas('shopItem', fn ($q) => $q->where('type', 'duration'))
->update(['status' => 'cancelled']);
// 扣除金币
$user->decrement('jjb', $item->price);
// 写入新的激活记录
UserPurchase::create([
'user_id' => $user->id,
'shop_item_id' => $item->id,
'status' => 'active',
'price_paid' => $item->price,
'expires_at' => Carbon::now()->addDays($item->duration_days ?? 7),
]);
});
return ['ok' => true, 'message' => "购买成功!{$item->icon} {$item->name} 已激活下次登录自动生效连续7天"];
}
/**
* 购买改名卡:扣金币、写 active 记录备用
*
* @return array{ok:bool, message:string}
*/
public function buyRenameCard(User $user, ShopItem $item): array
{
// 检查是否已有未使用的改名卡
$existing = UserPurchase::where('user_id', $user->id)
->where('status', 'active')
->whereHas('shopItem', fn ($q) => $q->where('slug', 'rename_card'))
->exists();
if ($existing) {
return ['ok' => false, 'message' => '您已有一张未使用的改名卡,使用后再购买。'];
}
DB::transaction(function () use ($user, $item) {
$user->decrement('jjb', $item->price);
UserPurchase::create([
'user_id' => $user->id,
'shop_item_id' => $item->id,
'status' => 'active',
'price_paid' => $item->price,
]);
});
return ['ok' => true, 'message' => '改名卡购买成功!请在商店中使用改名卡修改昵称。'];
}
/**
* 使用改名卡:校验新名、加黑名单、更新用户名
*
* @param string $newName 新昵称
* @return array{ok:bool, message:string}
*/
public function useRenameCard(User $user, string $newName): array
{
$newName = trim($newName);
// 格式校验1-10字符中英文数字下划线
if (! preg_match('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9_]{1,10}$/u', $newName)) {
return ['ok' => false, 'message' => '新昵称格式不合法1-10字中英文/数字/下划线)。'];
}
// 不能与自己现有名相同
if ($newName === $user->username) {
return ['ok' => false, 'message' => '新昵称与当前昵称相同,请重新输入。'];
}
// 不能与其他用户重名
if (User::where('username', $newName)->exists()) {
return ['ok' => false, 'message' => '该昵称已被他人注册,请换一个。'];
}
// 不能在黑名单保留期内
if ($blockingRecord = UsernameBlacklist::getBlockingRecord($newName)) {
$reason = '';
if ($blockingRecord->type === 'permanent') {
$reason = "(包含敏感词:{$blockingRecord->username}";
} else {
$reason = '(处于曾用名保护期)';
}
return ['ok' => false, 'message' => "该昵称已被系统禁止使用{$reason}"];
}
// 查找有效的改名卡记录
$purchase = UserPurchase::where('user_id', $user->id)
->where('status', 'active')
->whereHas('shopItem', fn ($q) => $q->where('slug', 'rename_card'))
->first();
if (! $purchase) {
return ['ok' => false, 'message' => '您没有可用的改名卡,请先购买。'];
}
DB::transaction(function () use ($user, $purchase, $newName) {
$oldName = $user->username;
// 修改用户名
$user->username = $newName;
$user->save();
// 旧名入黑名单保留30天
UsernameBlacklist::updateOrCreate(
['username' => $oldName],
['reserved_until' => Carbon::now()->addDays(30), 'created_at' => Carbon::now()]
);
// 标记改名卡为已使用
$purchase->update(['status' => 'used', 'used_at' => Carbon::now()]);
});
return ['ok' => true, 'message' => "改名成功!您的新昵称为【{$newName}旧昵称将保留30天黑名单。注意历史消息中的旧名不会同步修改。"];
}
/**
* 获取用户当前激活的周卡特效 key如 fireworks/rain/lightning/snow
* 过期的周卡会自动标记为 expired
*/
public function getActiveWeekEffect(User $user): ?string
{
$purchase = UserPurchase::with('shopItem')
->where('user_id', $user->id)
->where('status', 'active')
->whereHas('shopItem', fn ($q) => $q->where('type', 'duration'))
->first();
if (! $purchase) {
return null;
}
// 检查是否过期
if ($purchase->expires_at && $purchase->expires_at->isPast()) {
$purchase->update(['status' => 'expired']);
return null;
}
return $purchase->shopItem->effectKey();
}
/**
* 购买结婚戒指扣金币、写入背包active 状态,等待结婚时消耗)。
*
* @return array{ok:bool, message:string}
*/
public function buyRing(User $user, ShopItem $item): array
{
DB::transaction(function () use ($user, $item): void {
// 扣除金币
$user->decrement('jjb', $item->price);
// 写入背包active = 未使用,求婚时变为 used_pending→used 或 lost
UserPurchase::create([
'user_id' => $user->id,
'shop_item_id' => $item->id,
'status' => 'active',
'price_paid' => $item->price,
]);
});
return [
'ok' => true,
'message' => "💍 {$item->name} 购买成功!已放入背包,可用于求婚。",
];
}
/**
* 获取用户当前激活的改名卡(是否持有未用改名卡)
*/
public function hasRenameCard(User $user): bool
{
return UserPurchase::where('user_id', $user->id)
->where('status', 'active')
->whereHas('shopItem', fn ($q) => $q->where('slug', 'rename_card'))
->exists();
}
/**
* 购买自动钓鱼卡:手刺金币,写入 active 记录,到期时间 = 现在 + duration_minutes。
*
* @return array{ok:bool, message:string}
*/
public function buyAutoFishingCard(User $user, ShopItem $item): array
{
$minutes = (int) $item->duration_minutes;
if ($minutes <= 0) {
return ['ok' => false, 'message' => '该钓鱼卡配置异常,请联系管理员。'];
}
DB::transaction(function () use ($user, $item, $minutes): void {
// 手刺金币
$user->decrement('jjb', $item->price);
// 写入背包active刻起计时
UserPurchase::create([
'user_id' => $user->id,
'shop_item_id' => $item->id,
'status' => 'active',
'price_paid' => $item->price,
'expires_at' => Carbon::now()->addMinutes($minutes),
]);
});
$hours = round($minutes / 60, 1);
return [
'ok' => true,
'message' => "🎣 {$item->name}购买成功!{$hours}小时内鬼鱼自动收篼,尽情摆烂!",
];
}
/**
* 获取用户当前有效的自动钓鱼卡剩余分钟数(没有则返回 0
*/
public function getActiveAutoFishingMinutesLeft(User $user): int
{
$purchase = UserPurchase::where('user_id', $user->id)
->where('status', 'active')
->whereNotNull('expires_at')
->whereHas('shopItem', fn ($q) => $q->where('type', 'auto_fishing'))
->where('expires_at', '>', Carbon::now())
->orderByDesc('expires_at') // 取最晚过期的
->first();
if (! $purchase) {
return 0;
}
return (int) Carbon::now()->diffInMinutes($purchase->expires_at, false);
}
}